mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
NEW: Docs rebuild for compliance with Gatsby (#9316)
* Rewrite callout blocks * Rewrite frontmatter * Replace missing frontmatter * Replace note callouts * Fix icons * Hide children * Tidy up code blocks * Replace legacy code blocks with fenced
This commit is contained in:
parent
8c41dbb8b4
commit
54e7223d98
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Server Requirements
|
||||||
|
icon: server
|
||||||
|
summary: What you will need to run Silverstripe CMS on a web server
|
||||||
|
---
|
||||||
|
|
||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
SilverStripe CMS needs to be installed on a web server. Content authors and website administrators use their web browser
|
SilverStripe CMS needs to be installed on a web server. Content authors and website administrators use their web browser
|
||||||
@ -10,16 +16,18 @@ Our web-based [PHP installer](installation/) can check if you meet the requireme
|
|||||||
|
|
||||||
* PHP 5.3.3+, <7.2
|
* PHP 5.3.3+, <7.2
|
||||||
* We recommend using a PHP accelerator or opcode cache, such as [xcache](http://xcache.lighttpd.net/) or [WinCache](http://www.iis.net/download/wincacheforphp).
|
* We recommend using a PHP accelerator or opcode cache, such as [xcache](http://xcache.lighttpd.net/) or [WinCache](http://www.iis.net/download/wincacheforphp).
|
||||||
|
```
|
||||||
* Note: Some PHP 5.5+ packages already have [Zend OpCache](http://php.net/manual/en/book.opcache.php) installed by default. If this is the case on your system, do not try and run additional opcaches alongside Zend OpCache without first disabling it, as it will likely have unexpected consequences.
|
* Note: Some PHP 5.5+ packages already have [Zend OpCache](http://php.net/manual/en/book.opcache.php) installed by default. If this is the case on your system, do not try and run additional opcaches alongside Zend OpCache without first disabling it, as it will likely have unexpected consequences.
|
||||||
* Allocate at least 48MB of memory to each PHP process. (SilverStripe can be resource hungry for some intensive operations.)
|
```
|
||||||
* Required modules: dom, gd2, fileinfo, hash, iconv, mbstring, mysqli (or other database driver), session, simplexml, tokenizer, xml.
|
* Required modules: dom, gd2, fileinfo, hash, iconv, mbstring, mysqli (or other database driver), session, simplexml, tokenizer, xml.
|
||||||
* Recommended configuration
|
* Recommended configuration
|
||||||
|
|
||||||
|
```
|
||||||
safe_mode = Off
|
safe_mode = Off
|
||||||
magic_quotes_gpc = Off
|
magic_quotes_gpc = Off
|
||||||
memory_limit = 48M
|
memory_limit = 48M
|
||||||
|
|
||||||
* See [phpinfo()](http://php.net/manual/en/function.phpinfo.php) for more information about your environment
|
```
|
||||||
* One of the following databases:
|
* One of the following databases:
|
||||||
* MySQL 5.0+
|
* MySQL 5.0+
|
||||||
* PostgreSQL 8.3+ (requires ["postgresql" module](http://silverstripe.org/postgresql-module))
|
* PostgreSQL 8.3+ (requires ["postgresql" module](http://silverstripe.org/postgresql-module))
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Linux and Unix
|
||||||
|
summary: How to install Silversripe on a *nix system
|
||||||
|
icon: linux
|
||||||
|
---
|
||||||
|
|
||||||
# Installation on Linux, Unix and *nix like Operating Systems
|
# 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 as *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.
|
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 as *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.
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
# Mac OSX with MAMP
|
---
|
||||||
|
title: Mac OSX
|
||||||
|
summary: How to set up Silverstripe CMS on a MacOS system using MAMP
|
||||||
|
icon: apple
|
||||||
|
---
|
||||||
|
# MacOS with MAMP
|
||||||
|
|
||||||
This topic covers setting up your Mac as a web server and installing SilverStripe.
|
This topic covers setting up your Mac as a web server and installing SilverStripe.
|
||||||
|
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Windows
|
||||||
|
summary: How to install Silverstripe CMS on a Windows environment
|
||||||
|
icon: windows
|
||||||
|
---
|
||||||
# Windows with WAMPServer 2.5+
|
# Windows with WAMPServer 2.5+
|
||||||
|
|
||||||
An easy and reliable approach to getting SilverStripe running on Windows is to use Apache, which can be conveniently
|
An easy and reliable approach to getting SilverStripe running on Windows is to use Apache, which can be conveniently
|
||||||
@ -27,9 +32,6 @@ See the [Composer documentation](https://getcomposer.org/doc/00-intro.md#install
|
|||||||
|
|
||||||
Once you have installed the above, open a command line and use the following command to get a fresh copy of SilverStripe stable code installed into a 'silverstripe' sub-folder (note here we are using gitbash paths).
|
Once you have installed the above, open a command line and use the following command to get a fresh copy of SilverStripe stable code installed into a 'silverstripe' sub-folder (note here we are using gitbash paths).
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cd /c/wamp/www
|
|
||||||
$ composer create-project silverstripe/installer ./silverstripe
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Zip download
|
### Zip download
|
||||||
@ -65,11 +67,3 @@ control.
|
|||||||
Due to some changes to `mod_dir` in [Apache 2.4](http://httpd.apache.org/docs/current/mod/mod_dir.html#DirectoryCheckHandler) (precedence of handlers), index.php gets added to the URLs as soon as you navigate to the homepage of your site. Further requests are then handled by index.php rather than `mod_rewrite` (framework/main.php). To fix this place the following within the `mod_rewrite` section of your .htaccess file:
|
Due to some changes to `mod_dir` in [Apache 2.4](http://httpd.apache.org/docs/current/mod/mod_dir.html#DirectoryCheckHandler) (precedence of handlers), index.php gets added to the URLs as soon as you navigate to the homepage of your site. Further requests are then handled by index.php rather than `mod_rewrite` (framework/main.php). To fix this place the following within the `mod_rewrite` section of your .htaccess file:
|
||||||
|
|
||||||
```
|
```
|
||||||
<IfModule mod_rewrite.c>
|
|
||||||
# Turn off index.php handling requests to the homepage fixes issue in apache >=2.4
|
|
||||||
<IfModule mod_dir.c>
|
|
||||||
DirectoryIndex disabled
|
|
||||||
</IfModule>
|
|
||||||
# ------ #
|
|
||||||
</IfModule>
|
|
||||||
```
|
|
||||||
|
@ -14,41 +14,47 @@ which packages up the whole environment into a convenient application.
|
|||||||
Since we're compiling PHP, some build tooling is required.
|
Since we're compiling PHP, some build tooling is required.
|
||||||
Run the following command to install Xcode Command Line Tools.
|
Run the following command to install Xcode Command Line Tools.
|
||||||
|
|
||||||
|
```
|
||||||
xcode-select --install
|
xcode-select --install
|
||||||
|
|
||||||
Now you can install Homebrew itself:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
|
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
|
||||||
|
|
||||||
## Install PHP
|
```
|
||||||
|
|
||||||
First we're telling Homebrew about some new repositories to get the PHP installation from:
|
First we're telling Homebrew about some new repositories to get the PHP installation from:
|
||||||
|
|
||||||
|
```
|
||||||
brew tap homebrew/dupes
|
brew tap homebrew/dupes
|
||||||
brew tap homebrew/php
|
brew tap homebrew/php
|
||||||
|
|
||||||
We're installing PHP 5.5 here, with the required `mcrypt` module:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
brew install php55 php55-mcrypt
|
brew install php55 php55-mcrypt
|
||||||
|
|
||||||
There's a [Homebrew Troubleshooting](https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Troubleshooting.md) guide if Homebrew doesn't work out as expected (run `brew update` and `brew doctor`).
|
```
|
||||||
|
|
||||||
Have a look at the [brew-php-switcher](https://github.com/philcook/brew-php-switcher)
|
Have a look at the [brew-php-switcher](https://github.com/philcook/brew-php-switcher)
|
||||||
project to install multiple PHP versions in parallel and switch between them easily.
|
project to install multiple PHP versions in parallel and switch between them easily.
|
||||||
|
|
||||||
## Install the Database (MariaDB/MySQL)
|
## Install the Database (MariaDB/MySQL)
|
||||||
|
|
||||||
|
```
|
||||||
brew install mariadb
|
brew install mariadb
|
||||||
unset TMPDIR
|
unset TMPDIR
|
||||||
mysql_install_db --user=`whoami` --basedir="$(brew --prefix mariadb)" --datadir=/usr/local/var/mysql --tmpdir=/tmp
|
mysql_install_db --user=`whoami` --basedir="$(brew --prefix mariadb)" --datadir=/usr/local/var/mysql --tmpdir=/tmp
|
||||||
mysql.server start
|
mysql.server start
|
||||||
'/usr/local/opt/mariadb/bin/mysql_secure_installation'
|
'/usr/local/opt/mariadb/bin/mysql_secure_installation'
|
||||||
|
|
||||||
To start the database server on boot, run the following:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
ln -sfv /usr/local/opt/mariadb/*.plist ~/Library/LaunchAgents
|
ln -sfv /usr/local/opt/mariadb/*.plist ~/Library/LaunchAgents
|
||||||
|
|
||||||
You can also use `mysql.server start` and `mysql.server stop` on demand.
|
```
|
||||||
|
|
||||||
## Configure PHP and Apache
|
## Configure PHP and Apache
|
||||||
|
|
||||||
@ -57,16 +63,19 @@ We're not installing Apache, since OSX already ships with a perfectly fine insta
|
|||||||
Edit the existing configuration at `/etc/apache2/httpd.conf`,
|
Edit the existing configuration at `/etc/apache2/httpd.conf`,
|
||||||
and uncomment/add the following lines to activate the required modules:
|
and uncomment/add the following lines to activate the required modules:
|
||||||
|
|
||||||
|
```
|
||||||
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
|
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
|
||||||
LoadModule php5_module /usr/local/opt/php55/libexec/apache2/libphp5.so
|
LoadModule php5_module /usr/local/opt/php55/libexec/apache2/libphp5.so
|
||||||
|
|
||||||
Change the `DocumentRoot` setting to your user folder (replacing `<user>` with your OSX user name):
|
```
|
||||||
|
|
||||||
|
```
|
||||||
DocumentRoot "/Users/<user>/Sites"
|
DocumentRoot "/Users/<user>/Sites"
|
||||||
|
|
||||||
Now find the section starting with `<Directory "/Library/WebServer/Documents">` and change it as follows,
|
```
|
||||||
again replacing `<user>` with your OSX user name:
|
again replacing `<user>` with your OSX user name:
|
||||||
|
|
||||||
|
```
|
||||||
<Directory "/Users/<user>/Sites">
|
<Directory "/Users/<user>/Sites">
|
||||||
Options FollowSymLinks Multiviews
|
Options FollowSymLinks Multiviews
|
||||||
MultiviewsMatch Any
|
MultiviewsMatch Any
|
||||||
@ -74,27 +83,31 @@ again replacing `<user>` with your OSX user name:
|
|||||||
Require all granted
|
Require all granted
|
||||||
</Directory>
|
</Directory>
|
||||||
|
|
||||||
We also recommend running the web server process with your own user on a development environment,
|
```
|
||||||
since it makes permissions easier to handle when running commands both
|
since it makes permissions easier to handle when running commands both
|
||||||
from the command line and through the web server. Find and adjust the following options,
|
from the command line and through the web server. Find and adjust the following options,
|
||||||
replacing the `<user>` placeholder:
|
replacing the `<user>` placeholder:
|
||||||
|
|
||||||
|
```
|
||||||
User <user>
|
User <user>
|
||||||
Group staff
|
Group staff
|
||||||
|
|
||||||
Now start the web server:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
sudo apachectl start
|
sudo apachectl start
|
||||||
|
|
||||||
Every configuration change requires a restart:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
sudo apachectl restart
|
sudo apachectl restart
|
||||||
|
|
||||||
You can also load this webserver on boot:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
sudo launchctl load -w /System/Library/LaunchDaemons/org.apache.httpd.plist
|
sudo launchctl load -w /System/Library/LaunchDaemons/org.apache.httpd.plist
|
||||||
|
|
||||||
After starting the webserver, you should see a simple "Forbidden" page generated by Apache
|
```
|
||||||
when accessing `http://localhost`.
|
when accessing `http://localhost`.
|
||||||
|
|
||||||
## SilverStripe Installation
|
## SilverStripe Installation
|
||||||
|
@ -26,15 +26,10 @@ Vagrant downloads and sets up an entire operating system.
|
|||||||
Most of this requires using only the command line and text editor or IDE.
|
Most of this requires using only the command line and text editor or IDE.
|
||||||
|
|
||||||
Create a folder where your vagrant will be in and browse to the folder in the command line:
|
Create a folder where your vagrant will be in and browse to the folder in the command line:
|
||||||
```bash
|
|
||||||
mkdir virtuallythere
|
|
||||||
cd virtuallythere
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Creating the Vagrantfile
|
### Creating the Vagrantfile
|
||||||
Create/Browse to the folder you’ll be developing in:
|
Create/Browse to the folder you’ll be developing in:
|
||||||
```bash
|
|
||||||
vagrant init
|
|
||||||
```
|
```
|
||||||
|
|
||||||
In its current state, you could start the vagrant machine and it will run, but you won't be able to do much with it yet.
|
In its current state, you could start the vagrant machine and it will run, but you won't be able to do much with it yet.
|
||||||
@ -43,15 +38,11 @@ In its current state, you could start the vagrant machine and it will run, but y
|
|||||||
Open the `Vagrantfile` that was created in your vagrant folder with your preferred text editor.
|
Open the `Vagrantfile` that was created in your vagrant folder with your preferred text editor.
|
||||||
|
|
||||||
Look for the line which describes the box you are going to use:
|
Look for the line which describes the box you are going to use:
|
||||||
```ruby
|
|
||||||
config.vm.box = "base"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This defines what pre-built Operating System the vagrant machine will be using. We'll be changing `base` to something closer to what we’d like, perhaps similar to your production server, you can find a range of boxes [listed here](https://atlas.hashicorp.com/search)
|
This defines what pre-built Operating System the vagrant machine will be using. We'll be changing `base` to something closer to what we’d like, perhaps similar to your production server, you can find a range of boxes [listed here](https://atlas.hashicorp.com/search)
|
||||||
|
|
||||||
We've chosen to use `RHEL7.0`, but you can easily change it to suit your needs.
|
We've chosen to use `RHEL7.0`, but you can easily change it to suit your needs.
|
||||||
```ruby
|
|
||||||
config.vm.box = "box-cutter/centos70"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
*Important*: Because this is redhat, the shell commands used later on will be using `yum install` instead of `apt-get install` for Debian based boxes.
|
*Important*: Because this is redhat, the shell commands used later on will be using `yum install` instead of `apt-get install` for Debian based boxes.
|
||||||
@ -61,14 +52,9 @@ Now we’ll add the vagrant machine to our computer’s private network, this wi
|
|||||||
So this will be your own development environment!
|
So this will be your own development environment!
|
||||||
|
|
||||||
To do that, look for this line:
|
To do that, look for this line:
|
||||||
```ruby
|
|
||||||
# config.vm.network "private_network", ip: "192.168.33.10"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
First we’ll need to uncomment it, so delete only the `#` at the start of the line, then add a hostname IP address of your choice to use.
|
First we’ll need to uncomment it, so delete only the `#` at the start of the line, then add a hostname IP address of your choice to use.
|
||||||
```ruby
|
|
||||||
config.vm.hostname = "virtuallythere.dev"
|
|
||||||
config.vm.network "privatenetwork", ip: "10.1.2.50"
|
|
||||||
```
|
```
|
||||||
### Syncing files
|
### Syncing files
|
||||||
Next we’ll sync our website folder to the virtual machine, so it has the files needed to run SilverStripe. There are many different ways to do this, depending on your own preferences and possibly different boxes.
|
Next we’ll sync our website folder to the virtual machine, so it has the files needed to run SilverStripe. There are many different ways to do this, depending on your own preferences and possibly different boxes.
|
||||||
@ -76,22 +62,13 @@ Next we’ll sync our website folder to the virtual machine, so it has the files
|
|||||||
To keep things simple, we’re going to sync our vagrant folder to the virtual machine, so everything in your vagrant folder will be visible to the virtual machine.
|
To keep things simple, we’re going to sync our vagrant folder to the virtual machine, so everything in your vagrant folder will be visible to the virtual machine.
|
||||||
|
|
||||||
Find this line:
|
Find this line:
|
||||||
```ruby
|
|
||||||
config.vm.synced_folder "../data", "/vagrant_data"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then change to match this:
|
Then change to match this:
|
||||||
```ruby
|
|
||||||
config.vm.synced_folder ".", "/vagrant"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Setting resources
|
### Setting resources
|
||||||
This step is optional, but it is recommended to configure the virtual machine resources allocated to it, so it doesn’t take more resources than it should, something like this should be enough to start with:
|
This step is optional, but it is recommended to configure the virtual machine resources allocated to it, so it doesn’t take more resources than it should, something like this should be enough to start with:
|
||||||
```ruby
|
|
||||||
config.vm.provider "virtualbox" do |vb|
|
|
||||||
vb.memory = "1024"
|
|
||||||
vb.name = "virtuallythere"
|
|
||||||
end
|
|
||||||
```
|
```
|
||||||
|
|
||||||
*Important*: This is for Virtualbox again, change “virtualbox” to the virtual platform that you are using, you might need to make sure the setting `vb.memory` is supported by the platform you’re using because it may be different.
|
*Important*: This is for Virtualbox again, change “virtualbox” to the virtual platform that you are using, you might need to make sure the setting `vb.memory` is supported by the platform you’re using because it may be different.
|
||||||
@ -100,34 +77,13 @@ end
|
|||||||
Now we need to setup our environment using shell scripts, this will install software that you need for your server to be working and usable. You could even customise the setup to be closer like your production server.
|
Now we need to setup our environment using shell scripts, this will install software that you need for your server to be working and usable. You could even customise the setup to be closer like your production server.
|
||||||
|
|
||||||
For now find these lines:
|
For now find these lines:
|
||||||
```ruby
|
|
||||||
# config.vm.provision "shell", inline: <<-SHELL
|
|
||||||
# sudo apt-get update
|
|
||||||
# sudo apt-get install -y apache2
|
|
||||||
# SHELL
|
|
||||||
```
|
```
|
||||||
And modify it to call a shell script in your vagrant folder:
|
And modify it to call a shell script in your vagrant folder:
|
||||||
```ruby
|
|
||||||
config.vm.provision "shell", path: "setup.sh"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
*Important*: We’re using shell script because we’re using a Linux server, please use the scripting language that your server environment supports.
|
*Important*: We’re using shell script because we’re using a Linux server, please use the scripting language that your server environment supports.
|
||||||
|
|
||||||
Now to create the `setup.sh` file. This script will setup `php+modules`, `mariadb/mysql` and `apache`, the ones I had listed is the minimal required to get SilverStripe started and working out of the box.
|
Now to create the `setup.sh` file. This script will setup `php+modules`, `mariadb/mysql` and `apache`, the ones I had listed is the minimal required to get SilverStripe started and working out of the box.
|
||||||
```bash
|
|
||||||
yum update -y --disableplugin=fastestmirror
|
|
||||||
systemctl restart sshd
|
|
||||||
|
|
||||||
yum install -y httpd httpd-devel mod_ssl
|
|
||||||
yum -y install php php-common php-mysql php-pdo php-mcrypt* php-gd php-xml php-mbstring
|
|
||||||
echo "Include /vagrant/apache/*.conf" >> /etc/httpd/conf/httpd.conf
|
|
||||||
echo "date.timezone = Pacific/Auckland" >> /etc/php.ini
|
|
||||||
systemctl start httpd.service
|
|
||||||
systemctl enable httpd.service
|
|
||||||
|
|
||||||
yum install -y mariadb-server mariadb
|
|
||||||
systemctl start mariadb.service
|
|
||||||
systemctl enable mariadb.service
|
|
||||||
```
|
```
|
||||||
|
|
||||||
*Important*: Again, as noted above, this uses RHEL so `yum install` is used, please remember to change to `apt-get install` or other packaging tool as necessary.
|
*Important*: Again, as noted above, this uses RHEL so `yum install` is used, please remember to change to `apt-get install` or other packaging tool as necessary.
|
||||||
@ -136,56 +92,15 @@ Save `setup.sh` in the same folder as your Vagrantfile.
|
|||||||
|
|
||||||
### Setting up Apache
|
### Setting up Apache
|
||||||
If you inspect the script I’ve included above, you’ll notice this line:
|
If you inspect the script I’ve included above, you’ll notice this line:
|
||||||
```bash
|
|
||||||
echo "Include /vagrant/apache/*.conf" >> /etc/httpd/conf/httpd.conf
|
|
||||||
```
|
```
|
||||||
This will allow us to customise our apache, particularly the VirtualHost part
|
This will allow us to customise our apache, particularly the VirtualHost part
|
||||||
|
|
||||||
Earlier in the post, I had defined a hostname:
|
Earlier in the post, I had defined a hostname:
|
||||||
```ruby
|
|
||||||
config.vm.hostname = "virtuallythere.dev"
|
|
||||||
```
|
```
|
||||||
We’ll need to create a conf file for this hostname in a apache folder, create the folder first:
|
We’ll need to create a conf file for this hostname in a apache folder, create the folder first:
|
||||||
```bash
|
|
||||||
mkdir apache
|
|
||||||
```
|
```
|
||||||
|
|
||||||
We'll save a `vagrant.conf` file in the newly created apache folder, and inside we’ll define the VirtualHost:
|
We'll save a `vagrant.conf` file in the newly created apache folder, and inside we’ll define the VirtualHost:
|
||||||
```apache
|
|
||||||
ServerRoot "/etc/httpd"
|
|
||||||
|
|
||||||
<Directory />
|
|
||||||
AllowOverride none
|
|
||||||
Require all denied
|
|
||||||
</Directory>
|
|
||||||
|
|
||||||
DocumentRoot "/vagrant/public"
|
|
||||||
|
|
||||||
<Directory "/vagrant/public">
|
|
||||||
Options Indexes FollowSymLinks
|
|
||||||
AllowOverride All
|
|
||||||
Require all granted
|
|
||||||
</Directory>
|
|
||||||
|
|
||||||
<VirtualHost *:80>
|
|
||||||
ServerName virtuallythere.dev
|
|
||||||
ServerAlias www.virtuallythere.dev
|
|
||||||
DocumentRoot /vagrant/public
|
|
||||||
LogLevel warn
|
|
||||||
ServerSignature Off
|
|
||||||
|
|
||||||
<Directory /vagrant/public>
|
|
||||||
Options +FollowSymLinks
|
|
||||||
Options -ExecCGI -Includes -Indexes
|
|
||||||
AllowOverride all
|
|
||||||
Require all granted
|
|
||||||
</Directory>
|
|
||||||
|
|
||||||
# SilverStripe specific
|
|
||||||
<LocationMatch assets/>
|
|
||||||
php_flag engine off
|
|
||||||
</LocationMatch>
|
|
||||||
</VirtualHost>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Download SilverStripe
|
### Download SilverStripe
|
||||||
@ -194,8 +109,6 @@ As mentioned above, you could install SilverStripe by [Composer](https://getcomp
|
|||||||
|
|
||||||
### We’re ready for launch
|
### We’re ready for launch
|
||||||
That’s all! When that’s done, run:
|
That’s all! When that’s done, run:
|
||||||
```bash
|
|
||||||
vagrant up
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -126,6 +126,7 @@ This is an op-code cacher which speeds up PHP execution on Windows.
|
|||||||
* Ensure that **wincache** and **sqlsrv** details can be found in the information
|
* Ensure that **wincache** and **sqlsrv** details can be found in the information
|
||||||
* Go to **Enable or disable an extension** and disable everything, except for these:
|
* Go to **Enable or disable an extension** and disable everything, except for these:
|
||||||
|
|
||||||
|
```
|
||||||
php_curl.dll
|
php_curl.dll
|
||||||
php_gd2.dll
|
php_gd2.dll
|
||||||
php_mbstring.dll
|
php_mbstring.dll
|
||||||
@ -133,24 +134,26 @@ This is an op-code cacher which speeds up PHP execution on Windows.
|
|||||||
php_wincache.dll
|
php_wincache.dll
|
||||||
php_tidy.dll
|
php_tidy.dll
|
||||||
|
|
||||||
* Go to **Configure error reporting** and check **Development machine**
|
```
|
||||||
* Go to **Manage all settings** and set the following:
|
* Go to **Manage all settings** and set the following:
|
||||||
|
|
||||||
|
```
|
||||||
date.timezone = "Pacific/Auckland" (or choose from the list here: http://nz.php.net/manual/en/timezones.php)
|
date.timezone = "Pacific/Auckland" (or choose from the list here: http://nz.php.net/manual/en/timezones.php)
|
||||||
post_max_size = 64M
|
post_max_size = 64M
|
||||||
memory_limit = 256M
|
memory_limit = 256M
|
||||||
upload_max_filesize = 64M
|
upload_max_filesize = 64M
|
||||||
|
|
||||||
Tweak the above values as necessary if your requirements differ.
|
```
|
||||||
|
|
||||||
## Folder permissions for PHP
|
## Folder permissions for PHP
|
||||||
|
|
||||||
Now we need to set up folder permissions for PHP. Open the php.ini and find the paths for sessions and file uploads. They will look like this:
|
Now we need to set up folder permissions for PHP. Open the php.ini and find the paths for sessions and file uploads. They will look like this:
|
||||||
|
|
||||||
|
```
|
||||||
upload_tmp_dir="C:\Windows\Temp"
|
upload_tmp_dir="C:\Windows\Temp"
|
||||||
session.save_path="C:\Windows\Temp"
|
session.save_path="C:\Windows\Temp"
|
||||||
|
|
||||||
Two other important folders to set the permissions on are `assets` and `silverstripe-cache` (if used) in your web root.
|
```
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@ -166,6 +169,7 @@ This file tells SilverStripe projects installed on this machine which database s
|
|||||||
|
|
||||||
Inside the newly created _ss_environment.php file, insert the following code:
|
Inside the newly created _ss_environment.php file, insert the following code:
|
||||||
|
|
||||||
|
```
|
||||||
<?php
|
<?php
|
||||||
/* What kind of environment is this: development, test, or live (ie, production)? */
|
/* What kind of environment is this: development, test, or live (ie, production)? */
|
||||||
define('SS_ENVIRONMENT_TYPE', 'dev');
|
define('SS_ENVIRONMENT_TYPE', 'dev');
|
||||||
@ -177,7 +181,7 @@ Inside the newly created _ss_environment.php file, insert the following code:
|
|||||||
define('SS_DEFAULT_ADMIN_USERNAME', 'username');
|
define('SS_DEFAULT_ADMIN_USERNAME', 'username');
|
||||||
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
|
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
|
||||||
|
|
||||||
Insert the password you created for SQL Server earlier into the **SS_DATABASE_PASSWORD** field that is currently empty.
|
```
|
||||||
|
|
||||||
* Grab the latest stable version from here: http://www.silverstripe.org/stable-download
|
* Grab the latest stable version from here: http://www.silverstripe.org/stable-download
|
||||||
* Extract contents to **C:\inetpub\wwwroot\ss**
|
* Extract contents to **C:\inetpub\wwwroot\ss**
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Common problems
|
||||||
|
summary: Some things you can try when it's just not going your way
|
||||||
|
icon: help
|
||||||
|
---
|
||||||
|
|
||||||
# Common Problems
|
# Common Problems
|
||||||
|
|
||||||
From time to time, things will go wrong. Here's a few things to try when you're confused.
|
From time to time, things will go wrong. Here's a few things to try when you're confused.
|
||||||
@ -15,28 +21,20 @@ If you can log-in to the CMS as an administrator, append `?isDev=1` to any URL t
|
|||||||
"dev mode". If you can't log-in in the first place because of the error, add this directive to your `mysite/_config/config.yml`
|
"dev mode". If you can't log-in in the first place because of the error, add this directive to your `mysite/_config/config.yml`
|
||||||
(don't forget to remove it afterwards!):
|
(don't forget to remove it afterwards!):
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Director:
|
Director:
|
||||||
# temporary debugging statement
|
# temporary debugging statement
|
||||||
environment_type: 'dev'
|
environment_type: 'dev'
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
```
|
||||||
On "live" environments, the `?isDev=1` solution is preferred, as it means that your other visitors don't see ugly
|
On "live" environments, the `?isDev=1` solution is preferred, as it means that your other visitors don't see ugly
|
||||||
(and potentially security sensitive) PHP errors as well.
|
(and potentially security sensitive) PHP errors as well.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
## mod_rewrite isn't working but it's installed (prior to SilverStripe 3.1.11)
|
## mod_rewrite isn't working but it's installed (prior to SilverStripe 3.1.11)
|
||||||
|
|
||||||
Due to some changes to `mod_dir` in [Apache 2.4](http://httpd.apache.org/docs/current/mod/mod_dir.html#DirectoryCheckHandler) (precedence of handlers), index.php gets added to the URLs as soon as you navigate to the homepage of your site. Further requests are then handled by index.php rather than `mod_rewrite` (framework/main.php). To fix this place the following within the `mod_rewrite` section of your .htaccess file:
|
Due to some changes to `mod_dir` in [Apache 2.4](http://httpd.apache.org/docs/current/mod/mod_dir.html#DirectoryCheckHandler) (precedence of handlers), index.php gets added to the URLs as soon as you navigate to the homepage of your site. Further requests are then handled by index.php rather than `mod_rewrite` (framework/main.php). To fix this place the following within the `mod_rewrite` section of your .htaccess file:
|
||||||
|
|
||||||
```
|
|
||||||
<IfModule mod_rewrite.c>
|
|
||||||
# Turn off index.php handling requests to the homepage fixes issue in apache >=2.4
|
|
||||||
<IfModule mod_dir.c>
|
|
||||||
DirectoryIndex disabled
|
|
||||||
</IfModule>
|
|
||||||
# ------ #
|
|
||||||
</IfModule>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## My templates don't update on page refresh
|
## My templates don't update on page refresh
|
||||||
@ -102,24 +100,4 @@ If that doesn't work out, here's a little script to run checks in all relevant P
|
|||||||
Save it as `check.php` into your webroot, and run it as `php check.php` (or open it in your browser).
|
Save it as `check.php` into your webroot, and run it as `php check.php` (or open it in your browser).
|
||||||
After using the script (and fixing errors afterwards), please remember to remove it again.
|
After using the script (and fixing errors afterwards), please remember to remove it again.
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
// Check for whitespace around PHP brackets which show in output,
|
|
||||||
// and hence can break HTML rendering and HTTP operations.
|
|
||||||
$path = dirname(__FILE__);
|
|
||||||
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST);
|
|
||||||
$matched = false;
|
|
||||||
foreach($files as $name => $file){
|
|
||||||
if($file->getExtension() != 'php') continue;
|
|
||||||
if(preg_match('/thirdparty|vendor/',$file->getPathname())) continue;
|
|
||||||
$content = file_get_contents($file->getPathname());
|
|
||||||
if(preg_match('/^[[:blank:]]+<\?' . 'php/', $content)) {
|
|
||||||
echo sprintf("%s: Space before opening bracket\n", $file->getPathname());
|
|
||||||
$matched = true;
|
|
||||||
}
|
|
||||||
if(preg_match('/^\?' . '>\n?[[:blank:]]+/m', $content)) {
|
|
||||||
echo sprintf("%s: Space after closing bracket\n", $file->getPathname());
|
|
||||||
$matched = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
|
---
|
||||||
|
title: Configure Lighttpd
|
||||||
|
summary: Write a custom config for Lighttpd
|
||||||
|
---
|
||||||
|
|
||||||
# Lightttpd
|
# Lightttpd
|
||||||
|
|
||||||
1. Lighttpd works fine so long as you provide a custom config. Add the following to lighttpd.conf **BEFORE** installing
|
1. Lighttpd works fine so long as you provide a custom config. Add the following to lighttpd.conf **BEFORE** installing
|
||||||
Silverstripe. Replace "yoursite.com" and "/home/yoursite/public_html/" below.
|
Silverstripe. Replace "yoursite.com" and "/home/yoursite/public_html/" below.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
$HTTP["host"] == "yoursite.com" {
|
$HTTP["host"] == "yoursite.com" {
|
||||||
server.document-root = "/home/yoursite/public_html/"
|
server.document-root = "/home/yoursite/public_html/"
|
||||||
|
|
||||||
@ -34,7 +40,7 @@ Silverstripe. Replace "yoursite.com" and "/home/yoursite/public_html/" below.
|
|||||||
server.error-handler-404 = "/framework/main.php"
|
server.error-handler-404 = "/framework/main.php"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Rewrite rules do not check for file existence as they do on Apache. There is a ticket about it for Lighttpd:
|
Rewrite rules do not check for file existence as they do on Apache. There is a ticket about it for Lighttpd:
|
||||||
[http://redmine.lighttpd.net/issues/985](http://redmine.lighttpd.net/issues/985).
|
[http://redmine.lighttpd.net/issues/985](http://redmine.lighttpd.net/issues/985).
|
||||||
|
|
||||||
@ -49,6 +55,7 @@ things a lot simpler, as you just use two of the above host example blocks. But
|
|||||||
of Silverstripe on the same host, you can use something like this (be warned, it's quite nasty):
|
of Silverstripe on the same host, you can use something like this (be warned, it's quite nasty):
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
$HTTP["host"] == "yoursite.com" {
|
$HTTP["host"] == "yoursite.com" {
|
||||||
url.rewrite-once = (
|
url.rewrite-once = (
|
||||||
"(?i)(/copy1/.*\.([A-Za-z0-9]+))(.*?)$" => "$0",
|
"(?i)(/copy1/.*\.([A-Za-z0-9]+))(.*?)$" => "$0",
|
||||||
@ -64,7 +71,7 @@ of Silverstripe on the same host, you can use something like this (be warned, it
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Note: It doesn't work properly if the directory name copy1 or copy2 on your server has a dot in it, and you then open
|
Note: It doesn't work properly if the directory name copy1 or copy2 on your server has a dot in it, and you then open
|
||||||
the image editor inside admin, I found that out the hard way when using a directory name of silverstripe-v2.2.2 after
|
the image editor inside admin, I found that out the hard way when using a directory name of silverstripe-v2.2.2 after
|
||||||
directly unzipping the Silverstripe tarball leaving the name as is. I haven't found a solution for that yet, but for now
|
directly unzipping the Silverstripe tarball leaving the name as is. I haven't found a solution for that yet, but for now
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Configure Lighttpd
|
||||||
|
summary: Write a custom config for nginx
|
||||||
|
---
|
||||||
|
|
||||||
# Nginx
|
# Nginx
|
||||||
|
|
||||||
These instructions are also covered on the
|
These instructions are also covered on the
|
||||||
@ -9,83 +14,15 @@ able to run PHP files via the FastCGI-wrapper from Nginx.
|
|||||||
Now you need to set up a virtual host in Nginx with configuration settings
|
Now you need to set up a virtual host in Nginx with configuration settings
|
||||||
that are similar to those shown below.
|
that are similar to those shown below.
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
If you don't fully understand the configuration presented here, consult the
|
If you don't fully understand the configuration presented here, consult the
|
||||||
[nginx documentation](http://nginx.org/en/docs/).
|
[nginx documentation](http://nginx.org/en/docs/).
|
||||||
|
|
||||||
Especially be aware of [accidental php-execution](https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/ "Don't trust the tutorials") when extending the configuration.
|
Especially be aware of [accidental php-execution](https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/ "Don't trust the tutorials") when extending the configuration.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
But enough of the disclaimer, on to the actual configuration — typically in `nginx.conf`:
|
But enough of the disclaimer, on to the actual configuration — typically in `nginx.conf`:
|
||||||
|
|
||||||
```nginx
|
|
||||||
server {
|
|
||||||
include mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
client_max_body_size 0; # Manage this in php.ini
|
|
||||||
listen 80;
|
|
||||||
root /path/to/ss/folder;
|
|
||||||
server_name example.com www.example.com;
|
|
||||||
|
|
||||||
# Defend against SS-2015-013 -- http://www.silverstripe.org/software/download/security-releases/ss-2015-013
|
|
||||||
if ($http_x_forwarded_host) {
|
|
||||||
return 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files $uri /framework/main.php?url=$uri&$query_string;
|
|
||||||
}
|
|
||||||
|
|
||||||
error_page 404 /assets/error-404.html;
|
|
||||||
error_page 500 /assets/error-500.html;
|
|
||||||
|
|
||||||
location ^~ /assets/ {
|
|
||||||
sendfile on;
|
|
||||||
try_files $uri =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ /framework/.*(main|rpc|tiny_mce_gzip)\.php$ {
|
|
||||||
fastcgi_buffer_size 32k;
|
|
||||||
fastcgi_busy_buffers_size 64k;
|
|
||||||
fastcgi_buffers 4 32k;
|
|
||||||
fastcgi_keep_conn on;
|
|
||||||
fastcgi_pass 127.0.0.1:9000;
|
|
||||||
fastcgi_index index.php;
|
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
|
||||||
include fastcgi_params;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Denials
|
|
||||||
location ~ /\.. {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
location ~ \.ss$ {
|
|
||||||
satisfy any;
|
|
||||||
allow 127.0.0.1;
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
location ~ web\.config$ {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
location ~ \.ya?ml$ {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
location ~* README.*$ {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
location ^~ /vendor/ {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
location ~* /silverstripe-cache/ {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
location ~* composer\.(json|lock)$ {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
location ~* /(cms|framework)/silverstripe_version$ {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The above configuration sets up a virtual host `example.com` with
|
The above configuration sets up a virtual host `example.com` with
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: MySQL SSL Support
|
title: MySQL SSL Support
|
||||||
summary: Setting up MySQL SSL certificates to work with Silverstripe
|
summary: Setting up MySQL SSL certificates to work with Silverstripe
|
||||||
|
---
|
||||||
# MySQL SSL Support: Why do I need it?
|
# MySQL SSL Support: Why do I need it?
|
||||||
|
|
||||||
In a typical Silverstripe set up, you will only need to use a single host to function as the web server, email server, database server, among others.
|
In a typical Silverstripe set up, you will only need to use a single host to function as the web server, email server, database server, among others.
|
||||||
@ -9,9 +10,9 @@ In some cases, however, you may be required to connect to a database on a remote
|
|||||||
|
|
||||||
This article demonstrates how to generate SSL certificates using MySQL and implementing them in Silverstripe.
|
This article demonstrates how to generate SSL certificates using MySQL and implementing them in Silverstripe.
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
This article assumes that you have `MySQL` and `OpenSSL` installed.
|
This article assumes that you have `MySQL` and `OpenSSL` installed.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
|
|
||||||
## Generating Certificates
|
## Generating Certificates
|
||||||
@ -28,12 +29,12 @@ We also need to sign the certificates with our generated CA.
|
|||||||
|
|
||||||
The commands below illustrate how to do so on your MySQL host.
|
The commands below illustrate how to do so on your MySQL host.
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
The following commands will work on Linux/Unix based servers. For other servers such as windows, refer to the [MySQL documentation](https://dev.mysql.com/doc/refman/5.7/en/creating-ssl-files-using-openssl.html)
|
The following commands will work on Linux/Unix based servers. For other servers such as windows, refer to the [MySQL documentation](https://dev.mysql.com/doc/refman/5.7/en/creating-ssl-files-using-openssl.html)
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
|
|
||||||
# Create directory
|
# Create directory
|
||||||
sudo mkdir ssl
|
sudo mkdir ssl
|
||||||
@ -60,14 +61,16 @@ The following commands will work on Linux/Unix based servers. For other servers
|
|||||||
|
|
||||||
# Verify validity of generated certificates
|
# Verify validity of generated certificates
|
||||||
sudo openssl verify -CAfile ca-cert.pem server-cert.pem client-cert.pem
|
sudo openssl verify -CAfile ca-cert.pem server-cert.pem client-cert.pem
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
```
|
||||||
|
[warning]
|
||||||
After generating the certificates, make sure to set the correct permissions to prevent unauthorized access to your keys!
|
After generating the certificates, make sure to set the correct permissions to prevent unauthorized access to your keys!
|
||||||
|
|
||||||
It is critical that the key files (files ending in *key.pem) are kept secret. Once these files are exposed, you will need to regenerate the certificates to prevent exposing your data traffic.
|
It is critical that the key files (files ending in *key.pem) are kept secret. Once these files are exposed, you will need to regenerate the certificates to prevent exposing your data traffic.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
:::bash
|
|
||||||
|
```bash
|
||||||
# Set permissions readonly permissions and change owner to root
|
# Set permissions readonly permissions and change owner to root
|
||||||
sudo chown root:root *.pem
|
sudo chown root:root *.pem
|
||||||
sudo chmod 440 *.pem
|
sudo chmod 440 *.pem
|
||||||
@ -76,18 +79,19 @@ It is critical that the key files (files ending in *key.pem) are kept secret. On
|
|||||||
sudo chgrp mysql server*.pem
|
sudo chgrp mysql server*.pem
|
||||||
sudo mv *.pem /etc/mysql/ssl
|
sudo mv *.pem /etc/mysql/ssl
|
||||||
|
|
||||||
|
```
|
||||||
## Setting up MySQL to use SSL certificates
|
## Setting up MySQL to use SSL certificates
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
For Debian/Ubuntu instances, the configuration file is usually in `/etc/mysql/my.cnf`. Refer to your MySQL manual for more information
|
For Debian/Ubuntu instances, the configuration file is usually in `/etc/mysql/my.cnf`. Refer to your MySQL manual for more information
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
We must edit the MySQL configuration to use the newly generated certificates.
|
We must edit the MySQL configuration to use the newly generated certificates.
|
||||||
|
|
||||||
Edit your MySQL configuration file as follows.
|
Edit your MySQL configuration file as follows.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
[mysqld]
|
[mysqld]
|
||||||
...
|
...
|
||||||
ssl-ca=/etc/mysql/ca-cert.pem
|
ssl-ca=/etc/mysql/ca-cert.pem
|
||||||
@ -97,16 +101,16 @@ Edit your MySQL configuration file as follows.
|
|||||||
# IMPORTANT! When enabling MySQL remote connections, make sure to take adequate steps to secure your machine from unathorized access!
|
# IMPORTANT! When enabling MySQL remote connections, make sure to take adequate steps to secure your machine from unathorized access!
|
||||||
bind-address=0.0.0.0
|
bind-address=0.0.0.0
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
```
|
||||||
Enabling remote connections to your MySQL instance introduces various security risks. Make sure to take appropriate steps to secure your instance by using a strong password, disabling MySQL root access, and using a firewall to only accept qualified hosts, for example.
|
Enabling remote connections to your MySQL instance introduces various security risks. Make sure to take appropriate steps to secure your instance by using a strong password, disabling MySQL root access, and using a firewall to only accept qualified hosts, for example.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
Make sure to restart your MySQL instance to reflect the changes.
|
Make sure to restart your MySQL instance to reflect the changes.
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
sudo service mysql restart
|
sudo service mysql restart
|
||||||
|
|
||||||
|
```
|
||||||
## Setting up Silverstripe to connect to MySQL
|
## Setting up Silverstripe to connect to MySQL
|
||||||
|
|
||||||
Now that we have successfully setup the SSL your MySQL host, we now need to configure Silverstripe to use the certificates.
|
Now that we have successfully setup the SSL your MySQL host, we now need to configure Silverstripe to use the certificates.
|
||||||
@ -119,13 +123,13 @@ First we need to copy the client certificate files to the Silverstripe instance.
|
|||||||
- `client-cert.pem`
|
- `client-cert.pem`
|
||||||
- `ca-cert.pem`
|
- `ca-cert.pem`
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
[warning]
|
||||||
Make sure to only copy `client-key.pem`, `client-cert.pem`, and `ca-cert.pem` to avoid leaking your credentials!
|
Make sure to only copy `client-key.pem`, `client-cert.pem`, and `ca-cert.pem` to avoid leaking your credentials!
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
On your Silverstripe instance:
|
On your Silverstripe instance:
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
# Secure copy over SSH via rsync command. You may use an alternative method if desired.
|
# Secure copy over SSH via rsync command. You may use an alternative method if desired.
|
||||||
rsync -avP user@db1.example.com:/path/to/client/certs /path/to/secure/folder
|
rsync -avP user@db1.example.com:/path/to/client/certs /path/to/secure/folder
|
||||||
|
|
||||||
@ -134,15 +138,15 @@ On your Silverstripe instance:
|
|||||||
sudo chmod 750 /path/to/secure/folder
|
sudo chmod 750 /path/to/secure/folder
|
||||||
sudo chmod 400 /path/to/secure/folder/*
|
sudo chmod 400 /path/to/secure/folder/*
|
||||||
|
|
||||||
### Setting up _ss_environment.php to use SSL certificates
|
```
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
`SS_DATABASE_SERVER does not accept IP-based hostnames. Also, if the domain name of the host does not match the common name you used to generate the server certificate, you will get an `SSL certificate mismatch error`.
|
`SS_DATABASE_SERVER does not accept IP-based hostnames. Also, if the domain name of the host does not match the common name you used to generate the server certificate, you will get an `SSL certificate mismatch error`.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
Add or edit your `_ss_environment.php` configuration file. (See [Environment Management](/getting_started/environment_management) for more information.)
|
Add or edit your `_ss_environment.php` configuration file. (See [Environment Management](/getting_started/environment_management) for more information.)
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
// These four define set the database connection details.
|
// These four define set the database connection details.
|
||||||
@ -161,7 +165,7 @@ Add or edit your `_ss_environment.php` configuration file. (See [Environment Man
|
|||||||
define('SS_DEFAULT_ADMIN_USERNAME', 'username');
|
define('SS_DEFAULT_ADMIN_USERNAME', 'username');
|
||||||
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
|
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
|
||||||
|
|
||||||
|
```
|
||||||
When running the installer, make sure to check on the `Use _ss_environment file for configuration` option under the `Database Configuration` section to use the environment file.
|
When running the installer, make sure to check on the `Use _ss_environment file for configuration` option under the `Database Configuration` section to use the environment file.
|
||||||
|
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: Nginx and HHVM
|
title: Nginx and HHVM
|
||||||
summary: Setting up Nginx and HHVM on Debian/Ubuntu using packages.
|
summary: Setting up Nginx and HHVM on Debian/Ubuntu using packages.
|
||||||
|
---
|
||||||
# Nginx and HHVM
|
# Nginx and HHVM
|
||||||
|
|
||||||
[HHVM](http://hhvm.com/) is a faster alternative to PHP, in that it runs in a virtual machine
|
[HHVM](http://hhvm.com/) is a faster alternative to PHP, in that it runs in a virtual machine
|
||||||
@ -11,38 +12,44 @@ packages available to use.
|
|||||||
|
|
||||||
Install apt sources on Debian 7 (wheezy):
|
Install apt sources on Debian 7 (wheezy):
|
||||||
|
|
||||||
|
```
|
||||||
wget -O - http://dl.hhvm.com/conf/hhvm.gpg.key | sudo apt-key add -
|
wget -O - http://dl.hhvm.com/conf/hhvm.gpg.key | sudo apt-key add -
|
||||||
echo deb http://dl.hhvm.com/debian wheezy main | sudo tee /etc/apt/sources.list.d/hhvm.list
|
echo deb http://dl.hhvm.com/debian wheezy main | sudo tee /etc/apt/sources.list.d/hhvm.list
|
||||||
|
|
||||||
Install apt sources on Ubuntu 14.04 (trusty):
|
```
|
||||||
|
|
||||||
|
```
|
||||||
wget -O - http://dl.hhvm.com/conf/hhvm.gpg.key | sudo apt-key add -
|
wget -O - http://dl.hhvm.com/conf/hhvm.gpg.key | sudo apt-key add -
|
||||||
echo deb http://dl.hhvm.com/ubuntu trusty main | sudo tee /etc/apt/sources.list.d/hhvm.list
|
echo deb http://dl.hhvm.com/ubuntu trusty main | sudo tee /etc/apt/sources.list.d/hhvm.list
|
||||||
|
|
||||||
Now install the required packages:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install hhvm libgmp-dev libmemcached-dev
|
sudo apt-get install hhvm libgmp-dev libmemcached-dev
|
||||||
|
|
||||||
Start HHVM automatically on boot:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
sudo update-rc.d hhvm defaults
|
sudo update-rc.d hhvm defaults
|
||||||
|
|
||||||
Please see [Prebuilt Packages for HHVM on HHVM wiki](https://github.com/facebook/hhvm/wiki/Prebuilt%20Packages%20for%20HHVM) for more
|
```
|
||||||
installation options.
|
installation options.
|
||||||
|
|
||||||
Assuming you already have nginx installed, you can then run a script to enable support for
|
Assuming you already have nginx installed, you can then run a script to enable support for
|
||||||
nginx and/or apache2 depending on whether they are installed or not:
|
nginx and/or apache2 depending on whether they are installed or not:
|
||||||
|
|
||||||
|
```
|
||||||
sudo /usr/share/hhvm/install_fastcgi.sh
|
sudo /usr/share/hhvm/install_fastcgi.sh
|
||||||
|
|
||||||
For nginx, this will place a file at `/etc/nginx/hhvm.conf` which you can use to include in
|
```
|
||||||
your nginx server definitions to provide support for PHP requests.
|
your nginx server definitions to provide support for PHP requests.
|
||||||
|
|
||||||
In order to get SilverStripe working, you need to add some custom nginx configuration.
|
In order to get SilverStripe working, you need to add some custom nginx configuration.
|
||||||
|
|
||||||
Create `/etc/nginx/silverstripe.conf` and add this configuration:
|
Create `/etc/nginx/silverstripe.conf` and add this configuration:
|
||||||
|
|
||||||
|
```
|
||||||
fastcgi_buffer_size 32k;
|
fastcgi_buffer_size 32k;
|
||||||
fastcgi_busy_buffers_size 64k;
|
fastcgi_busy_buffers_size 64k;
|
||||||
fastcgi_buffers 4 32k;
|
fastcgi_buffers 4 32k;
|
||||||
@ -87,7 +94,7 @@ Create `/etc/nginx/silverstripe.conf` and add this configuration:
|
|||||||
deny all;
|
deny all;
|
||||||
}
|
}
|
||||||
|
|
||||||
The above script passes all non-static file requests to `/framework/main.php` in the webroot which relies on
|
```
|
||||||
`hhvm.conf` being included prior so that php requests are handled.
|
`hhvm.conf` being included prior so that php requests are handled.
|
||||||
|
|
||||||
Now in your nginx `server` configuration you can then include the `hhvm.conf` and `silverstripe.conf` files
|
Now in your nginx `server` configuration you can then include the `hhvm.conf` and `silverstripe.conf` files
|
||||||
@ -95,6 +102,7 @@ to complete the configuration required for PHP/HHVM and SilverStripe.
|
|||||||
|
|
||||||
e.g. `/etc/nginx/sites-enabled/mysite`:
|
e.g. `/etc/nginx/sites-enabled/mysite`:
|
||||||
|
|
||||||
|
```
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
root /var/www/mysite;
|
root /var/www/mysite;
|
||||||
@ -107,4 +115,4 @@ e.g. `/etc/nginx/sites-enabled/mysite`:
|
|||||||
include /etc/nginx/silverstripe.conf;
|
include /etc/nginx/silverstripe.conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
For more information on nginx configuration, please see the [nginx installation](configure_nginx) page.
|
```
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: How To's
|
||||||
|
---
|
||||||
|
|
||||||
|
# Installation: How To's
|
||||||
|
|
||||||
|
[CHILDREN]
|
@ -17,8 +17,8 @@ Check out our operating system specific guides for [Linux](linux_unix),
|
|||||||
|
|
||||||
If the above steps don't work for any reason have a read of the [Common Problems](common_problems) section.
|
If the above steps don't work for any reason have a read of the [Common Problems](common_problems) section.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
SilverStripe ships with default rewriting rules specific to your web server. Apart from
|
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,
|
routing requests to the framework, they also prevent access to sensitive files in the webroot,
|
||||||
for example YAML configuration files. Please refer to the [secure coding](/developer_guides/security/secure_coding/#filesystem) documentation for details.
|
for example YAML configuration files. Please refer to the [secure coding](/developer_guides/security/secure_coding/#filesystem) documentation for details.
|
||||||
</div>
|
[/notice]
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Composer
|
||||||
|
summary: What is composer and how to use it with Silverstripe CMS
|
||||||
|
---
|
||||||
|
|
||||||
# Installing and Upgrading with Composer
|
# Installing and Upgrading with 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. This is our recommended way of downloading SilverStripe and managing your code.
|
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.
|
||||||
@ -15,19 +20,21 @@ Next, [install composer](https://getcomposer.org/download/). For our documentati
|
|||||||
|
|
||||||
You can then run Composer commands by calling `composer`. For example:
|
You can then run Composer commands by calling `composer`. For example:
|
||||||
|
|
||||||
|
```
|
||||||
composer help
|
composer help
|
||||||
|
|
||||||
<div class="hint" markdown="1">
|
```
|
||||||
It is also possible to keep `composer.phar` out of your path, for example, to put it in your project root. Every command would then start with `php composer.phar` instead of `composer`. This is handy if need to keep your installation isolated from the rest of your computer's set-up, but we recommend putting composer into the path for most people.
|
It is also possible to keep `composer.phar` out of your path, for example, to put it in your project root. Every command would then start with `php composer.phar` instead of `composer`. This is handy if need to keep your installation isolated from the rest of your computer's set-up, but we recommend putting composer into the path for most people.
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
#### Updating composer
|
#### Updating composer
|
||||||
|
|
||||||
If you already have composer installed you can update it by running:
|
If you already have composer installed you can update it by running:
|
||||||
|
|
||||||
|
```
|
||||||
sudo composer self-update
|
sudo composer self-update
|
||||||
|
|
||||||
Composer updates regularly, so you should run this command fairly often. These instructions assume you are running the latest version.
|
```
|
||||||
|
|
||||||
## Installing Composer on Windows WAMP
|
## Installing Composer on Windows WAMP
|
||||||
For those that use WAMP as a development environment, [detailed information is available on installing using Composer.](/getting_started/installation/windows)
|
For those that use WAMP as a development environment, [detailed information is available on installing using Composer.](/getting_started/installation/windows)
|
||||||
@ -36,18 +43,20 @@ For those that use WAMP as a development environment, [detailed information is a
|
|||||||
|
|
||||||
Composer can create a new site for you, using the installer as a template (by default composer will download the latest stable version):
|
Composer can create a new site for you, using the installer as a template (by default composer will download the latest stable version):
|
||||||
|
|
||||||
|
```
|
||||||
composer create-project silverstripe/installer ./my/website/folder
|
composer create-project silverstripe/installer ./my/website/folder
|
||||||
|
|
||||||
`./my/website/folder` should be the root directory where your site will live.
|
```
|
||||||
For example, on OS X, you might use a subdirectory of `~/Sites`.
|
For example, on OS X, you might use a subdirectory of `~/Sites`.
|
||||||
As long as your web server is up and running, this will get all the code that you need.
|
As long as your web server is up and running, this will get all the code that you need.
|
||||||
Now visit the site in your web browser, and the installation process will be completed.
|
Now visit the site in your web browser, and the installation process will be completed.
|
||||||
|
|
||||||
You can also specify a version to download that version explicitly, i.e. this will download the older `3.0.3` release:
|
You can also specify a version to download that version explicitly, i.e. this will download the older `3.0.3` release:
|
||||||
|
|
||||||
|
```
|
||||||
composer create-project silverstripe/installer ./my/website/folder 3.0.3
|
composer create-project silverstripe/installer ./my/website/folder 3.0.3
|
||||||
|
|
||||||
When `create-project` is used with a release version like above,
|
```
|
||||||
it will try to get the code from archives instead of creating
|
it will try to get the code from archives instead of creating
|
||||||
git repositories. If you're planning to contribute to SilverStripe,
|
git repositories. If you're planning to contribute to SilverStripe,
|
||||||
see [Using development versions](#using-development-versions).
|
see [Using development versions](#using-development-versions).
|
||||||
@ -56,28 +65,31 @@ see [Using development versions](#using-development-versions).
|
|||||||
|
|
||||||
Composer isn't only used to download SilverStripe CMS, it can also be used to manage all SilverStripe modules. Installing a module can be done with the following command:
|
Composer isn't only used to download SilverStripe CMS, it can also be used to manage all SilverStripe modules. Installing a module can be done with the following command:
|
||||||
|
|
||||||
|
```
|
||||||
composer require "silverstripe/forum:*"
|
composer require "silverstripe/forum:*"
|
||||||
|
|
||||||
This will install the forum module in the latest compatible version.
|
```
|
||||||
By default, Composer updates other existing modules (like `framework` and `cms`),
|
By default, Composer updates other existing modules (like `framework` and `cms`),
|
||||||
and installs "dev" dependencies like PHPUnit. In case you don't need those dependencies,
|
and installs "dev" dependencies like PHPUnit. In case you don't need those dependencies,
|
||||||
use the following command instead:
|
use the following command instead:
|
||||||
|
|
||||||
|
```
|
||||||
composer require --no-update "silverstripe/forum:*"
|
composer require --no-update "silverstripe/forum:*"
|
||||||
composer update --no-dev
|
composer update --no-dev
|
||||||
|
|
||||||
The `require` command has two parts. First is `silverstripe/forum`. This is the name of the package.
|
```
|
||||||
You can find other packages with the following command:
|
You can find other packages with the following command:
|
||||||
|
|
||||||
|
```
|
||||||
composer search silverstripe
|
composer search silverstripe
|
||||||
|
|
||||||
This will return a list of package names of the forum `vendor/package`. If you prefer, you can search for packages on [packagist.org](https://packagist.org/search/?q=silverstripe).
|
```
|
||||||
|
|
||||||
The second part after the colon, `*`, is a version string. `*` is a good default: it will give you the latest version that works with the other modules you have installed. Alternatively, you can specificy a specific version, or a constraint such as `>=3.0`. For more information, read the [Composer documentation](http://getcomposer.org/doc/01-basic-usage.md#the-require-key).
|
The second part after the colon, `*`, is a version string. `*` is a good default: it will give you the latest version that works with the other modules you have installed. Alternatively, you can specificy a specific version, or a constraint such as `>=3.0`. For more information, read the [Composer documentation](http://getcomposer.org/doc/01-basic-usage.md#the-require-key).
|
||||||
|
|
||||||
<div class="warning" markdown="1">
|
[warning]
|
||||||
`master` is not a legal version string - it's a branch name. These are different things. The version string that would get you the branch is `dev-master`. The version string that would get you a numeric branch is a little different. The version string for the `3.0` branch is `3.0.x-dev`.
|
`master` is not a legal version string - it's a branch name. These are different things. The version string that would get you the branch is `dev-master`. The version string that would get you a numeric branch is a little different. The version string for the `3.0` branch is `3.0.x-dev`.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
## Updating dependencies
|
## Updating dependencies
|
||||||
|
|
||||||
@ -85,9 +97,10 @@ Except for the control code of the Voyager space probe, every piece of code in t
|
|||||||
|
|
||||||
To get the latest updates of the modules in your project, run this command:
|
To get the latest updates of the modules in your project, run this command:
|
||||||
|
|
||||||
|
```
|
||||||
composer update --no-dev
|
composer update --no-dev
|
||||||
|
|
||||||
Updates to the required modules will be installed, and the `composer.lock` file will get updated with the specific commits of each of those.
|
```
|
||||||
|
|
||||||
## Deploying projects with Composer
|
## Deploying projects with Composer
|
||||||
|
|
||||||
@ -110,6 +123,7 @@ Since SilverStripe modules are installed into their own folder, you have to mana
|
|||||||
|
|
||||||
Here is the default SilverStripe [.gitignore](http://git-scm.com/docs/gitignore) with the forum module ignored
|
Here is the default SilverStripe [.gitignore](http://git-scm.com/docs/gitignore) with the forum module ignored
|
||||||
|
|
||||||
|
```
|
||||||
assets/*
|
assets/*
|
||||||
_ss_environment.php
|
_ss_environment.php
|
||||||
tools/phing-metadata
|
tools/phing-metadata
|
||||||
@ -123,7 +137,7 @@ Here is the default SilverStripe [.gitignore](http://git-scm.com/docs/gitignore)
|
|||||||
# Don't include the forum module, as this will be installed with composer
|
# Don't include the forum module, as this will be installed with composer
|
||||||
forum
|
forum
|
||||||
|
|
||||||
In large projects it can get difficult to manage your [.gitignore](http://git-scm.com/docs/gitignore) and ensure it contains all composer managed modules and themes.
|
```
|
||||||
|
|
||||||
You can automate this with the [SSAutoGitIgnore](https://github.com/guru-digital/SSAutoGitIgnore/) package.
|
You can automate this with the [SSAutoGitIgnore](https://github.com/guru-digital/SSAutoGitIgnore/) package.
|
||||||
This package will maintain your [.gitignore](http://git-scm.com/docs/gitignore) and ensure it is kept up to date with your composer managed modules without affecting custom ignores. Once installed and setup, it will automatically run every time you install, remove or update modules using composer.
|
This package will maintain your [.gitignore](http://git-scm.com/docs/gitignore) and ensure it is kept up to date with your composer managed modules without affecting custom ignores. Once installed and setup, it will automatically run every time you install, remove or update modules using composer.
|
||||||
@ -132,20 +146,23 @@ This package will maintain your [.gitignore](http://git-scm.com/docs/gitignore)
|
|||||||
|
|
||||||
Include the package in your project by running this command
|
Include the package in your project by running this command
|
||||||
|
|
||||||
|
```
|
||||||
composer require gdmedia/ss-auto-git-ignore --dev
|
composer require gdmedia/ss-auto-git-ignore --dev
|
||||||
|
|
||||||
Edit your composer.json and insert
|
```
|
||||||
|
|
||||||
|
```
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"post-update-cmd": "GDM\\SSAutoGitIgnore\\UpdateScript::Go"
|
"post-update-cmd": "GDM\\SSAutoGitIgnore\\UpdateScript::Go"
|
||||||
}
|
}
|
||||||
|
|
||||||
This will instruct composer to run SSAutoGitIgnore after every update. SSAutoGitIgnore will then ensure composer managed models and themes are correctly added to your [.gitignore](http://git-scm.com/docs/gitignore).
|
```
|
||||||
For more information about SSAutoGitIgnore, see the [SSAutoGitIgnore home page](https://github.com/guru-digital/SSAutoGitIgnore/).
|
For more information about SSAutoGitIgnore, see the [SSAutoGitIgnore home page](https://github.com/guru-digital/SSAutoGitIgnore/).
|
||||||
For more information about post-updated-cmd and scripts, read the ["Scripts" chapter of the Composer documentation](https://getcomposer.org/doc/articles/scripts.md).
|
For more information about post-updated-cmd and scripts, read the ["Scripts" chapter of the Composer documentation](https://getcomposer.org/doc/articles/scripts.md).
|
||||||
|
|
||||||
Full example of composer.json with the SSAutoGitIgnore installed and enabled
|
Full example of composer.json with the SSAutoGitIgnore installed and enabled
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"name": "silverstripe/installer",
|
"name": "silverstripe/installer",
|
||||||
"description": "The SilverStripe Framework Installer",
|
"description": "The SilverStripe Framework Installer",
|
||||||
@ -166,7 +183,7 @@ Full example of composer.json with the SSAutoGitIgnore installed and enabled
|
|||||||
"minimum-stability": "dev"
|
"minimum-stability": "dev"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Dev Environments for Contributing Code {#contributing}
|
```
|
||||||
|
|
||||||
So you want to contribute to SilverStripe? Fantastic! You can do this with composer too.
|
So you want to contribute to SilverStripe? Fantastic! You can do this with composer too.
|
||||||
You have to tell composer three things in order to be able to do this:
|
You have to tell composer three things in order to be able to do this:
|
||||||
@ -178,9 +195,10 @@ You have to tell composer three things in order to be able to do this:
|
|||||||
The first two steps are done as part of the initial create project using additional arguments.
|
The first two steps are done as part of the initial create project using additional arguments.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
composer create-project --keep-vcs --dev silverstripe/installer ./my/website/folder 3.0.x-dev
|
composer create-project --keep-vcs --dev silverstripe/installer ./my/website/folder 3.0.x-dev
|
||||||
|
|
||||||
The process will take a bit longer, since all modules are checked out as full git repositories which you can work on. The command checks out from the 3.0 release line. To check out from master instead,
|
```
|
||||||
replace `3.0.x-dev` with `dev-master` (more info on [composer version naming](http://getcomposer.org/doc/02-libraries.md#specifying-the-version)).
|
replace `3.0.x-dev` with `dev-master` (more info on [composer version naming](http://getcomposer.org/doc/02-libraries.md#specifying-the-version)).
|
||||||
|
|
||||||
The `--keep-vcs` flag will make sure you have access to the git history of the installer and the requirements
|
The `--keep-vcs` flag will make sure you have access to the git history of the installer and the requirements
|
||||||
@ -206,6 +224,7 @@ create forks and send pull requests.
|
|||||||
|
|
||||||
To remove dependencies, or if you prefer seeing all your dependencies in a text file, you can edit the `composer.json` file. It will appear in your project root, and by default, it will look something like this:
|
To remove dependencies, or if you prefer seeing all your dependencies in a text file, you can edit the `composer.json` file. It will appear in your project root, and by default, it will look something like this:
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
"name": "silverstripe/installer",
|
"name": "silverstripe/installer",
|
||||||
"description": "The SilverStripe Framework Installer",
|
"description": "The SilverStripe Framework Installer",
|
||||||
@ -222,13 +241,15 @@ To remove dependencies, or if you prefer seeing all your dependencies in a text
|
|||||||
"minimum-stability": "dev"
|
"minimum-stability": "dev"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
To add modules, you should add more entries into the `"require"` section. For example, we might add the blog and forum modules. Be careful with the commas at the end of the lines!
|
To add modules, you should add more entries into the `"require"` section. For example, we might add the blog and forum modules. Be careful with the commas at the end of the lines!
|
||||||
|
|
||||||
Save your file, and then run the following command to refresh the installed packages:
|
Save your file, and then run the following command to refresh the installed packages:
|
||||||
|
|
||||||
|
```
|
||||||
composer update
|
composer update
|
||||||
|
|
||||||
## Using development versions
|
```
|
||||||
|
|
||||||
Composer will by default download the latest stable version of silverstripe/installer.
|
Composer will by default download the latest stable version of silverstripe/installer.
|
||||||
The `composer.json` file that comes with silverstripe/installer may also explicitly state it requires the stable version of cms and framework - this is to ensure that when developers are getting started, running `composer update` won't upgrade their project to an unstable version
|
The `composer.json` file that comes with silverstripe/installer may also explicitly state it requires the stable version of cms and framework - this is to ensure that when developers are getting started, running `composer update` won't upgrade their project to an unstable version
|
||||||
@ -239,13 +260,15 @@ is this required if you want to contribute back to the SilverStripe project, it
|
|||||||
This is a two step process. First you get composer to start a project based on
|
This is a two step process. First you get composer to start a project based on
|
||||||
the latest unstable silverstripe/installer
|
the latest unstable silverstripe/installer
|
||||||
|
|
||||||
|
```
|
||||||
composer create-project silverstripe/installer ./my/website/folder master-dev
|
composer create-project silverstripe/installer ./my/website/folder master-dev
|
||||||
|
|
||||||
Or for the latest development version in the 3.0.x series
|
```
|
||||||
|
|
||||||
|
```
|
||||||
composer create-project silverstripe/installer ./my/website/folder 3.0.x-dev
|
composer create-project silverstripe/installer ./my/website/folder 3.0.x-dev
|
||||||
|
|
||||||
## Working with project forks and unreleased modules
|
```
|
||||||
|
|
||||||
By default, Composer will install modules listed on the packagist site. There a few reasons that you might not
|
By default, Composer will install modules listed on the packagist site. There a few reasons that you might not
|
||||||
want to do this. For example:
|
want to do this. For example:
|
||||||
@ -282,10 +305,11 @@ Composer will scan all of the repositories you list, collect meta-data about the
|
|||||||
|
|
||||||
Now add an "upstream" remote to the original repository location so you can rebase or merge your fork as required.
|
Now add an "upstream" remote to the original repository location so you can rebase or merge your fork as required.
|
||||||
|
|
||||||
|
```
|
||||||
cd cms
|
cd cms
|
||||||
git remote add -f upstream git://github.com/silverstripe/silverstripe-cms.git
|
git remote add -f upstream git://github.com/silverstripe/silverstripe-cms.git
|
||||||
|
|
||||||
For more information, read the ["Repositories" chapter of the Composer documentation](http://getcomposer.org/doc/05-repositories.md).
|
```
|
||||||
|
|
||||||
### Forks and branch names
|
### Forks and branch names
|
||||||
|
|
||||||
@ -297,6 +321,7 @@ In this case, you need to use Composer's aliasing feature to specify how you wan
|
|||||||
|
|
||||||
Open `composer.json`, and find the module's `require`. Then put `as (core version name)` on the end.
|
Open `composer.json`, and find the module's `require`. Then put `as (core version name)` on the end.
|
||||||
|
|
||||||
|
```
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
"require": {
|
"require": {
|
||||||
@ -308,7 +333,7 @@ Open `composer.json`, and find the module's `require`. Then put `as (core versi
|
|||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
||||||
What this means is that when the `myproj` branch is checked out into a project, this will satisfy any dependencies that 3.0.x-dev would meet. So, if another module has `"silverstripe/framework": ">=3.0.0"` in its dependency list, it won't get a conflict.
|
```
|
||||||
|
|
||||||
Both the version and the alias are specified as Composer versions, not branch names. For the relationship between branch/tag names and Composer versions, read [the relevant Composer documentation](http://getcomposer.org/doc/02-libraries.md#specifying-the-version).
|
Both the version and the alias are specified as Composer versions, not branch names. For the relationship between branch/tag names and Composer versions, read [the relevant Composer documentation](http://getcomposer.org/doc/02-libraries.md#specifying-the-version).
|
||||||
|
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Environment Management
|
||||||
|
summary: How to configure your server environment for Silverstripe CMS
|
||||||
|
---
|
||||||
|
|
||||||
# Environment management
|
# Environment management
|
||||||
|
|
||||||
As website developers, we noticed that we had a few problems. You may have the same problems:
|
As website developers, we noticed that we had a few problems. You may have the same problems:
|
||||||
@ -20,7 +25,7 @@ these at `http://localhost/`. For example, you might have a project at `~/Sites
|
|||||||
Create a new file, `~/Sites/_ss_environment.php`. Put the following content in it, editing the values of the
|
Create a new file, `~/Sites/_ss_environment.php`. Put the following content in it, editing the values of the
|
||||||
"SS_DATABASE_..." and "SS_DEFAULT_ADMIN_..." defines as appropriate.
|
"SS_DATABASE_..." and "SS_DEFAULT_ADMIN_..." defines as appropriate.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
/* What kind of environment is this: development, test, or live (ie, production)? */
|
/* What kind of environment is this: development, test, or live (ie, production)? */
|
||||||
define('SS_ENVIRONMENT_TYPE', 'dev/test/live');
|
define('SS_ENVIRONMENT_TYPE', 'dev/test/live');
|
||||||
@ -34,11 +39,11 @@ Create a new file, `~/Sites/_ss_environment.php`. Put the following content in
|
|||||||
define('SS_DEFAULT_ADMIN_USERNAME', 'username');
|
define('SS_DEFAULT_ADMIN_USERNAME', 'username');
|
||||||
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
|
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
|
||||||
|
|
||||||
|
```
|
||||||
Now, edit each of your site's configuration file, usually `mysite/_config.php`. Delete all mention
|
Now, edit each of your site's configuration file, usually `mysite/_config.php`. Delete all mention
|
||||||
of `$databaseConfig` and `Director::set_dev_servers`, and instead make sure that you file starts like this.
|
of `$databaseConfig` and `Director::set_dev_servers`, and instead make sure that you file starts like this.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
global $project;
|
global $project;
|
||||||
@ -50,7 +55,7 @@ of `$databaseConfig` and `Director::set_dev_servers`, and instead make sure that
|
|||||||
// Use _ss_environment.php file for configuration
|
// Use _ss_environment.php file for configuration
|
||||||
require_once("conf/ConfigureFromEnv.php");
|
require_once("conf/ConfigureFromEnv.php");
|
||||||
|
|
||||||
|
```
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
The mechanism by which the `_ss_environment.php` files work is quite simple. Here's how it works:
|
The mechanism by which the `_ss_environment.php` files work is quite simple. Here's how it works:
|
||||||
@ -67,7 +72,7 @@ configuration commands that use those defines as their arguments. If you are cu
|
|||||||
|
|
||||||
This is my `_ss_environment.php` file. I have it placed in `/var`, as each of the sites are in a subfolder of `/var`.
|
This is my `_ss_environment.php` file. I have it placed in `/var`, as each of the sites are in a subfolder of `/var`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
// These four define set the database connection details.
|
// These four define set the database connection details.
|
||||||
define('SS_DATABASE_CLASS', 'MySQLPDODatabase');
|
define('SS_DATABASE_CLASS', 'MySQLPDODatabase');
|
||||||
@ -102,15 +107,15 @@ This is my `_ss_environment.php` file. I have it placed in `/var`, as each of th
|
|||||||
global $_FILE_TO_URL_MAPPING;
|
global $_FILE_TO_URL_MAPPING;
|
||||||
$_FILE_TO_URL_MAPPING['/var/www'] = 'http://simon.geek.nz';
|
$_FILE_TO_URL_MAPPING['/var/www'] = 'http://simon.geek.nz';
|
||||||
|
|
||||||
### Example settings to enable Database SSL
|
```
|
||||||
|
|
||||||
In some circumstances, like connecting to a database on a remote host for example, you may wish to enable SSL encryption to ensure the protection of sensitive information and database access credentials. The code below illustrates how to do so.
|
In some circumstances, like connecting to a database on a remote host for example, you may wish to enable SSL encryption to ensure the protection of sensitive information and database access credentials. The code below illustrates how to do so.
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
SSL database connections are supported for `MySQLDatabase` and `MySQLPDODatabase` as of the moment.
|
SSL database connections are supported for `MySQLDatabase` and `MySQLPDODatabase` as of the moment.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
// These four define set the database connection details.
|
// These four define set the database connection details.
|
||||||
@ -128,7 +133,7 @@ SSL database connections are supported for `MySQLDatabase` and `MySQLPDODatabase
|
|||||||
define('SS_DEFAULT_ADMIN_USERNAME', 'username');
|
define('SS_DEFAULT_ADMIN_USERNAME', 'username');
|
||||||
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
|
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
|
||||||
|
|
||||||
|
```
|
||||||
When running the installer, make sure to check on the `Use _ss_environment file for configuration` option under the `Database Configuration` section to use the environment file.
|
When running the installer, make sure to check on the `Use _ss_environment file for configuration` option under the `Database Configuration` section to use the environment file.
|
||||||
|
|
||||||
## Available Constants
|
## Available Constants
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Directory Structure
|
||||||
|
summary: An overview of what each directory contains in a Silverstripe CMS installation
|
||||||
|
icon: sitemap
|
||||||
|
---
|
||||||
|
|
||||||
# Directory Structure
|
# Directory Structure
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Coding conventions
|
||||||
|
summary: The style guidelines we follow in all of our open source code
|
||||||
|
iconBrand: php
|
||||||
|
---
|
||||||
|
|
||||||
# Coding Conventions
|
# Coding Conventions
|
||||||
|
|
||||||
This document provides guidelines for code formatting and documentation
|
This document provides guidelines for code formatting and documentation
|
||||||
@ -38,10 +44,10 @@ Class, function, variable and constant names may only contain alphanumeric chara
|
|||||||
|
|
||||||
Class and filenames are in `UpperCamelCase` format:
|
Class and filenames are in `UpperCamelCase` format:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyClass {}
|
class MyClass {}
|
||||||
|
|
||||||
If a class name is comprised of more than one word, the first letter of each
|
```
|
||||||
new word must be capitalized. Successive capitalized letters are used in
|
new word must be capitalized. Successive capitalized letters are used in
|
||||||
acronyms, e.g. a class `XMLImporter` is used while `XmlImporter` is not.
|
acronyms, e.g. a class `XMLImporter` is used while `XmlImporter` is not.
|
||||||
|
|
||||||
@ -49,53 +55,53 @@ acronyms, e.g. a class `XMLImporter` is used while `XmlImporter` is not.
|
|||||||
|
|
||||||
Static methods should be in `lowercase_with_underscores()` format:
|
Static methods should be in `lowercase_with_underscores()` format:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public static function my_static_method() {}
|
public static function my_static_method() {}
|
||||||
|
|
||||||
Action handlers on controllers should be in `completelylowercase()` format.
|
```
|
||||||
This is because they go into the controller URL in the same format (eg, `home/successfullyinstalled`).
|
This is because they go into the controller URL in the same format (eg, `home/successfullyinstalled`).
|
||||||
Method names are allowed to contain underscores here, in order to allow URL parts with dashes
|
Method names are allowed to contain underscores here, in order to allow URL parts with dashes
|
||||||
(`mypage\my-action` gets translated to `my_action()` automatically).
|
(`mypage\my-action` gets translated to `my_action()` automatically).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function mycontrolleraction() {}
|
public function mycontrolleraction() {}
|
||||||
|
|
||||||
Object methods that will be callable from templates should be in `$this->UpperCamelCase()` format.
|
```
|
||||||
Alternatively, `$this->getUpperCamelCase()` will work the same way in templates -
|
Alternatively, `$this->getUpperCamelCase()` will work the same way in templates -
|
||||||
you can access both coding styles as `$UpperCamelCase`.
|
you can access both coding styles as `$UpperCamelCase`.
|
||||||
|
|
||||||
Other instance methods should be in `$this->lowerCamelCase()` format:
|
Other instance methods should be in `$this->lowerCamelCase()` format:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function myInstanceMethod() {}
|
public function myInstanceMethod() {}
|
||||||
|
|
||||||
Methods inside classes must always declare their visibility by using one of the private, protected, or public modifiers.
|
```
|
||||||
|
|
||||||
### Variables
|
### Variables
|
||||||
|
|
||||||
Static variables should be `self::$lowercase_with_underscores`
|
Static variables should be `self::$lowercase_with_underscores`
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
self::$my_static_variable = 'foo';
|
self::$my_static_variable = 'foo';
|
||||||
|
|
||||||
Member variables should be `$this->lowerCamelCase`
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$this->myMemberVariable = 'foo';
|
$this->myMemberVariable = 'foo';
|
||||||
|
|
||||||
Member variables always declare their visibility by using one of the private, protected, or public modifiers
|
```
|
||||||
|
|
||||||
### Constants
|
### Constants
|
||||||
|
|
||||||
All letters used in a constant name must be capitalized,
|
All letters used in a constant name must be capitalized,
|
||||||
while all words in a constant name must be separated by underscore characters.
|
while all words in a constant name must be separated by underscore characters.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
const INTEREST_RATE = 0.19;
|
const INTEREST_RATE = 0.19;
|
||||||
|
|
||||||
define('INTEREST_RATE', 0.19);
|
define('INTEREST_RATE', 0.19);
|
||||||
|
|
||||||
Constants must be defined as class members with the `const` modifier.
|
```
|
||||||
Defining constants in the global scope with the `define` function is permitted but strongly discouraged.
|
Defining constants in the global scope with the `define` function is permitted but strongly discouraged.
|
||||||
|
|
||||||
### File Naming and Directory Structure
|
### File Naming and Directory Structure
|
||||||
@ -106,7 +112,7 @@ For example `MyClass` and `MyClass_Controller` will both need to be placed into
|
|||||||
|
|
||||||
Example: `mysite/code/MyClass.php`
|
Example: `mysite/code/MyClass.php`
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyClass {}
|
class MyClass {}
|
||||||
@ -115,7 +121,7 @@ Example: `mysite/code/MyClass.php`
|
|||||||
|
|
||||||
class MyClass_OtherRelatedClass {}
|
class MyClass_OtherRelatedClass {}
|
||||||
|
|
||||||
To help with namespacing common class names (like Database) it is recommended to use a prefix convention `SS_ClassName` but the filename will remain `ClassName.php`.
|
```
|
||||||
|
|
||||||
See [directory structure](directory_structure) for more information.
|
See [directory structure](directory_structure) for more information.
|
||||||
|
|
||||||
@ -125,10 +131,10 @@ See [directory structure](directory_structure) for more information.
|
|||||||
|
|
||||||
PHP code must always be delimited by the full-form, standard PHP tags:
|
PHP code must always be delimited by the full-form, standard PHP tags:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
```
|
||||||
Short tags are never allowed. For files containing only PHP code, the closing tag must always be omitted.
|
Short tags are never allowed. For files containing only PHP code, the closing tag must always be omitted.
|
||||||
It is not required by PHP, and omitting it prevents the accidental injection of trailing white space into the response.
|
It is not required by PHP, and omitting it prevents the accidental injection of trailing white space into the response.
|
||||||
|
|
||||||
@ -141,47 +147,47 @@ white space is expected.
|
|||||||
|
|
||||||
When a string is literal (contains no variable substitutions), the apostrophe or "single quote" should always be used to demarcate the string:
|
When a string is literal (contains no variable substitutions), the apostrophe or "single quote" should always be used to demarcate the string:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$a = 'Example String';
|
$a = 'Example String';
|
||||||
|
|
||||||
#### String Literals Containing Apostrophes
|
```
|
||||||
|
|
||||||
When a literal string itself contains apostrophes, it is permitted to demarcate the string with quotation marks or "double quotes".
|
When a literal string itself contains apostrophes, it is permitted to demarcate the string with quotation marks or "double quotes".
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$greeting = "They said 'hello'";
|
$greeting = "They said 'hello'";
|
||||||
|
|
||||||
This syntax is preferred over escaping apostrophes as it is much easier to read.
|
```
|
||||||
|
|
||||||
#### String Substitution
|
#### String Substitution
|
||||||
|
|
||||||
Variable substitution is permitted using either of these forms:
|
Variable substitution is permitted using either of these forms:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$greeting = "Hello $name, welcome back!";
|
$greeting = "Hello $name, welcome back!";
|
||||||
$greeting = "Hello {$name}, welcome back!";
|
$greeting = "Hello {$name}, welcome back!";
|
||||||
|
|
||||||
For consistency, placing the dollar sign outside of the brackets is not permitted:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$greeting = "Hello ${name}, welcome back!";
|
$greeting = "Hello ${name}, welcome back!";
|
||||||
|
|
||||||
#### String Concatentation
|
```
|
||||||
|
|
||||||
Strings must be concatenated using the "." operator. A space must always be added before and after the "." operator to improve readability:
|
Strings must be concatenated using the "." operator. A space must always be added before and after the "." operator to improve readability:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$copyright = 'SilverStripe Ltd (' . $year . ')';
|
$copyright = 'SilverStripe Ltd (' . $year . ')';
|
||||||
|
|
||||||
When concatenating strings with the "." operator, it is encouraged to break the statement into multiple lines to improve readability.
|
```
|
||||||
In these cases, each successive line should be padded with white space such that the "."; operator is aligned under the "=" operator:
|
In these cases, each successive line should be padded with white space such that the "."; operator is aligned under the "=" operator:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$sql = 'SELECT "ID", "Name" FROM "Person" '
|
$sql = 'SELECT "ID", "Name" FROM "Person" '
|
||||||
. 'WHERE "Name" = \'Susan\' '
|
. 'WHERE "Name" = \'Susan\' '
|
||||||
. 'ORDER BY "Name" ASC ';
|
. 'ORDER BY "Name" ASC ';
|
||||||
|
|
||||||
### Arrays
|
```
|
||||||
|
|
||||||
#### Numerically Indexed Arrays
|
#### Numerically Indexed Arrays
|
||||||
|
|
||||||
@ -190,30 +196,30 @@ Negative numbers are not permitted as indices.
|
|||||||
An indexed array may start with any non-negative number, however all base indices besides 0 are discouraged.
|
An indexed array may start with any non-negative number, however all base indices besides 0 are discouraged.
|
||||||
When declaring indexed arrays with the Array function, a trailing space must be added after each comma delimiter to improve readability:
|
When declaring indexed arrays with the Array function, a trailing space must be added after each comma delimiter to improve readability:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$sampleArray = array(1, 2, 3, 'Zend', 'Studio');
|
$sampleArray = array(1, 2, 3, 'Zend', 'Studio');
|
||||||
|
|
||||||
It is permitted to declare multi-line indexed arrays using the "array" construct.
|
```
|
||||||
In this case, each successive line must be padded with spaces such that beginning of each line is aligned:
|
In this case, each successive line must be padded with spaces such that beginning of each line is aligned:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$sampleArray = array(1, 2, 3, 'Zend', 'Studio',
|
$sampleArray = array(1, 2, 3, 'Zend', 'Studio',
|
||||||
$a, $b, $c,
|
$a, $b, $c,
|
||||||
56.44, $d, 500);
|
56.44, $d, 500);
|
||||||
|
|
||||||
Alternately, the initial array item may begin on the following line.
|
```
|
||||||
If so, it should be padded at one indentation level greater than the line containing the array declaration,
|
If so, it should be padded at one indentation level greater than the line containing the array declaration,
|
||||||
and all successive lines should have the same indentation;
|
and all successive lines should have the same indentation;
|
||||||
the closing paren should be on a line by itself at the same indentation level as the line containing the array declaration:
|
the closing paren should be on a line by itself at the same indentation level as the line containing the array declaration:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$sampleArray = array(
|
$sampleArray = array(
|
||||||
1, 2, 3, 'Zend', 'Studio',
|
1, 2, 3, 'Zend', 'Studio',
|
||||||
$a, $b, $c,
|
$a, $b, $c,
|
||||||
56.44, $d, 500,
|
56.44, $d, 500,
|
||||||
);
|
);
|
||||||
|
|
||||||
When using this latter declaration, we encourage using a trailing comma for the last item in the array;
|
```
|
||||||
this minimizes the impact of adding new items on successive lines, and helps to ensure no parse errors occur due to a missing comma.
|
this minimizes the impact of adding new items on successive lines, and helps to ensure no parse errors occur due to a missing comma.
|
||||||
|
|
||||||
#### Associative Arrays
|
#### Associative Arrays
|
||||||
@ -221,34 +227,34 @@ this minimizes the impact of adding new items on successive lines, and helps to
|
|||||||
When declaring associative arrays with the `array` construct, breaking the statement into multiple lines is encouraged.
|
When declaring associative arrays with the `array` construct, breaking the statement into multiple lines is encouraged.
|
||||||
In this case, each successive line must be padded with white space such that both the keys and the values are aligned:
|
In this case, each successive line must be padded with white space such that both the keys and the values are aligned:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$sampleArray = array('firstKey' => 'firstValue',
|
$sampleArray = array('firstKey' => 'firstValue',
|
||||||
'secondKey' => 'secondValue');
|
'secondKey' => 'secondValue');
|
||||||
|
|
||||||
Alternately, the initial array item may begin on the following line.
|
```
|
||||||
If so, it should be padded at one indentation level greater than the line containing the array declaration,
|
If so, it should be padded at one indentation level greater than the line containing the array declaration,
|
||||||
and all successive lines should have the same indentation; the closing paren should be on a line by itself at the
|
and all successive lines should have the same indentation; the closing paren should be on a line by itself at the
|
||||||
same indentation level as the line containing the array declaration.
|
same indentation level as the line containing the array declaration.
|
||||||
For readability, the various "=>" assignment operators should be padded such that they align.
|
For readability, the various "=>" assignment operators should be padded such that they align.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$sampleArray = array(
|
$sampleArray = array(
|
||||||
'firstKey' => 'firstValue',
|
'firstKey' => 'firstValue',
|
||||||
'secondKey' => 'secondValue',
|
'secondKey' => 'secondValue',
|
||||||
);
|
);
|
||||||
|
|
||||||
### Function and Method Declaration
|
```
|
||||||
|
|
||||||
No method or function invocation is allowed to have spaces directly
|
No method or function invocation is allowed to have spaces directly
|
||||||
before or after the opening parathesis, as well as no space before the closing parenthesis.
|
before or after the opening parathesis, as well as no space before the closing parenthesis.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function foo($arg1, $arg2) {} // good
|
public function foo($arg1, $arg2) {} // good
|
||||||
public function foo ( $arg1, $arg2 ) {} // bad
|
public function foo ( $arg1, $arg2 ) {} // bad
|
||||||
|
|
||||||
Keep the opening brace on the same line as the statement.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// good
|
// good
|
||||||
public function foo() {
|
public function foo() {
|
||||||
// ...
|
// ...
|
||||||
@ -260,20 +266,20 @@ Keep the opening brace on the same line as the statement.
|
|||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
In cases where the argument list exceeds the maximum line length, you may introduce line breaks.
|
```
|
||||||
Additional arguments to the function or method must be indented one additional level beyond the function or method declaration.
|
Additional arguments to the function or method must be indented one additional level beyond the function or method declaration.
|
||||||
A line break should then occur before the closing argument paren,
|
A line break should then occur before the closing argument paren,
|
||||||
which should then be placed on the same line as the opening brace of the function
|
which should then be placed on the same line as the opening brace of the function
|
||||||
or method with one space separating the two, and at the same indentation level as the function or method declaration.
|
or method with one space separating the two, and at the same indentation level as the function or method declaration.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function bar($arg1, $arg2, $arg3,
|
public function bar($arg1, $arg2, $arg3,
|
||||||
$arg4, $arg5, $arg6
|
$arg4, $arg5, $arg6
|
||||||
) {
|
) {
|
||||||
// indented code
|
// indented code
|
||||||
}
|
}
|
||||||
|
|
||||||
Function and method arguments should be separated by a single trailing space after the comma delimiter,
|
```
|
||||||
apart from the last argument.
|
apart from the last argument.
|
||||||
|
|
||||||
### Control Structures
|
### Control Structures
|
||||||
@ -286,18 +292,18 @@ before or after the opening parenthesis, as well as no space before the closing
|
|||||||
The opening brace and closing brace are written on the same line as the conditional statement.
|
The opening brace and closing brace are written on the same line as the conditional statement.
|
||||||
Any content within the braces must be indented using a tab.
|
Any content within the braces must be indented using a tab.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
if($a != 2) {
|
if($a != 2) {
|
||||||
$a = 2;
|
$a = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
If the conditional statement causes the line length to exceed the maximum line length and has several clauses,
|
```
|
||||||
you may break the conditional into multiple lines. In such a case, break the line prior to a logic operator,
|
you may break the conditional into multiple lines. In such a case, break the line prior to a logic operator,
|
||||||
and pad the line such that it aligns under the first character of the conditional clause.
|
and pad the line such that it aligns under the first character of the conditional clause.
|
||||||
The closing paren in the conditional will then be placed on a line with the opening brace,
|
The closing paren in the conditional will then be placed on a line with the opening brace,
|
||||||
with one space separating the two, at an indentation level equivalent to the opening control statement.
|
with one space separating the two, at an indentation level equivalent to the opening control statement.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
if(($a == $b)
|
if(($a == $b)
|
||||||
&& ($b == $c)
|
&& ($b == $c)
|
||||||
|| (Foo::CONST == $d)
|
|| (Foo::CONST == $d)
|
||||||
@ -305,12 +311,12 @@ with one space separating the two, at an indentation level equivalent to the ope
|
|||||||
$a = $d;
|
$a = $d;
|
||||||
}
|
}
|
||||||
|
|
||||||
The intention of this latter declaration format is to prevent issues when adding or removing clauses
|
```
|
||||||
from the conditional during later revisions. For `if` statements that include `elseif` or `else`,
|
from the conditional during later revisions. For `if` statements that include `elseif` or `else`,
|
||||||
the formatting conventions are similar to the `if` construct.
|
the formatting conventions are similar to the `if` construct.
|
||||||
The following examples demonstrate proper formatting for `if` statements with `else` and/or `elseif` constructs:
|
The following examples demonstrate proper formatting for `if` statements with `else` and/or `elseif` constructs:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
if($a != 2) {
|
if($a != 2) {
|
||||||
$a = 2;
|
$a = 2;
|
||||||
} elseif($a == 3) {
|
} elseif($a == 3) {
|
||||||
@ -319,9 +325,9 @@ The following examples demonstrate proper formatting for `if` statements with `e
|
|||||||
$a = 7;
|
$a = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
Statements with `if` can be written without braces on a single line as the block, as long as no `else` statement exists.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// good
|
// good
|
||||||
if($a == $b) doThis();
|
if($a == $b) doThis();
|
||||||
|
|
||||||
@ -329,12 +335,12 @@ Statements with `if` can be written without braces on a single line as the block
|
|||||||
if($a == $b) doThis();
|
if($a == $b) doThis();
|
||||||
else doThat();
|
else doThat();
|
||||||
|
|
||||||
#### switch
|
```
|
||||||
|
|
||||||
All content within the "switch" statement must be indented using tabs.
|
All content within the "switch" statement must be indented using tabs.
|
||||||
Content under each "case" statement must be indented using an additional tab.
|
Content under each "case" statement must be indented using an additional tab.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
switch($numPeople) {
|
switch($numPeople) {
|
||||||
case 1:
|
case 1:
|
||||||
break;
|
break;
|
||||||
@ -344,7 +350,7 @@ Content under each "case" statement must be indented using an additional tab.
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
The construct `default` should never be omitted from a switch statement.
|
```
|
||||||
|
|
||||||
#### for/foreach/while
|
#### for/foreach/while
|
||||||
|
|
||||||
@ -354,7 +360,7 @@ Loop constructs follow the same principles as "Control Structures: if/else/elsei
|
|||||||
|
|
||||||
Try to avoid using PHP's ability to mix HTML into the code.
|
Try to avoid using PHP's ability to mix HTML into the code.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// PHP code
|
// PHP code
|
||||||
public function getTitle() {
|
public function getTitle() {
|
||||||
return "<h2>Bad Example</h2>";
|
return "<h2>Bad Example</h2>";
|
||||||
@ -363,9 +369,9 @@ Try to avoid using PHP's ability to mix HTML into the code.
|
|||||||
// Template code
|
// Template code
|
||||||
$Title
|
$Title
|
||||||
|
|
||||||
Better: Keep HTML in template files:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// PHP code
|
// PHP code
|
||||||
public function getTitle() {
|
public function getTitle() {
|
||||||
return "Better Example";
|
return "Better Example";
|
||||||
@ -374,7 +380,7 @@ Better: Keep HTML in template files:
|
|||||||
// Template code
|
// Template code
|
||||||
<h2>$Title</h2>
|
<h2>$Title</h2>
|
||||||
|
|
||||||
## Comments
|
```
|
||||||
|
|
||||||
Use [phpdoc](http://phpdoc.org/) syntax before each definition (see [tutorial](http://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumentor/tutorial_phpDocumentor.quickstart.pkg.html)
|
Use [phpdoc](http://phpdoc.org/) syntax before each definition (see [tutorial](http://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumentor/tutorial_phpDocumentor.quickstart.pkg.html)
|
||||||
and [tag overview](http://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumentor/tutorial_tags.pkg.html)).
|
and [tag overview](http://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumentor/tutorial_tags.pkg.html)).
|
||||||
@ -388,7 +394,7 @@ and [tag overview](http://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumen
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
/**
|
/**
|
||||||
* My short description for this class.
|
* My short description for this class.
|
||||||
* My longer description with
|
* My longer description with
|
||||||
@ -415,6 +421,7 @@ Example:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
### Class Member Ordering
|
### Class Member Ordering
|
||||||
|
|
||||||
Put code into the classes in the following order (where applicable).
|
Put code into the classes in the following order (where applicable).
|
||||||
@ -434,18 +441,18 @@ Put code into the classes in the following order (where applicable).
|
|||||||
If you have to use raw SQL, make sure your code works across databases. Make sure you escape your queries like below,
|
If you have to use raw SQL, make sure your code works across databases. Make sure you escape your queries like below,
|
||||||
with the column or table name escaped with double quotes as below.
|
with the column or table name escaped with double quotes as below.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
MyClass::get()->where(array("\"Score\" > ?" => 50));
|
MyClass::get()->where(array("\"Score\" > ?" => 50));
|
||||||
|
|
||||||
It is preferable to use parameterised queries whenever necessary to provide conditions
|
```
|
||||||
to a SQL query, where values placeholders are each replaced with a single unquoted question mark.
|
to a SQL query, where values placeholders are each replaced with a single unquoted question mark.
|
||||||
If it's absolutely necessary to use literal values in a query make sure that values
|
If it's absolutely necessary to use literal values in a query make sure that values
|
||||||
are single quoted.
|
are single quoted.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
MyClass::get()->where("\"Title\" = 'my title'");
|
MyClass::get()->where("\"Title\" = 'my title'");
|
||||||
|
|
||||||
Use [ANSI SQL](http://en.wikipedia.org/wiki/SQL#Standardization) format where possible.
|
```
|
||||||
|
|
||||||
### Secure Development
|
### Secure Development
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Getting Started
|
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.
|
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.
|
||||||
|
icon: rocket
|
||||||
|
---
|
||||||
|
|
||||||
## Installing SilverStripe
|
## Installing SilverStripe
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
---
|
||||||
title: Building a basic site
|
title: Building a basic site
|
||||||
summary: An overview of the SilverStripe installation and an introduction to creating a web page.
|
summary: An overview of the SilverStripe installation and an introduction to creating a web page.
|
||||||
|
---
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
This tutorial is deprecated, and has been replaced by Lessons 1, 2, 3, and 4 in the [Lessons section](http://www.silverstripe.org/learn/lessons)
|
This tutorial is deprecated, and has been replaced by Lessons 1, 2, 3, and 4 in the [Lessons section](http://www.silverstripe.org/learn/lessons)
|
||||||
</div>
|
[/alert]
|
||||||
# Tutorial 1 - Building a Basic Site
|
# Tutorial 1 - Building a Basic Site
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
@ -42,8 +44,9 @@ Let's have a look at the folder structure.
|
|||||||
| framework/ | | The framework that builds both your own site and the CMS that powers it. You’ll be utilizing files in this directory often, both directly and indirectly. |
|
| framework/ | | The framework that builds both your own site and the CMS that powers it. You’ll be utilizing files in this directory often, both directly and indirectly. |
|
||||||
| mysite/ | | Contains all your site's code (mainly PHP). |
|
| mysite/ | | Contains all your site's code (mainly PHP). |
|
||||||
| themes/ | | Combines all images, stylesheets, javascript and templates powering your website into a reusable "theme". |
|
| themes/ | | Combines all images, stylesheets, javascript and templates powering your website into a reusable "theme". |
|
||||||
|
```
|
||||||
|
|
||||||
When designing your site you should only need to modify the *mysite*, *themes* and *assets* folders. The rest of the folders contain files and data that are not specific to any site.
|
```
|
||||||
|
|
||||||
## Using the CMS
|
## Using the CMS
|
||||||
|
|
||||||
@ -111,43 +114,43 @@ for a template file in the *simple/templates* folder, with the name `<PageType>`
|
|||||||
|
|
||||||
Open *themes/simple/templates/Page.ss*. It uses standard HTML apart from these exceptions:
|
Open *themes/simple/templates/Page.ss*. It uses standard HTML apart from these exceptions:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% base_tag %>
|
<% base_tag %>
|
||||||
|
|
||||||
The base_tag variable is replaced with the HTML [base element](http://www.w3.org/TR/html401/struct/links.html#h-12.4). This
|
```
|
||||||
ensures the browser knows where to locate your site's images and css files.
|
ensures the browser knows where to locate your site's images and css files.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Title
|
$Title
|
||||||
$SiteConfig.Title
|
$SiteConfig.Title
|
||||||
|
|
||||||
These two variables are found within the html `<title>` tag, and are replaced by the "Page Name" and "Settings -> Site Title" fields in the CMS.
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$MetaTags
|
$MetaTags
|
||||||
|
|
||||||
The MetaTags variable will add meta tags, which are used by search engines. You can define your meta tags in the tab fields at the bottom of the content editor in the CMS.
|
```
|
||||||
:::ss
|
```ss
|
||||||
$Layout
|
$Layout
|
||||||
|
|
||||||
The Layout variable is replaced with the contents of a template file with the same name as the page type we are using.
|
```
|
||||||
|
|
||||||
Open *themes/simple/templates/Layout/Page.ss*. You will see more HTML and more SilverStripe template replacement tags and variables.
|
Open *themes/simple/templates/Layout/Page.ss*. You will see more HTML and more SilverStripe template replacement tags and variables.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Content
|
$Content
|
||||||
|
|
||||||
The Content variable is replaced with the content of the page currently being viewed. This allows you to make all changes to
|
```
|
||||||
your site's content in the CMS.
|
your site's content in the CMS.
|
||||||
|
|
||||||
These template markers are processed by SilverStripe into HTML before being sent to your
|
These template markers are processed by SilverStripe into HTML before being sent to your
|
||||||
browser and are either prefixed with a dollar sign ($)
|
browser and are either prefixed with a dollar sign ($)
|
||||||
or placed between SilverStripe template tags:
|
or placed between SilverStripe template tags:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% %>
|
<% %>
|
||||||
|
|
||||||
|
```
|
||||||
**Flushing the cache**
|
**Flushing the cache**
|
||||||
|
|
||||||
Whenever we edit a template file, we need to append *?flush=1* onto the end of the URL, e.g.
|
Whenever we edit a template file, we need to append *?flush=1* onto the end of the URL, e.g.
|
||||||
@ -162,16 +165,16 @@ Open up *themes/simple/templates/Includes/Navigation.ss*
|
|||||||
|
|
||||||
The Menu for our site is created using a **loop**. Loops allow us to iterate over a data set, and render each item using a sub-template.
|
The Menu for our site is created using a **loop**. Loops allow us to iterate over a data set, and render each item using a sub-template.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% loop $Menu(1) %>
|
<% loop $Menu(1) %>
|
||||||
|
|
||||||
returns a set of first level menu items. We can then use the template variable
|
```
|
||||||
*$MenuTitle* to show the title of the page we are linking to, *$Link* for the URL of the page, and `$isSection` and `$isCurrent` to help style our menu with CSS (explained in more detail shortly).
|
*$MenuTitle* to show the title of the page we are linking to, *$Link* for the URL of the page, and `$isSection` and `$isCurrent` to help style our menu with CSS (explained in more detail shortly).
|
||||||
|
|
||||||
> *$Title* refers to **Page Name** in the CMS, whereas *$MenuTitle* refers to (the often shorter) **Navigation label**
|
> *$Title* refers to **Page Name** in the CMS, whereas *$MenuTitle* refers to (the often shorter) **Navigation label**
|
||||||
|
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<% loop $Menu(1) %>
|
<% loop $Menu(1) %>
|
||||||
<li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>">
|
<li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>">
|
||||||
@ -180,7 +183,7 @@ returns a set of first level menu items. We can then use the template variable
|
|||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Here we've created an unordered list called *Menu1*, which *themes/simple/css/layout.css* will style into the menu.
|
```
|
||||||
Then, using a loop over the page control *Menu(1)*, we add a link to the list for each menu item.
|
Then, using a loop over the page control *Menu(1)*, we add a link to the list for each menu item.
|
||||||
|
|
||||||
This creates the navigation at the top of the page:
|
This creates the navigation at the top of the page:
|
||||||
@ -195,17 +198,17 @@ A useful feature is highlighting the current page the user is looking at. We can
|
|||||||
|
|
||||||
For example, if you were here: "Home > Company > Staff > Bob Smith", you may want to highlight 'Company' to say you are in that section.
|
For example, if you were here: "Home > Company > Staff > Bob Smith", you may want to highlight 'Company' to say you are in that section.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>">
|
<li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>">
|
||||||
<a href="$Link" title="$Title.XML">$MenuTitle.XML</a>
|
<a href="$Link" title="$Title.XML">$MenuTitle.XML</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
you will then be able to target a section in css (*simple/css/layout.css*), e.g.:
|
```
|
||||||
|
|
||||||
:::css
|
```css
|
||||||
.section { background:#ccc; }
|
.section { background:#ccc; }
|
||||||
|
|
||||||
## A second level of navigation
|
```
|
||||||
|
|
||||||
The top navigation system is currently quite restrictive. There is no way to
|
The top navigation system is currently quite restrictive. There is no way to
|
||||||
nest pages, so we have a completely flat site. Adding a second level in SilverStripe is easy. First (if you haven't already done so), let's add some pages.
|
nest pages, so we have a completely flat site. Adding a second level in SilverStripe is easy. First (if you haven't already done so), let's add some pages.
|
||||||
@ -224,7 +227,7 @@ Great, we now have a hierarchical site structure! Let's look at how this is crea
|
|||||||
|
|
||||||
Adding a second level menu is very similar to adding the first level menu. Open up */themes/simple/templates/Includes/Sidebar.ss* template and look at the following code:
|
Adding a second level menu is very similar to adding the first level menu. Open up */themes/simple/templates/Includes/Sidebar.ss* template and look at the following code:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<% loop $Menu(2) %>
|
<% loop $Menu(2) %>
|
||||||
<li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>">
|
<li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>">
|
||||||
@ -236,7 +239,7 @@ Adding a second level menu is very similar to adding the first level menu. Open
|
|||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
This should look very familiar. It is the same idea as our first menu, except the loop block now uses *Menu(2)* instead of *Menu(1)*.
|
```
|
||||||
As we can see here, the *Menu* control takes a single
|
As we can see here, the *Menu* control takes a single
|
||||||
argument - the level of the menu we want to get. Our css file will style this linked list into the second level menu,
|
argument - the level of the menu we want to get. Our css file will style this linked list into the second level menu,
|
||||||
using our usual `is` technique to highlight the current page.
|
using our usual `is` technique to highlight the current page.
|
||||||
@ -245,7 +248,7 @@ To make sure the menu is not displayed on every page, for example, those that *d
|
|||||||
Look again in the *Sidebar.ss* file and you will see that the menu is surrounded with an **if block**
|
Look again in the *Sidebar.ss* file and you will see that the menu is surrounded with an **if block**
|
||||||
like this:
|
like this:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $Menu(2) %>
|
<% if $Menu(2) %>
|
||||||
...
|
...
|
||||||
<ul>
|
<ul>
|
||||||
@ -261,7 +264,7 @@ like this:
|
|||||||
...
|
...
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
The if block only includes the code inside it if the condition is true. In this case, it checks for the existence of
|
```
|
||||||
*Menu(2)*. If it exists then the code inside will be processed and the menu will be shown. Otherwise the code will not
|
*Menu(2)*. If it exists then the code inside will be processed and the menu will be shown. Otherwise the code will not
|
||||||
be processed and the menu will not be shown.
|
be processed and the menu will not be shown.
|
||||||
|
|
||||||
@ -269,22 +272,22 @@ Now that we have two levels of navigation, it would also be useful to include so
|
|||||||
|
|
||||||
Open up */themes/simple/templates/Includes/BreadCrumbs.ss* template and look at the following code:
|
Open up */themes/simple/templates/Includes/BreadCrumbs.ss* template and look at the following code:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $Level(2) %>
|
<% if $Level(2) %>
|
||||||
<div id="Breadcrumbs">
|
<div id="Breadcrumbs">
|
||||||
$Breadcrumbs
|
$Breadcrumbs
|
||||||
</div>
|
</div>
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
Breadcrumbs are only useful on pages that aren't in the top level. We can ensure that we only show them if we aren't in
|
```
|
||||||
the top level with another if statement.
|
the top level with another if statement.
|
||||||
|
|
||||||
The *Level* page control allows you to get data from the page's parents, e.g. if you used *Level(1)*, you could use:
|
The *Level* page control allows you to get data from the page's parents, e.g. if you used *Level(1)*, you could use:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Level(1).Title
|
$Level(1).Title
|
||||||
|
|
||||||
to get the top level page title. In this case, we merely use it to check the existence of a second level page: if one exists then we include breadcrumbs.
|
```
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@ -294,7 +297,7 @@ Feel free to experiment with the if and loop statements. For example, you could
|
|||||||
|
|
||||||
The following example runs an if statement and a loop on *Children*, checking to see if any sub-pages exist within each top level navigation item. You will need to come up with your own CSS to correctly style this approach.
|
The following example runs an if statement and a loop on *Children*, checking to see if any sub-pages exist within each top level navigation item. You will need to come up with your own CSS to correctly style this approach.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<% loop $Menu(1) %>
|
<% loop $Menu(1) %>
|
||||||
<li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>">
|
<li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>">
|
||||||
@ -315,7 +318,7 @@ The following example runs an if statement and a loop on *Children*, checking to
|
|||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## Using a different template for the home page
|
## Using a different template for the home page
|
||||||
|
|
||||||
@ -335,14 +338,14 @@ types right now, we will go into much more detail in the [next tutorial](/tutori
|
|||||||
|
|
||||||
Create a new file *HomePage.php* in *mysite/code*. Copy the following code into it:
|
Create a new file *HomePage.php* in *mysite/code*. Copy the following code into it:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class HomePage extends Page {
|
class HomePage extends Page {
|
||||||
}
|
}
|
||||||
class HomePage_Controller extends Page_Controller {
|
class HomePage_Controller extends Page_Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Every page type also has a database table corresponding to it. Every time we modify the database, we need to rebuild it.
|
Every page type also has a database table corresponding to it. Every time we modify the database, we need to rebuild it.
|
||||||
We can do this by going to `http://localhost/your_site_name/dev/build`.
|
We can do this by going to `http://localhost/your_site_name/dev/build`.
|
||||||
|
|
||||||
@ -370,22 +373,24 @@ It always tries to use the most specific template in an inheritance chain.
|
|||||||
To create a new template layout, create a copy of *Page.ss* (found in *themes/simple/templates/Layout*) and call it *HomePage.ss*. If we flush the cache (*?flush=1*), SilverStripe should now be using *HomePage.ss* for the homepage, and *Page.ss* for the rest of the site. Now let's customise the *HomePage* template.
|
To create a new template layout, create a copy of *Page.ss* (found in *themes/simple/templates/Layout*) and call it *HomePage.ss*. If we flush the cache (*?flush=1*), SilverStripe should now be using *HomePage.ss* for the homepage, and *Page.ss* for the rest of the site. Now let's customise the *HomePage* template.
|
||||||
|
|
||||||
First, we don't need the breadcrumbs and the secondary menu for the homepage. Let's remove them:
|
First, we don't need the breadcrumbs and the secondary menu for the homepage. Let's remove them:
|
||||||
:::ss
|
```ss
|
||||||
<% include SideBar %>
|
<% include SideBar %>
|
||||||
|
|
||||||
|
```
|
||||||
We'll also replace the title text with an image. Find this line:
|
We'll also replace the title text with an image. Find this line:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<h1>$Title</h1>
|
<h1>$Title</h1>
|
||||||
|
|
||||||
|
```
|
||||||
and replace it with:
|
and replace it with:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<div id="Banner">
|
<div id="Banner">
|
||||||
<img src="http://www.silverstripe.org/assets/SilverStripe-200.png" alt="Homepage image" />
|
<img src="http://www.silverstripe.org/assets/SilverStripe-200.png" alt="Homepage image" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
```
|
||||||
Your Home page should now look like this:
|
Your Home page should now look like this:
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
---
|
||||||
title: Extending a basic site
|
title: Extending a basic site
|
||||||
summary: Building on tutorial 1, a look at storing data in SilverStripe and creating a latest news feed.
|
summary: Building on tutorial 1, a look at storing data in SilverStripe and creating a latest news feed.
|
||||||
|
---
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
This tutorial is deprecated, and has been replaced by Lessons 4, 5, and 6 in the [Lessons section](http://www.silverstripe.org/learn/lessons)
|
This tutorial is deprecated, and has been replaced by Lessons 4, 5, and 6 in the [Lessons section](http://www.silverstripe.org/learn/lessons)
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
# Tutorial 2 - Extending a basic site
|
# Tutorial 2 - Extending a basic site
|
||||||
|
|
||||||
@ -67,7 +69,7 @@ We'll start with the *ArticlePage* page type. First we create the model, a class
|
|||||||
|
|
||||||
**mysite/code/ArticlePage.php**
|
**mysite/code/ArticlePage.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class ArticlePage extends Page {
|
class ArticlePage extends Page {
|
||||||
}
|
}
|
||||||
@ -75,7 +77,7 @@ We'll start with the *ArticlePage* page type. First we create the model, a class
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
Here we've created our data object/controller pair, but we haven't extended them at all. SilverStripe will use the template for the *Page* page type as explained in the first tutorial, so we don't need
|
Here we've created our data object/controller pair, but we haven't extended them at all. SilverStripe will use the template for the *Page* page type as explained in the first tutorial, so we don't need
|
||||||
to specifically create the view for this page type.
|
to specifically create the view for this page type.
|
||||||
|
|
||||||
@ -83,7 +85,7 @@ Let's create the *ArticleHolder* page type.
|
|||||||
|
|
||||||
**mysite/code/ArticleHolder.php**
|
**mysite/code/ArticleHolder.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class ArticleHolder extends Page {
|
class ArticleHolder extends Page {
|
||||||
private static $allowed_children = array('ArticlePage');
|
private static $allowed_children = array('ArticlePage');
|
||||||
@ -91,7 +93,7 @@ Let's create the *ArticleHolder* page type.
|
|||||||
class ArticleHolder_Controller extends Page_Controller {
|
class ArticleHolder_Controller extends Page_Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Here we have done something interesting: the *$allowed_children* field. This is one of a number of static fields we can define to change the properties of a page type. The *$allowed_children* field is an array of page types that are allowed
|
Here we have done something interesting: the *$allowed_children* field. This is one of a number of static fields we can define to change the properties of a page type. The *$allowed_children* field is an array of page types that are allowed
|
||||||
to be children of the page in the site tree. As we only want **news articles** in the news section, we only want pages of the type *ArticlePage* as children. We can enforce this in the CMS by setting the *$allowed_children* field within this class.
|
to be children of the page in the site tree. As we only want **news articles** in the news section, we only want pages of the type *ArticlePage* as children. We can enforce this in the CMS by setting the *$allowed_children* field within this class.
|
||||||
|
|
||||||
@ -99,11 +101,11 @@ We will be introduced to other fields like this as we progress; there is a full
|
|||||||
|
|
||||||
Now that we have created our page types, we need to let SilverStripe rebuild the database: [http://localhost/your_site_name/dev/build](http://localhost/your_site_name/dev/build). SilverStripe should detect that there are two new page types, and add them to the list of page types in the database.
|
Now that we have created our page types, we need to let SilverStripe rebuild the database: [http://localhost/your_site_name/dev/build](http://localhost/your_site_name/dev/build). SilverStripe should detect that there are two new page types, and add them to the list of page types in the database.
|
||||||
|
|
||||||
<div class="hint" markdown="1">
|
[hint]
|
||||||
It is SilverStripe convention to suffix general page types with "Page", and page types that hold other page types with
|
It is SilverStripe convention to suffix general page types with "Page", and page types that hold other page types with
|
||||||
"Holder". This is to ensure that we don't have URLs with the same name as a page type; if we named our *ArticleHolder*
|
"Holder". This is to ensure that we don't have URLs with the same name as a page type; if we named our *ArticleHolder*
|
||||||
page type "News", it would conflict with the page name also called "News".
|
page type "News", it would conflict with the page name also called "News".
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
## Adding date and author fields
|
## Adding date and author fields
|
||||||
|
|
||||||
@ -111,7 +113,7 @@ Now that we have an *ArticlePage* page type, let's make it a little more useful.
|
|||||||
the $db array to add extra fields to the database. It would be nice to know when each article was posted, and who posted
|
the $db array to add extra fields to the database. It would be nice to know when each article was posted, and who posted
|
||||||
it. Add a *$db* property definition in the *ArticlePage* class:
|
it. Add a *$db* property definition in the *ArticlePage* class:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class ArticlePage extends Page {
|
class ArticlePage extends Page {
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
@ -122,19 +124,19 @@ 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 and casting"](/developer_guides/model/data_types_and_casting) 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">
|
[hint]
|
||||||
The names chosen for the fields you add must not already be used. Be careful using field names such as Title,
|
The names chosen for the fields you add must not already be used. Be careful using field names such as Title,
|
||||||
Content etc. as these may already be defined in the page types your new page is extending from.
|
Content etc. as these may already be defined in the page types your new page is extending from.
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
When we rebuild the database, we will see that the *ArticlePage* table has been created. Even though we had an *ArticlePage* page type before, a table was not created because there were no fields unique to the article page type. There are now extra fields in the database, but still no way of changing them.
|
When we rebuild the database, we will see that the *ArticlePage* table has been created. Even though we had an *ArticlePage* page type before, a table was not created because there were no fields unique to the article page type. There are now extra fields in the database, but still no way of changing them.
|
||||||
|
|
||||||
To add our new fields to the CMS we have to override the *getCMSFields()* method, which is called by the CMS when it creates the form to edit a page. Add the method to the *ArticlePage* class.
|
To add our new fields to the CMS we have to override the *getCMSFields()* method, which is called by the CMS when it creates the form to edit a page. Add the method to the *ArticlePage* class.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class ArticlePage extends Page {
|
class ArticlePage extends Page {
|
||||||
// ...
|
// ...
|
||||||
@ -153,37 +155,37 @@ To add our new fields to the CMS we have to override the *getCMSFields()* method
|
|||||||
|
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
Let's walk through this method.
|
Let's walk through this method.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields = parent::getCMSFields();
|
$fields = parent::getCMSFields();
|
||||||
|
|
||||||
|
```
|
||||||
Firstly, we get the fields from the parent class; we want to add fields, not replace them. The *$fields* variable
|
Firstly, we get the fields from the parent class; we want to add fields, not replace them. The *$fields* variable
|
||||||
returned is a [api:FieldList] object.
|
returned is a [api:FieldList] object.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields->addFieldToTab('Root.Main', new TextField('Author'), 'Content');
|
$fields->addFieldToTab('Root.Main', new TextField('Author'), 'Content');
|
||||||
$fields->addFieldToTab('Root.Main', new DateField('Date'), 'Content');
|
$fields->addFieldToTab('Root.Main', new DateField('Date'), 'Content');
|
||||||
|
|
||||||
|
```
|
||||||
We can then add our new fields with *addFieldToTab*. The first argument is the tab on which we want to add the field to:
|
We can then add our new fields with *addFieldToTab*. The first argument is the tab on which we want to add the field to:
|
||||||
"Root.Main" is the tab which the content editor is on. The second argument is the field to add; this is not a database field, but a [api:FormField] - see the documentation for more details.
|
"Root.Main" is the tab which the content editor is on. The second argument is the field to add; this is not a database field, but a [api:FormField] - see the documentation for more details.
|
||||||
|
|
||||||
<div class="hint" markdown="1">
|
[hint]
|
||||||
Note: By default, the CMS only has one tab. Creating new tabs is much like adding to existing tabs. For instance: `$fields->addFieldToTab('Root.NewTab', new TextField('Author'));`
|
Note: By default, the CMS only has one tab. Creating new tabs is much like adding to existing tabs. For instance: `$fields->addFieldToTab('Root.NewTab', new TextField('Author'));`
|
||||||
would create a new tab called "New Tab", and a single "Author" textfield inside.
|
would create a new tab called "New Tab", and a single "Author" textfield inside.
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
We have added two fields: A simple [api:TextField] and a [api:DateField].
|
We have added two fields: A simple [api:TextField] and a [api:DateField].
|
||||||
There are many more fields available in the default installation, listed in ["form field types"](/developer_guides/forms/field_types/common_subclasses).
|
There are many more fields available in the default installation, listed in ["form field types"](/developer_guides/forms/field_types/common_subclasses).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
return $fields;
|
return $fields;
|
||||||
|
|
||||||
|
```
|
||||||
Finally, we return the fields to the CMS. If we flush the cache (by adding ?flush=1 at the end of the URL), we will be able to edit the fields in the CMS.
|
Finally, we return the fields to the CMS. If we flush the cache (by adding ?flush=1 at the end of the URL), we will be able to edit the fields in the CMS.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
@ -198,7 +200,7 @@ This makes it confusing and doesn't give the user much help when adding a date.
|
|||||||
To make the date field a bit more user friendly, you can add a dropdown calendar, set the date format and add a better title. By default,
|
To make the date field a bit more user friendly, you can add a dropdown calendar, set the date format and add a better title. By default,
|
||||||
the date field will have the date format defined by your locale.
|
the date field will have the date format defined by your locale.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class ArticlePage extends Page {
|
class ArticlePage extends Page {
|
||||||
|
|
||||||
@ -217,27 +219,27 @@ the date field will have the date format defined by your locale.
|
|||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
Let's walk through these changes.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$dateField = new DateField('Date', 'Article Date (for example: 20/12/2010)');
|
$dateField = new DateField('Date', 'Article Date (for example: 20/12/2010)');
|
||||||
|
|
||||||
*$dateField* is declared in order to change the configuration of the DateField.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$dateField->setConfig('showcalendar', true);
|
$dateField->setConfig('showcalendar', true);
|
||||||
|
|
||||||
By enabling *showCalendar* you show a calendar overlay when clicking on the field.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$dateField->setConfig('dateformat', 'dd/MM/YYYY');
|
$dateField->setConfig('dateformat', 'dd/MM/YYYY');
|
||||||
|
|
||||||
*dateFormat* allows you to specify how you wish the date to be entered and displayed in the CMS field. See the [api:DateField] documentation for more configuration options.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields->addFieldToTab('Root.Main', new TextField('Author', 'Author Name'), 'Content');
|
$fields->addFieldToTab('Root.Main', new TextField('Author', 'Author Name'), 'Content');
|
||||||
|
|
||||||
By default the field name *'Date'* or *'Author'* is shown as the title, however this might not be that helpful so to change the title, add the new title as the second argument.
|
```
|
||||||
|
|
||||||
|
|
||||||
## Creating the templates
|
## Creating the templates
|
||||||
@ -253,7 +255,7 @@ First, the template for displaying a single article:
|
|||||||
**themes/simple/templates/Layout/ArticlePage.ss**
|
**themes/simple/templates/Layout/ArticlePage.ss**
|
||||||
|
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% include SideBar %>
|
<% include SideBar %>
|
||||||
<div class="content-container unit size3of4 lastUnit">
|
<div class="content-container unit size3of4 lastUnit">
|
||||||
<article>
|
<article>
|
||||||
@ -266,7 +268,7 @@ First, the template for displaying a single article:
|
|||||||
$Form
|
$Form
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
```
|
||||||
Most of the code is just like the regular Page.ss, we include an informational div with the date and the author of the Article.
|
Most of the code is just like the regular Page.ss, we include an informational div with the date and the author of the Article.
|
||||||
|
|
||||||
To access the new fields, we use *$Date* and *$Author*. In fact, all template variables and page controls come from either the data object or the controller for the page being displayed. The *$Title* variable comes from the *Title* field of the [api:SiteTree] class. *$Date* and *$Author* come from the *ArticlePage* table through your custom Page. *$Content* comes from the *SiteTree* table through the same data object. The data for your page is
|
To access the new fields, we use *$Date* and *$Author*. In fact, all template variables and page controls come from either the data object or the controller for the page being displayed. The *$Title* variable comes from the *Title* field of the [api:SiteTree] class. *$Date* and *$Author* come from the *ArticlePage* table through your custom Page. *$Content* comes from the *SiteTree* table through the same data object. The data for your page is
|
||||||
@ -286,7 +288,7 @@ We'll now create a template for the article holder. We want our news section to
|
|||||||
|
|
||||||
**themes/simple/templates/Layout/ArticleHolder.ss**
|
**themes/simple/templates/Layout/ArticleHolder.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% include SideBar %>
|
<% include SideBar %>
|
||||||
<div class="content-container unit size3of4 lastUnit">
|
<div class="content-container unit size3of4 lastUnit">
|
||||||
<article>
|
<article>
|
||||||
@ -304,7 +306,7 @@ We'll now create a template for the article holder. We want our news section to
|
|||||||
$Form
|
$Form
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
```
|
||||||
Here we use the page control *Children*. As the name suggests, this control allows you to iterate over the children of a page. In this case, the children are our news articles. The *$Link* variable will give the address of the article which we can use to create a link, and the *FirstParagraph* function of the [api:HTMLText] field gives us a nice summary of the article. The function strips all tags from the paragraph extracted.
|
Here we use the page control *Children*. As the name suggests, this control allows you to iterate over the children of a page. In this case, the children are our news articles. The *$Link* variable will give the address of the article which we can use to create a link, and the *FirstParagraph* function of the [api:HTMLText] field gives us a nice summary of the article. The function strips all tags from the paragraph extracted.
|
||||||
|
|
||||||
![](../_images/tutorial2_articleholder.jpg)
|
![](../_images/tutorial2_articleholder.jpg)
|
||||||
@ -320,46 +322,46 @@ Cut the code between "loop Children" in *ArticleHolder.ss** and replace it with
|
|||||||
|
|
||||||
**themes/simple/templates/Layout/ArticleHolder.ss**
|
**themes/simple/templates/Layout/ArticleHolder.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
...
|
...
|
||||||
<% loop $Children %>
|
<% loop $Children %>
|
||||||
<% include ArticleTeaser %>
|
<% include ArticleTeaser %>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
...
|
...
|
||||||
|
|
||||||
Paste the code that was in ArticleHolder into a new include file called ArticleTeaser.ss:
|
```
|
||||||
|
|
||||||
**themes/simple/templates/Includes/ArticleTeaser.ss**
|
**themes/simple/templates/Includes/ArticleTeaser.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<article>
|
<article>
|
||||||
<h2><a href="$Link" title="Read more on "{$Title}"">$Title</a></h2>
|
<h2><a href="$Link" title="Read more on "{$Title}"">$Title</a></h2>
|
||||||
<p>$Content.FirstParagraph</p>
|
<p>$Content.FirstParagraph</p>
|
||||||
<a href="$Link" title="Read more on "{$Title}"">Read more >></a>
|
<a href="$Link" title="Read more on "{$Title}"">Read more >></a>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
### Changing the icons of pages in the CMS
|
```
|
||||||
|
|
||||||
Now let's make a purely cosmetic change that nevertheless helps to make the information presented in the CMS clearer.
|
Now let's make a purely cosmetic change that nevertheless helps to make the information presented in the CMS clearer.
|
||||||
Add the following field to the *ArticleHolder* and *ArticlePage* classes:
|
Add the following field to the *ArticleHolder* and *ArticlePage* classes:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
private static $icon = "cms/images/treeicons/news-file.gif";
|
private static $icon = "cms/images/treeicons/news-file.gif";
|
||||||
|
|
||||||
|
```
|
||||||
And this one to the *HomePage* class:
|
And this one to the *HomePage* class:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
private static $icon = "cms/images/treeicons/home-file.png";
|
private static $icon = "cms/images/treeicons/home-file.png";
|
||||||
|
|
||||||
|
```
|
||||||
This will change the icons for the pages in the CMS.
|
This will change the icons for the pages in the CMS.
|
||||||
|
|
||||||
![](../_images/tutorial2_icons2.jpg)
|
![](../_images/tutorial2_icons2.jpg)
|
||||||
|
|
||||||
<div class="hint" markdown="1">
|
[hint]
|
||||||
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.
|
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.
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
## Showing the latest news on the homepage
|
## Showing the latest news on the homepage
|
||||||
|
|
||||||
@ -367,19 +369,19 @@ It would be nice to greet page visitors with a summary of the latest news when t
|
|||||||
|
|
||||||
**mysite/code/HomePage.php**
|
**mysite/code/HomePage.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// ...
|
// ...
|
||||||
public function LatestNews($num=5) {
|
public function LatestNews($num=5) {
|
||||||
$holder = ArticleHolder::get()->First();
|
$holder = ArticleHolder::get()->First();
|
||||||
return ($holder) ? ArticlePage::get()->filter('ParentID', $holder->ID)->sort('Date DESC')->limit($num) : false;
|
return ($holder) ? ArticlePage::get()->filter('ParentID', $holder->ID)->sort('Date DESC')->limit($num) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
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:
|
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**
|
**themes/simple/templates/Layout/Homepage.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<!-- ... -->
|
<!-- ... -->
|
||||||
<div class="content">$Content</div>
|
<div class="content">$Content</div>
|
||||||
</article>
|
</article>
|
||||||
@ -387,7 +389,7 @@ This function simply runs a database query that gets the latest news articles fr
|
|||||||
<% include ArticleTeaser %>
|
<% include ArticleTeaser %>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
|
||||||
|
```
|
||||||
When SilverStripe comes across a variable or page control it doesn't recognize, it first passes control to the controller. If the controller doesn't have a function for the variable or page control, it then passes control to the data object. If it has no matching functions, it then searches its database fields. Failing that it will return nothing.
|
When SilverStripe comes across a variable or page control it doesn't recognize, it first passes control to the controller. If the controller doesn't have a function for the variable or page control, it then passes control to the data object. If it has no matching functions, it then searches its database fields. Failing that it will return nothing.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
@ -402,7 +404,7 @@ An RSS feed is something that no news section should be without. SilverStripe ma
|
|||||||
|
|
||||||
**mysite/code/ArticleHolder.php**
|
**mysite/code/ArticleHolder.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
private static $allowed_actions = array(
|
private static $allowed_actions = array(
|
||||||
'rss'
|
'rss'
|
||||||
);
|
);
|
||||||
@ -412,7 +414,7 @@ An RSS feed is something that no news section should be without. SilverStripe ma
|
|||||||
return $rss->outputToBrowser();
|
return $rss->outputToBrowser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Ensure that when you have input the code to implement an RSS feed; flush the webpage afterwards
|
Ensure that when you have input the code to implement an RSS feed; flush the webpage afterwards
|
||||||
(add ?flush=all on the end of your URL). This is because allowed_actions has changed.
|
(add ?flush=all on the end of your URL). This is because allowed_actions has changed.
|
||||||
|
|
||||||
@ -426,13 +428,13 @@ Now all we need is to let the user know that our RSS feed exists. Add this funct
|
|||||||
|
|
||||||
**mysite/code/ArticleHolder.php**
|
**mysite/code/ArticleHolder.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function init() {
|
public function init() {
|
||||||
RSSFeed::linkToFeed($this->Link() . "rss");
|
RSSFeed::linkToFeed($this->Link() . "rss");
|
||||||
parent::init();
|
parent::init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
This automatically generates a link-tag in the header of our template. The *init* function is then called on the parent class to ensure any initialization the parent would have done if we hadn't overridden the *init* function is still called. Depending on your browser, you can see the RSS feed link in the address bar.
|
This automatically generates a link-tag in the header of our template. The *init* function is then called on the parent class to ensure any initialization the parent would have done if we hadn't overridden the *init* function is still called. Depending on your browser, you can see the RSS feed link in the address bar.
|
||||||
|
|
||||||
## Adding a staff section
|
## Adding a staff section
|
||||||
@ -441,7 +443,7 @@ Now that we have a complete news section, let's take a look at the staff section
|
|||||||
|
|
||||||
**mysite/code/StaffHolder.php**
|
**mysite/code/StaffHolder.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class StaffHolder extends Page {
|
class StaffHolder extends Page {
|
||||||
@ -454,12 +456,12 @@ Now that we have a complete news section, let's take a look at the staff section
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Nothing here should be new. The *StaffPage* page type is more interesting though. Each staff member has a portrait image. We want to make a permanent connection between this image and the specific *StaffPage* (otherwise we could simply insert an image in the *$Content* field).
|
Nothing here should be new. The *StaffPage* page type is more interesting though. Each staff member has a portrait image. We want to make a permanent connection between this image and the specific *StaffPage* (otherwise we could simply insert an image in the *$Content* field).
|
||||||
|
|
||||||
**mysite/code/StaffPage.php**
|
**mysite/code/StaffPage.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class StaffPage extends Page {
|
class StaffPage extends Page {
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
@ -480,7 +482,7 @@ Nothing here should be new. The *StaffPage* page type is more interesting though
|
|||||||
class StaffPage_Controller extends Page_Controller {
|
class StaffPage_Controller extends Page_Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Instead of adding our *Image* as a field in *$db*, we have used the *$has_one* array. This is because an *Image* is not a simple database field like all the fields we have seen so far, but has its own database table. By using the *$has_one* array, we create a relationship between the *StaffPage* table and the *Image* table by storing the id of the respective *Image* in the *StaffPage* table.
|
Instead of adding our *Image* as a field in *$db*, we have used the *$has_one* array. This is because an *Image* is not a simple database field like all the fields we have seen so far, but has its own database table. By using the *$has_one* array, we create a relationship between the *StaffPage* table and the *Image* table by storing the id of the respective *Image* in the *StaffPage* table.
|
||||||
|
|
||||||
We then add an [api:UploadField] in the *getCMSFields* function to the tab "Root.Images". Since this tab doesn't exist,
|
We then add an [api:UploadField] in the *getCMSFields* function to the tab "Root.Images". Since this tab doesn't exist,
|
||||||
@ -501,7 +503,7 @@ The staff section templates aren't too difficult to create, thanks to the utilit
|
|||||||
|
|
||||||
**themes/simple/templates/Layout/StaffHolder.ss**
|
**themes/simple/templates/Layout/StaffHolder.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% include SideBar %>
|
<% include SideBar %>
|
||||||
<div class="content-container unit size3of4 lastUnit">
|
<div class="content-container unit size3of4 lastUnit">
|
||||||
<article>
|
<article>
|
||||||
@ -520,7 +522,7 @@ The staff section templates aren't too difficult to create, thanks to the utilit
|
|||||||
$Form
|
$Form
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
```
|
||||||
This template is very similar to the *ArticleHolder* template. The *ScaleWidth* method of the [api:Image] class
|
This template is very similar to the *ArticleHolder* template. The *ScaleWidth* method of the [api:Image] class
|
||||||
will resize the image before sending it to the browser. The resized image is cached, so the server doesn't have to
|
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.
|
resize the image every time the page is viewed.
|
||||||
@ -531,7 +533,7 @@ The *StaffPage* template is also very straight forward.
|
|||||||
|
|
||||||
**themes/simple/templates/Layout/StaffPage.ss**
|
**themes/simple/templates/Layout/StaffPage.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% include SideBar %>
|
<% include SideBar %>
|
||||||
<div class="content-container unit size3of4 lastUnit">
|
<div class="content-container unit size3of4 lastUnit">
|
||||||
<article>
|
<article>
|
||||||
@ -543,7 +545,7 @@ The *StaffPage* template is also very straight forward.
|
|||||||
$Form
|
$Form
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Here we use the *ScaleWidth* method to get a different sized image from the same source image. You should now have
|
```
|
||||||
a complete staff section.
|
a complete staff section.
|
||||||
|
|
||||||
![](../_images/tutorial2_einstein.jpg)
|
![](../_images/tutorial2_einstein.jpg)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Forms
|
title: Forms
|
||||||
summary: Capture and store user information through web forms.
|
summary: Capture and store user information through web forms.
|
||||||
|
iconBrand: wpforms
|
||||||
|
---
|
||||||
# Tutorial 3 - Forms
|
# Tutorial 3 - Forms
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
@ -22,55 +24,10 @@ The poll we will be creating on our homepage will ask the user for their name an
|
|||||||
|
|
||||||
**mysite/code/HomePage.php**
|
**mysite/code/HomePage.php**
|
||||||
|
|
||||||
```php
|
|
||||||
class HomePage_Controller extends Page_Controller {
|
|
||||||
private static $allowed_actions = array('BrowserPollForm');
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
public function BrowserPollForm() {
|
|
||||||
// Create fields
|
|
||||||
$fields = new FieldList(
|
|
||||||
new TextField('Name'),
|
|
||||||
new OptionsetField('Browser', 'Your Favourite Browser', array(
|
|
||||||
'Firefox' => 'Firefox',
|
|
||||||
'Chrome' => 'Chrome',
|
|
||||||
'Internet Explorer' => 'Internet Explorer',
|
|
||||||
'Safari' => 'Safari',
|
|
||||||
'Opera' => 'Opera',
|
|
||||||
'Lynx' => 'Lynx'
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create actions
|
|
||||||
$actions = new FieldList(
|
|
||||||
new FormAction('doBrowserPoll', 'Submit')
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Form($this, 'BrowserPollForm', $fields, $actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's step through this code.
|
Let's step through this code.
|
||||||
|
|
||||||
```php
|
|
||||||
// Create fields
|
|
||||||
$fields = new FieldList(
|
|
||||||
new TextField('Name'),
|
|
||||||
new OptionsetField('Browser', 'Your Favourite Browser', array(
|
|
||||||
'Firefox' => 'Firefox',
|
|
||||||
'Chrome' => 'Chrome',
|
|
||||||
'Internet Explorer' => 'Internet Explorer',
|
|
||||||
'Safari' => 'Safari',
|
|
||||||
'Opera' => 'Opera',
|
|
||||||
'Lynx' => 'Lynx'
|
|
||||||
))
|
|
||||||
);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
First we create our form fields.
|
First we create our form fields.
|
||||||
@ -81,10 +38,6 @@ 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
|
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.
|
array mapping the values to the options listed in the dropdown.
|
||||||
|
|
||||||
```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.
|
After creating the fields, we create the form actions. Form actions appear as buttons at the bottom of the form.
|
||||||
@ -93,8 +46,6 @@ 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
|
All the form actions (in this case only one) are collected into a [api:FieldList] object the same way we did with
|
||||||
the fields.
|
the fields.
|
||||||
|
|
||||||
```php
|
|
||||||
return new Form($this, 'BrowserPollForm', $fields, $actions);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally we create the [api:Form] object and return it.
|
Finally we create the [api:Form] object and return it.
|
||||||
@ -107,14 +58,6 @@ Add the following code to the top of your home page template, just before `<div
|
|||||||
|
|
||||||
**themes/simple/templates/Layout/HomePage.ss**
|
**themes/simple/templates/Layout/HomePage.ss**
|
||||||
|
|
||||||
```ss
|
|
||||||
...
|
|
||||||
<div id="BrowserPoll">
|
|
||||||
<h2>Browser Poll</h2>
|
|
||||||
$BrowserPollForm
|
|
||||||
</div>
|
|
||||||
<div class="Content">
|
|
||||||
...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
In order to make the graphs render correctly,
|
In order to make the graphs render correctly,
|
||||||
@ -123,49 +66,6 @@ Add the following code to the existing `form.css` file:
|
|||||||
|
|
||||||
**themes/simple/css/form.css**
|
**themes/simple/css/form.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;
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -183,29 +83,11 @@ If you recall, in the [second tutorial](/tutorials/extending_a_basic_site) we sa
|
|||||||
|
|
||||||
**mysite/code/BrowserPollSubmission.php**
|
**mysite/code/BrowserPollSubmission.php**
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
class BrowserPollSubmission extends DataObject {
|
|
||||||
private static $db = array(
|
|
||||||
'Name' => 'Text',
|
|
||||||
'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*:
|
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**
|
**mysite/code/HomePage.php**
|
||||||
|
|
||||||
```php
|
|
||||||
class HomePage_Controller extends Page_Controller {
|
|
||||||
// ...
|
|
||||||
public function doBrowserPoll($data, $form) {
|
|
||||||
$submission = new BrowserPollSubmission();
|
|
||||||
$form->saveInto($submission);
|
|
||||||
$submission->write();
|
|
||||||
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.
|
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.
|
||||||
@ -222,12 +104,6 @@ Change the end of the 'BrowserPollForm' function so it looks like this:
|
|||||||
|
|
||||||
**mysite/code/HomePage.php**
|
**mysite/code/HomePage.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.
|
If we then open the homepage and attempt to submit the form without filling in the required fields errors should appear.
|
||||||
@ -244,32 +120,11 @@ We can do this using a session variable. The [api:Session] class handles all ses
|
|||||||
|
|
||||||
**mysite/code/HomePage.php**
|
**mysite/code/HomePage.php**
|
||||||
|
|
||||||
```php
|
|
||||||
// ...
|
|
||||||
class HomePage_Controller extends Page_Controller {
|
|
||||||
// ...
|
|
||||||
public function doBrowserPoll($data, $form) {
|
|
||||||
$submission = new BrowserPollSubmission();
|
|
||||||
$form->saveInto($submission);
|
|
||||||
$submission->write();
|
|
||||||
Session::set('BrowserPollVoted', true);
|
|
||||||
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
|
Then we simply need to check if the session variable has been set in 'BrowserPollForm()', and to not return the form if
|
||||||
it is.
|
it is.
|
||||||
|
|
||||||
```php
|
|
||||||
// ...
|
|
||||||
class HomePage_Controller extends Page_Controller {
|
|
||||||
// ...
|
|
||||||
public function BrowserPollForm() {
|
|
||||||
if(Session::get('BrowserPollVoted')) return false;
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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,
|
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,
|
||||||
@ -285,43 +140,15 @@ Create the function 'BrowserPollResults' on the *HomePage_Controller* class.
|
|||||||
|
|
||||||
**mysite/code/HomePage.php**
|
**mysite/code/HomePage.php**
|
||||||
|
|
||||||
```php
|
|
||||||
public function BrowserPollResults() {
|
|
||||||
$submissions = new GroupedList(BrowserPollSubmission::get());
|
|
||||||
$total = $submissions->Count();
|
|
||||||
|
|
||||||
$list = new ArrayList();
|
|
||||||
foreach($submissions->groupBy('Browser') as $browserName => $browserSubmissions) {
|
|
||||||
$percentage = (int) ($browserSubmissions->Count() / $total * 100);
|
|
||||||
$list->push(new ArrayData(array(
|
|
||||||
'Browser' => $browserName,
|
|
||||||
'Percentage' => $percentage
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
return $list;
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
This code introduces a few new concepts, so let's step through it.
|
This code introduces a few new concepts, so let's step through it.
|
||||||
|
|
||||||
```php
|
|
||||||
$submissions = new GroupedList(BrowserPollSubmission::get());
|
|
||||||
```
|
```
|
||||||
First we get all of the `BrowserPollSubmission` records from the database. This returns the submissions as a [api:DataList]. Then we wrap it inside a [api:GroupedList], which adds the ability to group those records. The resulting object will behave just like the original `DataList`, though (with the addition of a `groupBy()` method).
|
First we get all of the `BrowserPollSubmission` records from the database. This returns the submissions as a [api:DataList]. Then we wrap it inside a [api:GroupedList], which adds the ability to group those records. The resulting object will behave just like the original `DataList`, though (with the addition of a `groupBy()` method).
|
||||||
|
|
||||||
```php
|
|
||||||
$total = $submissions->Count();
|
|
||||||
```
|
```
|
||||||
We get the total number of submissions, which is needed to calculate the percentages.
|
We get the total number of submissions, which is needed to calculate the percentages.
|
||||||
|
|
||||||
```php
|
|
||||||
$list = new ArrayList();
|
|
||||||
foreach($submissions->groupBy('Browser') as $browserName => $browserSubmissions) {
|
|
||||||
$percentage = (int) ($browserSubmissions->Count() / $total * 100);
|
|
||||||
$list->push(new ArrayData(array(
|
|
||||||
'Browser' => $browserName,
|
|
||||||
'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.
|
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.
|
||||||
@ -333,22 +160,6 @@ The final step is to create the template to display our data. Change the 'Browse
|
|||||||
|
|
||||||
**themes/simple/templates/Layout/HomePage.ss**
|
**themes/simple/templates/Layout/HomePage.ss**
|
||||||
|
|
||||||
```ss
|
|
||||||
<div id="BrowserPoll">
|
|
||||||
<h2>Browser Poll</h2>
|
|
||||||
<% if $BrowserPollForm %>
|
|
||||||
$BrowserPollForm
|
|
||||||
<% else %>
|
|
||||||
<ul>
|
|
||||||
<% loop $BrowserPollResults %>
|
|
||||||
<li>
|
|
||||||
<div class="browser">$Browser: $Percentage%</div>
|
|
||||||
<div class="bar" style="width:$Percentage%"> </div>
|
|
||||||
</li>
|
|
||||||
<% end_loop %>
|
|
||||||
</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,
|
Here we first check if the *BrowserPollForm* is returned, and if it is display it. Otherwise the user has already voted,
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
title: Site Search
|
title: Site Search
|
||||||
summary: Enable website search. How to handle paged result sets and the SearchForm class.
|
summary: Enable website search. How to handle paged result sets and the SearchForm class.
|
||||||
|
icon: search
|
||||||
|
---
|
||||||
|
|
||||||
# Tutorial 4 - Site Search
|
# Tutorial 4 - Site Search
|
||||||
|
|
||||||
@ -18,10 +21,10 @@ We are going to add a search box on the top of the page. When a user types somet
|
|||||||
To enable the search engine you need to include the following code in your `mysite/_config.php` file.
|
To enable the search engine you need to include the following code in your `mysite/_config.php` file.
|
||||||
This will enable fulltext search on page content as well as names of all files in the `/assets` folder.
|
This will enable fulltext search on page content as well as names of all files in the `/assets` folder.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
FulltextSearchable::enable();
|
FulltextSearchable::enable();
|
||||||
|
|
||||||
After including that in your `_config.php` you will need to rebuild the database by visiting [http://localhost/your_site_name/dev/build](http://localhost/your_site_name/dev/build) in your web browser (replace localhost/your_site_name with a domain if applicable). This will add fulltext search columns.
|
```
|
||||||
|
|
||||||
The actual search form code is already provided in FulltextSearchable so when you add the enable line above to your `_config.php` you can add your form as `$SearchForm`.
|
The actual search form code is already provided in FulltextSearchable so when you add the enable line above to your `_config.php` you can add your form as `$SearchForm`.
|
||||||
|
|
||||||
@ -34,7 +37,7 @@ To add the search form, we can add `$SearchForm` anywhere in our templates. In t
|
|||||||
|
|
||||||
**themes/simple/templates/Includes/Header.ss**
|
**themes/simple/templates/Includes/Header.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
...
|
...
|
||||||
<% if $SearchForm %>
|
<% if $SearchForm %>
|
||||||
<span class="search-dropdown-icon">L</span>
|
<span class="search-dropdown-icon">L</span>
|
||||||
@ -44,7 +47,7 @@ To add the search form, we can add `$SearchForm` anywhere in our templates. In t
|
|||||||
<% end_if %>
|
<% end_if %>
|
||||||
<% include Navigation %>
|
<% include Navigation %>
|
||||||
|
|
||||||
This displays as:
|
```
|
||||||
|
|
||||||
![](../_images/tutorial4_searchbox.jpg)
|
![](../_images/tutorial4_searchbox.jpg)
|
||||||
|
|
||||||
@ -55,7 +58,7 @@ is applied via `FulltextSearchable::enable()`
|
|||||||
|
|
||||||
**cms/code/search/ContentControllerSearchExtension.php**
|
**cms/code/search/ContentControllerSearchExtension.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class ContentControllerSearchExtension extends Extension {
|
class ContentControllerSearchExtension extends Extension {
|
||||||
...
|
...
|
||||||
|
|
||||||
@ -69,7 +72,7 @@ is applied via `FulltextSearchable::enable()`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
The code populates an array with the data we wish to pass to the template - the search results, query and title of the page. The final line is a little more complicated.
|
The code populates an array with the data we wish to pass to the template - the search results, query and title of the page. The final line is a little more complicated.
|
||||||
|
|
||||||
When we call a function by its url (eg http://localhost/home/results), SilverStripe will look for a template with the name `PageType_function.ss`. As we are implementing the *results* function on the *Page* page type, we create our
|
When we call a function by its url (eg http://localhost/home/results), SilverStripe will look for a template with the name `PageType_function.ss`. As we are implementing the *results* function on the *Page* page type, we create our
|
||||||
@ -97,7 +100,7 @@ class.
|
|||||||
|
|
||||||
*themes/simple/templates/Layout/Page_results.ss*
|
*themes/simple/templates/Layout/Page_results.ss*
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<div id="Content" class="searchResults">
|
<div id="Content" class="searchResults">
|
||||||
<h1>$Title</h1>
|
<h1>$Title</h1>
|
||||||
|
|
||||||
@ -149,7 +152,7 @@ class.
|
|||||||
<% end_if %>
|
<% end_if %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Then finally add ?flush=1 to the URL and you should see the new template.
|
```
|
||||||
|
|
||||||
|
|
||||||
![](../_images/tutorial4_search.jpg)
|
![](../_images/tutorial4_search.jpg)
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
|
---
|
||||||
title: DataObject Relationship Management
|
title: DataObject Relationship Management
|
||||||
summary: Learn how to create custom DataObjects and how to build interfaces for managing that data.
|
summary: Learn how to create custom DataObjects and how to build interfaces for managing that data.
|
||||||
|
icon: link
|
||||||
|
---
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
This tutorial is deprecated, and has been replaced by Lessons 7, 8, 9, and 10 in the [Lessons section](http://www.silverstripe.org/learn/lessons)
|
This tutorial is deprecated, and has been replaced by Lessons 7, 8, 9, and 10 in the [Lessons section](http://www.silverstripe.org/learn/lessons)
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
# Tutorial 5 - Dataobject Relationship Management
|
# Tutorial 5 - Dataobject Relationship Management
|
||||||
|
|
||||||
@ -38,7 +41,7 @@ Let's create the `Student` and `Project` objects.
|
|||||||
|
|
||||||
**mysite/code/Student.php**
|
**mysite/code/Student.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class Student extends DataObject {
|
class Student extends DataObject {
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
@ -50,9 +53,9 @@ Let's create the `Student` and `Project` objects.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
**mysite/code/Project.php**
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class Project extends Page {
|
class Project extends Page {
|
||||||
private static $has_many = array(
|
private static $has_many = array(
|
||||||
@ -62,7 +65,7 @@ Let's create the `Student` and `Project` objects.
|
|||||||
class Project_Controller extends Page_Controller {
|
class Project_Controller extends Page_Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
The relationships are defined through the `$has_one`
|
```
|
||||||
and `$has_many` properties on the objects.
|
and `$has_many` properties on the objects.
|
||||||
The array keys declares the name of the relationship,
|
The array keys declares the name of the relationship,
|
||||||
the array values contain the class name
|
the array values contain the class name
|
||||||
@ -95,7 +98,7 @@ The restriction is enforced through the `$allowed_children` directive.
|
|||||||
|
|
||||||
**mysite/code/ProjectsHolder.php**
|
**mysite/code/ProjectsHolder.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class ProjectsHolder extends Page {
|
class ProjectsHolder extends Page {
|
||||||
private static $allowed_children = array(
|
private static $allowed_children = array(
|
||||||
@ -105,7 +108,7 @@ The restriction is enforced through the `$allowed_children` directive.
|
|||||||
class ProjectsHolder_Controller extends Page_Controller {
|
class ProjectsHolder_Controller extends Page_Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
You might have noticed that we don't specify the relationship
|
```
|
||||||
to a project. That's because it's already inherited from the parent implementation,
|
to a project. That's because it's already inherited from the parent implementation,
|
||||||
as part of the normal page hierarchy in the CMS.
|
as part of the normal page hierarchy in the CMS.
|
||||||
|
|
||||||
@ -128,7 +131,7 @@ All customization to fields for a page type are managed through a method called
|
|||||||
|
|
||||||
**mysite/code/Project.php**
|
**mysite/code/Project.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class Project extends Page {
|
class Project extends Page {
|
||||||
// ...
|
// ...
|
||||||
@ -155,7 +158,7 @@ All customization to fields for a page type are managed through a method called
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
This creates a tabular field, which lists related student records, one row at a time.
|
```
|
||||||
It's empty by default, but you can add new students as required,
|
It's empty by default, but you can add new students as required,
|
||||||
or relate them to the project by typing in the box above the table.
|
or relate them to the project by typing in the box above the table.
|
||||||
|
|
||||||
@ -168,13 +171,13 @@ The GridField API is composed of "components", which makes it very flexible.
|
|||||||
One example of this is the configuration of column names on our table:
|
One example of this is the configuration of column names on our table:
|
||||||
We call `setDisplayFields()` directly on the component responsible for their rendering.
|
We call `setDisplayFields()` directly on the component responsible for their rendering.
|
||||||
|
|
||||||
<div class="note" markdown="1">
|
[note]
|
||||||
Adding a `GridField` to a page type is a popular way to manage data,
|
Adding a `GridField` to a page type is a popular way to manage data,
|
||||||
but not the only one. If your data requires a dedicated interface
|
but not the only one. If your data requires a dedicated interface
|
||||||
with more sophisticated search and management logic, consider
|
with more sophisticated search and management logic, consider
|
||||||
using the [ModelAdmin](/developer_guides/customising_the_admin_interface/modeladmin)
|
using the [ModelAdmin](/developer_guides/customising_the_admin_interface/modeladmin)
|
||||||
interface instead.
|
interface instead.
|
||||||
</div>
|
[/note]
|
||||||
|
|
||||||
![tutorial:tutorial5_project_creation.jpg](../_images/tutorial5_project_creation.jpg)
|
![tutorial:tutorial5_project_creation.jpg](../_images/tutorial5_project_creation.jpg)
|
||||||
|
|
||||||
@ -200,7 +203,7 @@ The first step is to create the `Mentor` object and set the relation with the `P
|
|||||||
|
|
||||||
**mysite/code/Mentor.php**
|
**mysite/code/Mentor.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class Mentor extends DataObject {
|
class Mentor extends DataObject {
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
@ -211,9 +214,9 @@ The first step is to create the `Mentor` object and set the relation with the `P
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
**mysite/code/Project.php**
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class Project extends Page {
|
class Project extends Page {
|
||||||
// ...
|
// ...
|
||||||
private static $many_many = array(
|
private static $many_many = array(
|
||||||
@ -221,7 +224,7 @@ The first step is to create the `Mentor` object and set the relation with the `P
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
This code will create a relationship between the `Project` table and the `Mentor` table by storing the ids of the respective `Project` and `Mentor` in a another table named "Project_Mentors"
|
```
|
||||||
(after you've performed a `dev/build` command, of course).
|
(after you've performed a `dev/build` command, of course).
|
||||||
|
|
||||||
The second step is to add the table in the method `getCMSFields()`,
|
The second step is to add the table in the method `getCMSFields()`,
|
||||||
@ -231,7 +234,7 @@ to configure it a bit differently.
|
|||||||
|
|
||||||
**mysite/code/Project.php**
|
**mysite/code/Project.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class Project extends Page {
|
class Project extends Page {
|
||||||
// ...
|
// ...
|
||||||
public function getCMSFields() {
|
public function getCMSFields() {
|
||||||
@ -248,7 +251,7 @@ to configure it a bit differently.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
The important difference to our student management UI is the usage
|
```
|
||||||
of `$this->Mentor()` (rather than `Mentor::get()`). It will limit
|
of `$this->Mentor()` (rather than `Mentor::get()`). It will limit
|
||||||
the list of records to those related through the many-many relationship.
|
the list of records to those related through the many-many relationship.
|
||||||
|
|
||||||
@ -286,7 +289,7 @@ a named list of object.
|
|||||||
|
|
||||||
**themes/simple/templates/Layout/ProjectsHolder.ss**
|
**themes/simple/templates/Layout/ProjectsHolder.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% include SideBar %>
|
<% include SideBar %>
|
||||||
<div class="content-container unit size3of4 lastUnit">
|
<div class="content-container unit size3of4 lastUnit">
|
||||||
<article>
|
<article>
|
||||||
@ -325,7 +328,7 @@ a named list of object.
|
|||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Navigate to the holder page through your website navigation,
|
```
|
||||||
or the "Preview" feature in the CMS. You should see a list of all projects now.
|
or the "Preview" feature in the CMS. You should see a list of all projects now.
|
||||||
Add `?flush=1` to the page URL to force a refresh of the template cache.
|
Add `?flush=1` to the page URL to force a refresh of the template cache.
|
||||||
|
|
||||||
@ -344,7 +347,7 @@ we can access the "Students" and "Mentors" relationships directly in the templat
|
|||||||
|
|
||||||
**themes/simple/templates/Layout/Project.ss**
|
**themes/simple/templates/Layout/Project.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% include SideBar %>
|
<% include SideBar %>
|
||||||
<div class="content-container unit size3of4 lastUnit">
|
<div class="content-container unit size3of4 lastUnit">
|
||||||
<article>
|
<article>
|
||||||
@ -375,7 +378,7 @@ we can access the "Students" and "Mentors" relationships directly in the templat
|
|||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Follow the link to a project detail from from your holder page,
|
```
|
||||||
or navigate to it through the submenu provided by the theme.
|
or navigate to it through the submenu provided by the theme.
|
||||||
|
|
||||||
### Student Detail Template
|
### Student Detail Template
|
||||||
@ -387,19 +390,19 @@ by introducing a new template for them.
|
|||||||
|
|
||||||
**themes/simple/templates/Includes/StudentInfo.ss**
|
**themes/simple/templates/Includes/StudentInfo.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Name ($University)
|
$Name ($University)
|
||||||
|
|
||||||
To use this template, we need to add a new method to our student class:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class Student extends DataObject {
|
class Student extends DataObject {
|
||||||
function getInfo() {
|
function getInfo() {
|
||||||
return $this->renderWith('StudentInfo');
|
return $this->renderWith('StudentInfo');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Replace the student template code in both `Project.ss`
|
```
|
||||||
and `ProjectHolder.ss` templates with the new placeholder, `$Info`.
|
and `ProjectHolder.ss` templates with the new placeholder, `$Info`.
|
||||||
That's the code enclosed in `<% loop $Students %>` and `<% end_loop %>`.
|
That's the code enclosed in `<% loop $Students %>` and `<% end_loop %>`.
|
||||||
With this pattern, you can increase code reuse across templates.
|
With this pattern, you can increase code reuse across templates.
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
---
|
||||||
title: Tutorials
|
title: Tutorials
|
||||||
introduction: The tutorials below take a step by step look at how to build a SilverStripe application.
|
introduction: The tutorials below take a step by step look at how to build a SilverStripe application.
|
||||||
|
icon: graduation-cap
|
||||||
|
---
|
||||||
|
|
||||||
## Written Tutorials
|
## Written Tutorials
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
These tutorials are deprecated, and have been replaced by the new [Lessons](http://silverstripe.org/learn/lessons) section.
|
These tutorials are deprecated, and have been replaced by the new [Lessons](http://silverstripe.org/learn/lessons) section.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## Video lessons
|
## Video lessons
|
||||||
These include video screencasts, written tutorials and code examples to get you started working with SilverStripe websites.
|
These include video screencasts, written tutorials and code examples to get you started working with SilverStripe websites.
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Introduction to the Data Model and ORM
|
title: Introduction to the Data Model and ORM
|
||||||
summary: Introduction to creating and querying a database records through the ORM (object-relational model)
|
summary: Introduction to creating and querying a database records through the ORM (object-relational model)
|
||||||
|
icon: database
|
||||||
|
---
|
||||||
# Introduction to the Data Model and ORM
|
# Introduction to the Data Model and ORM
|
||||||
|
|
||||||
SilverStripe uses an [object-relational model](http://en.wikipedia.org/wiki/Object-relational_model) to represent its
|
SilverStripe uses an [object-relational model](http://en.wikipedia.org/wiki/Object-relational_model) to represent its
|
||||||
@ -19,7 +21,7 @@ Let's look at a simple example:
|
|||||||
|
|
||||||
**mysite/code/Player.php**
|
**mysite/code/Player.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -32,7 +34,7 @@ Let's look at a simple example:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
This `Player` class definition will create a database table `Player` with columns for `PlayerNumber`, `FirstName` and
|
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.
|
so on. After writing this class, we need to regenerate the database schema.
|
||||||
|
|
||||||
@ -60,10 +62,10 @@ It **won't** do any of the following
|
|||||||
their table names don't match a SilverStripe data class.
|
their table names don't match a SilverStripe data class.
|
||||||
|
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
You need to be logged in as an administrator to perform this command, unless your site is in [dev mode](../debugging),
|
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).
|
or the command is run through [CLI](../cli).
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
When rebuilding the database schema through the [api:SS_ClassLoader] the following additional properties are
|
When rebuilding the database schema through the [api:SS_ClassLoader] the following additional properties are
|
||||||
automatically set on the `DataObject`.
|
automatically set on the `DataObject`.
|
||||||
@ -75,7 +77,7 @@ automatically set on the `DataObject`.
|
|||||||
|
|
||||||
**mysite/code/Player.php**
|
**mysite/code/Player.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -88,64 +90,67 @@ automatically set on the `DataObject`.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Generates the following `SQL`.
|
```
|
||||||
|
|
||||||
|
```
|
||||||
CREATE TABLE `Player` (
|
CREATE TABLE `Player` (
|
||||||
`ID` int(11) NOT NULL AUTO_INCREMENT,
|
`ID` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
`ClassName` enum('Player') DEFAULT 'Player',
|
`ClassName` enum('Player') DEFAULT 'Player',
|
||||||
`LastEdited` datetime DEFAULT NULL,
|
`LastEdited` datetime DEFAULT NULL,
|
||||||
`Created` datetime DEFAULT NULL,
|
`Created` datetime DEFAULT NULL,
|
||||||
`PlayerNumber` int(11) NOT NULL DEFAULT '0',
|
```
|
||||||
|
```
|
||||||
`FirstName` varchar(255) DEFAULT NULL,
|
`FirstName` varchar(255) DEFAULT NULL,
|
||||||
`LastName` mediumtext,
|
```
|
||||||
`Birthday` datetime DEFAULT NULL,
|
`Birthday` datetime DEFAULT NULL,
|
||||||
|
|
||||||
PRIMARY KEY (`ID`),
|
PRIMARY KEY (`ID`),
|
||||||
|
```
|
||||||
KEY `ClassName` (`ClassName`)
|
KEY `ClassName` (`ClassName`)
|
||||||
);
|
);
|
||||||
|
|
||||||
## Creating Data Records
|
```
|
||||||
|
|
||||||
A new instance of a [api:DataObject] can be created using the `new` syntax.
|
A new instance of a [api:DataObject] can be created using the `new` syntax.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$player = new Player();
|
$player = new Player();
|
||||||
|
|
||||||
Or, a better way is to use the `create` method.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$player = Player::create();
|
$player = Player::create();
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
```
|
||||||
Using the `create()` method provides chainability, which can 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 the class can be overriden by [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection).
|
Using the `create()` method provides chainability, which can 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 the class can be overriden by [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection).
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
|
|
||||||
Database columns and properties can be set as class properties on the object. The SilverStripe ORM handles the saving
|
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.
|
of the values through a custom `__set()` method.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$player->FirstName = "Sam";
|
$player->FirstName = "Sam";
|
||||||
$player->PlayerNumber = 07;
|
$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.
|
set.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$player->write();
|
$player->write();
|
||||||
|
|
||||||
For convenience, the `write()` method returns the record's ID. This is particularly useful when creating new records.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$player = Player::create();
|
$player = Player::create();
|
||||||
$id = $player->write();
|
$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
|
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.
|
shortcuts and methods for fetching, sorting and filtering data from our database.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get();
|
$players = Player::get();
|
||||||
// returns a `DataList` containing all the `Player` objects.
|
// returns a `DataList` containing all the `Player` objects.
|
||||||
|
|
||||||
@ -158,19 +163,19 @@ shortcuts and methods for fetching, sorting and filtering data from our database
|
|||||||
echo $player->dbObject('LastEdited')->Ago();
|
echo $player->dbObject('LastEdited')->Ago();
|
||||||
// calls the `Ago` method on the `LastEdited` property.
|
// 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()`:
|
are `filter()` and `sort()`:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$members = Player::get()->filter(array(
|
$members = Player::get()->filter(array(
|
||||||
'FirstName' => 'Sam'
|
'FirstName' => 'Sam'
|
||||||
))->sort('Surname');
|
))->sort('Surname');
|
||||||
|
|
||||||
// returns a `DataList` containing all the `Player` records that have the `FirstName` of 'Sam'
|
// 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.
|
Provided `filter` values are automatically escaped and do not require any escaping.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
## Lazy Loading
|
## Lazy Loading
|
||||||
|
|
||||||
@ -179,7 +184,7 @@ The `ORM` doesn't actually execute the [api:SQLQuery] until you iterate on the r
|
|||||||
It's smart enough to generate a single efficient query at the last moment in time without needing to post-process the
|
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
|
result set in PHP. In `MySQL` the query generated by the ORM may look something like this
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->filter(array(
|
$players = Player::get()->filter(array(
|
||||||
'FirstName' => 'Sam'
|
'FirstName' => 'Sam'
|
||||||
));
|
));
|
||||||
@ -189,10 +194,10 @@ result set in PHP. In `MySQL` the query generated by the ORM may look something
|
|||||||
// executes the following single query
|
// executes the following single query
|
||||||
// SELECT * FROM Player WHERE FirstName = 'Sam' ORDER BY Surname
|
// 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.
|
This also means that getting the count of a list of objects will be done with a single, efficient query.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->filter(array(
|
$players = Player::get()->filter(array(
|
||||||
'FirstName' => 'Sam'
|
'FirstName' => 'Sam'
|
||||||
))->sort('Surname');
|
))->sort('Surname');
|
||||||
@ -201,89 +206,90 @@ This also means that getting the count of a list of objects will be done with a
|
|||||||
// SELECT COUNT(*) FROM Player WHERE FirstName = 'Sam'
|
// SELECT COUNT(*) FROM Player WHERE FirstName = 'Sam'
|
||||||
echo $players->Count();
|
echo $players->Count();
|
||||||
|
|
||||||
|
```
|
||||||
## Looping over a list of objects
|
## Looping over a list of objects
|
||||||
|
|
||||||
`get()` returns a `DataList` instance. You can loop over `DataList` instances in both PHP and templates.
|
`get()` returns a `DataList` instance. You can loop over `DataList` instances in both PHP and templates.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get();
|
$players = Player::get();
|
||||||
|
|
||||||
foreach($players as $player) {
|
foreach($players as $player) {
|
||||||
echo $player->FirstName;
|
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
|
```php
|
||||||
$players = Player::get();
|
$players = Player::get();
|
||||||
|
|
||||||
if($players->exists()) {
|
if($players->exists()) {
|
||||||
// do something here
|
// do something here
|
||||||
}
|
}
|
||||||
|
|
||||||
See the [Lists](lists) documentation for more information on dealing with [api:SS_List] instances.
|
```
|
||||||
|
|
||||||
## Returning a single DataObject
|
## 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
|
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)`:
|
can use `byID($id)`:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$player = Player::get()->byID(5);
|
$player = Player::get()->byID(5);
|
||||||
|
|
||||||
`get()` returns a [api:DataList] instance. You can use operations on that to get back a single record.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get();
|
$players = Player::get();
|
||||||
|
|
||||||
$first = $players->first();
|
$first = $players->first();
|
||||||
$last = $players->last();
|
$last = $players->last();
|
||||||
|
|
||||||
## Sorting
|
```
|
||||||
|
|
||||||
If would like to sort the list by `FirstName` in a ascending way (from A to Z).
|
If would like to sort the list by `FirstName` in a ascending way (from A to Z).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// Sort can either be Ascending (ASC) or Descending (DESC)
|
// Sort can either be Ascending (ASC) or Descending (DESC)
|
||||||
$players = Player::get()->sort('FirstName', 'ASC');
|
$players = Player::get()->sort('FirstName', 'ASC');
|
||||||
|
|
||||||
// Ascending is implied
|
// Ascending is implied
|
||||||
$players = Player::get()->sort('FirstName');
|
$players = Player::get()->sort('FirstName');
|
||||||
|
|
||||||
To reverse the sort
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->sort('FirstName', 'DESC');
|
$players = Player::get()->sort('FirstName', 'DESC');
|
||||||
|
|
||||||
// or..
|
// or..
|
||||||
$players = Player::get()->sort('FirstName', 'ASC')->reverse();
|
$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`
|
`LastName`
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Players::get()->sort(array(
|
$players = Players::get()->sort(array(
|
||||||
'FirstName' => 'ASC',
|
'FirstName' => 'ASC',
|
||||||
'LastName'=>'ASC'
|
'LastName'=>'ASC'
|
||||||
));
|
));
|
||||||
|
|
||||||
You can also sort randomly. Using the `DB` class, you can get the random sort method per database type.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$random = DB::get_conn()->random();
|
$random = DB::get_conn()->random();
|
||||||
$players = Player::get()->sort($random)
|
$players = Player::get()->sort($random)
|
||||||
|
|
||||||
|
```
|
||||||
## Filtering Results
|
## Filtering Results
|
||||||
|
|
||||||
The `filter()` method filters the list of objects that gets returned.
|
The `filter()` method filters the list of objects that gets returned.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->filter(array(
|
$players = Player::get()->filter(array(
|
||||||
'FirstName' => 'Sam'
|
'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.
|
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
|
The key in the filter corresponds to the field that you want to filter and the value in the filter corresponds to the
|
||||||
@ -291,7 +297,7 @@ value that you want to filter to.
|
|||||||
|
|
||||||
So, this would return only those players called "Sam Minnée".
|
So, this would return only those players called "Sam Minnée".
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->filter(array(
|
$players = Player::get()->filter(array(
|
||||||
'FirstName' => 'Sam',
|
'FirstName' => 'Sam',
|
||||||
'LastName' => 'Minnée',
|
'LastName' => 'Minnée',
|
||||||
@ -299,34 +305,35 @@ So, this would return only those players called "Sam Minnée".
|
|||||||
|
|
||||||
// SELECT * FROM Player WHERE FirstName = 'Sam' AND 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
|
```php
|
||||||
$players = Player::get()->filter('FirstName', 'Sam');
|
$players = Player::get()->filter('FirstName', 'Sam');
|
||||||
|
|
||||||
Or if you want to find both Sam and Sig.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->filter(
|
$players = Player::get()->filter(
|
||||||
'FirstName', array('Sam', 'Sig')
|
'FirstName', array('Sam', 'Sig')
|
||||||
);
|
);
|
||||||
|
|
||||||
// SELECT * FROM Player WHERE FirstName IN ('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.
|
exact match.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->filter(array(
|
$players = Player::get()->filter(array(
|
||||||
'FirstName:StartsWith' => 'S'
|
'FirstName:StartsWith' => 'S'
|
||||||
'PlayerNumber:GreaterThan' => '10'
|
'PlayerNumber:GreaterThan' => '10'
|
||||||
));
|
));
|
||||||
|
```
|
||||||
|
|
||||||
### filterAny
|
### filterAny
|
||||||
|
|
||||||
Use the `filterAny()` method to match multiple criteria non-exclusively (with an "OR" disjunctive),
|
Use the `filterAny()` method to match multiple criteria non-exclusively (with an "OR" disjunctive),
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->filterAny(array(
|
$players = Player::get()->filterAny(array(
|
||||||
'FirstName' => 'Sam',
|
'FirstName' => 'Sam',
|
||||||
'Age' => 17,
|
'Age' => 17,
|
||||||
@ -334,9 +341,9 @@ Use the `filterAny()` method to match multiple criteria non-exclusively (with an
|
|||||||
|
|
||||||
// SELECT * FROM Player WHERE ("FirstName" = 'Sam' OR "Age" = '17')
|
// SELECT * FROM Player WHERE ("FirstName" = 'Sam' OR "Age" = '17')
|
||||||
|
|
||||||
You can combine both conjunctive ("AND") and disjunctive ("OR") statements.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()
|
$players = Player::get()
|
||||||
->filter(array(
|
->filter(array(
|
||||||
'LastName' => 'Minnée'
|
'LastName' => 'Minnée'
|
||||||
@ -347,55 +354,55 @@ You can combine both conjunctive ("AND") and disjunctive ("OR") statements.
|
|||||||
));
|
));
|
||||||
// SELECT * FROM Player WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "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
|
```php
|
||||||
$players = Player::get()->filterAny(array(
|
$players = Player::get()->filterAny(array(
|
||||||
'FirstName:StartsWith' => 'S'
|
'FirstName:StartsWith' => 'S'
|
||||||
'PlayerNumber:GreaterThan' => '10'
|
'PlayerNumber:GreaterThan' => '10'
|
||||||
));
|
));
|
||||||
|
|
||||||
|
```
|
||||||
### filterByCallback
|
### 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
|
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()`.
|
PHP, thus `filter()` or `filterAny()` are to be preferred over `filterByCallback()`.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
Because `filterByCallback()` has to run in PHP, it has a significant performance tradeoff, and should not be used on large recordsets.
|
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`.
|
`filterByCallback()` will always return an `ArrayList`.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
The first parameter to the callback is the item, the second parameter is the list itself. The callback will run once
|
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.
|
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.
|
The below example will get all `Players` aged over 10.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->filterByCallback(function($item, $list) {
|
$players = Player::get()->filterByCallback(function($item, $list) {
|
||||||
return ($item->Age() > 10);
|
return ($item->Age() > 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
### Exclude
|
```
|
||||||
|
|
||||||
The `exclude()` method is the opposite to the filter in that it removes entries from a list.
|
The `exclude()` method is the opposite to the filter in that it removes entries from a list.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->exclude('FirstName', 'Sam');
|
$players = Player::get()->exclude('FirstName', 'Sam');
|
||||||
|
|
||||||
// SELECT * FROM Player WHERE FirstName != 'Sam'
|
// SELECT * FROM Player WHERE FirstName != 'Sam'
|
||||||
|
|
||||||
Remove both Sam and Sig..
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->exclude(
|
$players = Player::get()->exclude(
|
||||||
'FirstName', array('Sam','Sig')
|
'FirstName', array('Sam','Sig')
|
||||||
);
|
);
|
||||||
|
|
||||||
`Exclude` follows the same pattern as filter, so for removing only Sam Minnée from the list:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->exclude(array(
|
$players = Player::get()->exclude(array(
|
||||||
'FirstName' => 'Sam',
|
'FirstName' => 'Sam',
|
||||||
'Surname' => 'Minnée',
|
'Surname' => 'Minnée',
|
||||||
@ -403,16 +410,16 @@ Remove both Sam and Sig..
|
|||||||
|
|
||||||
// SELECT * FROM Player WHERE (FirstName != 'Sam' OR LastName != 'Minnée')
|
// SELECT * FROM Player WHERE (FirstName != 'Sam' OR LastName != 'Minnée')
|
||||||
|
|
||||||
Removing players with *either* the first name of Sam or the last name of Minnée requires multiple `->exclude` calls:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->exclude('FirstName', 'Sam')->exclude('Surname', 'Minnée');
|
$players = Player::get()->exclude('FirstName', 'Sam')->exclude('Surname', 'Minnée');
|
||||||
|
|
||||||
// SELECT * FROM Player WHERE FirstName != 'Sam' AND LastName != 'Minnée'
|
// SELECT * FROM Player WHERE FirstName != 'Sam' AND LastName != 'Minnée'
|
||||||
|
|
||||||
And removing Sig and Sam with that are either age 17 or 43.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->exclude(array(
|
$players = Player::get()->exclude(array(
|
||||||
'FirstName' => array('Sam', 'Sig'),
|
'FirstName' => array('Sam', 'Sig'),
|
||||||
'Age' => array(17, 43)
|
'Age' => array(17, 43)
|
||||||
@ -420,49 +427,49 @@ And removing Sig and Sam with that are either age 17 or 43.
|
|||||||
|
|
||||||
// SELECT * FROM Player WHERE ("FirstName" NOT IN ('Sam','Sig) OR "Age" NOT IN ('17', '43'));
|
// SELECT * FROM Player WHERE ("FirstName" NOT IN ('Sam','Sig) OR "Age" NOT IN ('17', '43'));
|
||||||
|
|
||||||
You can use [SearchFilters](searchfilters) to add additional behavior to your `exclude` command.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->exclude(array(
|
$players = Player::get()->exclude(array(
|
||||||
'FirstName:EndsWith' => 'S'
|
'FirstName:EndsWith' => 'S'
|
||||||
'PlayerNumber:LessThanOrEqual' => '10'
|
'PlayerNumber:LessThanOrEqual' => '10'
|
||||||
));
|
));
|
||||||
|
|
||||||
### Subtract
|
```
|
||||||
|
|
||||||
You can subtract entries from a [api:DataList] by passing in another DataList to `subtract()`
|
You can subtract entries from a [api:DataList] by passing in another DataList to `subtract()`
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$sam = Player::get()->filter('FirstName', 'Sam');
|
$sam = Player::get()->filter('FirstName', 'Sam');
|
||||||
$players = Player::get();
|
$players = Player::get();
|
||||||
|
|
||||||
$noSams = $players->subtract($sam);
|
$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.
|
when you want to find all the members that does not exist in a Group.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// ... Finding all members that does not belong to $group.
|
// ... Finding all members that does not belong to $group.
|
||||||
$otherMembers = Member::get()->subtract($group->Members());
|
$otherMembers = Member::get()->subtract($group->Members());
|
||||||
|
|
||||||
### Limit
|
```
|
||||||
|
|
||||||
You can limit the amount of records returned in a DataList by using the `limit()` method.
|
You can limit the amount of records returned in a DataList by using the `limit()` method.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$members = Member::get()->limit(5);
|
$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
|
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.
|
offset, if not provided as an argument, will default to 0.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// Return 10 members with an offset of 4 (starting from the 5th result).
|
// Return 10 members with an offset of 4 (starting from the 5th result).
|
||||||
$members = Member::get()->sort('Surname')->limit(10, 4);
|
$members = Member::get()->sort('Surname')->limit(10, 4);
|
||||||
|
|
||||||
<div class="alert">
|
```
|
||||||
Note that the `limit` argument order is different from a MySQL LIMIT clause.
|
Note that the `limit` argument order is different from a MySQL LIMIT clause.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
### Raw SQL
|
### Raw SQL
|
||||||
|
|
||||||
@ -480,10 +487,10 @@ you need it to, you may also consider extending the ORM with new data types or f
|
|||||||
|
|
||||||
You can specify a WHERE clause fragment (that will be combined with other filters using AND) with the `where()` method:
|
You can specify a WHERE clause fragment (that will be combined with other filters using AND) with the `where()` method:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$members = Member::get()->where("\"FirstName\" = 'Sam'")
|
$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:
|
You can specify a join with the `innerJoin` and `leftJoin` methods. Both of these methods have the same arguments:
|
||||||
|
|
||||||
@ -491,7 +498,7 @@ You can specify a join with the `innerJoin` and `leftJoin` methods. Both of the
|
|||||||
* The filter clause for the join.
|
* The filter clause for the join.
|
||||||
* An optional alias.
|
* An optional alias.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// Without an alias
|
// Without an alias
|
||||||
$members = Member::get()
|
$members = Member::get()
|
||||||
->leftJoin("Group_Members", "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\"");
|
->leftJoin("Group_Members", "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\"");
|
||||||
@ -499,17 +506,17 @@ You can specify a join with the `innerJoin` and `leftJoin` methods. Both of the
|
|||||||
$members = Member::get()
|
$members = Member::get()
|
||||||
->innerJoin("Group_Members", "\"Rel\".\"MemberID\" = \"Member\".\"ID\"", "Rel");
|
->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
|
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.
|
**not** return the additionally joined data.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
### Default Values
|
### Default Values
|
||||||
|
|
||||||
Define the default values for all the `$db` fields. This example sets the "Status"-column on Player to "Active"
|
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.
|
whenever a new object is created.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -519,10 +526,10 @@ whenever a new object is created.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
```
|
||||||
Note: Alternatively you can set defaults directly in the database-schema (rather than the object-model). See
|
Note: Alternatively you can set defaults directly in the database-schema (rather than the object-model). See
|
||||||
[Data Types and Casting](/developer_guides/model/data_types_and_casting) for details.
|
[Data Types and Casting](/developer_guides/model/data_types_and_casting) for details.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Subclasses
|
## Subclasses
|
||||||
|
|
||||||
@ -533,7 +540,7 @@ time.
|
|||||||
|
|
||||||
For example, suppose we have the following set of classes:
|
For example, suppose we have the following set of classes:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page extends SiteTree {
|
class Page extends SiteTree {
|
||||||
@ -547,9 +554,9 @@ For example, suppose we have the following set of classes:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
The data for the following classes would be stored across the following tables:
|
```
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
SiteTree:
|
SiteTree:
|
||||||
- ID: Int
|
- ID: Int
|
||||||
- ClassName: Enum('SiteTree', 'Page', 'NewsPage')
|
- ClassName: Enum('SiteTree', 'Page', 'NewsPage')
|
||||||
@ -561,16 +568,16 @@ The data for the following classes would be stored across the following tables:
|
|||||||
- ID: Int
|
- ID: Int
|
||||||
- Summary: Text
|
- Summary: Text
|
||||||
|
|
||||||
Accessing the data is transparent to the developer.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$news = NewsPage::get();
|
$news = NewsPage::get();
|
||||||
|
|
||||||
foreach($news as $article) {
|
foreach($news as $article) {
|
||||||
echo $article->Title;
|
echo $article->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
|
* "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.
|
special fields. This is called the "base table". In our case, `SiteTree` is the base table.
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Relations between Records
|
title: Relations between Records
|
||||||
summary: Relate models together using the ORM using has_one, has_many, and many_many.
|
summary: Relate models together using the ORM using has_one, has_many, and many_many.
|
||||||
|
icon: link
|
||||||
|
---
|
||||||
# Relations between Records
|
# Relations between Records
|
||||||
|
|
||||||
In most situations you will likely see more than one [api:DataObject] and several classes in your data model may relate
|
In most situations you will likely see more than one [api:DataObject] and several classes in your data model may relate
|
||||||
@ -15,7 +17,7 @@ SilverStripe supports a number of relationship types and each relationship type
|
|||||||
A 1-to-1 relation creates a database-column called "`<relationship-name>`ID", in the example below this would be
|
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.
|
"TeamID" on the "Player"-table.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Team extends DataObject {
|
class Team extends DataObject {
|
||||||
@ -36,12 +38,12 @@ A 1-to-1 relation creates a database-column called "`<relationship-name>`ID", in
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
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()`)
|
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
|
```php
|
||||||
$player = Player::get()->byId(1);
|
$player = Player::get()->byId(1);
|
||||||
|
|
||||||
$team = $player->Team();
|
$team = $player->Team();
|
||||||
@ -50,16 +52,16 @@ At the database level, the `has_one` creates a `TeamID` field on `Player`. A `ha
|
|||||||
echo $player->Team()->Title;
|
echo $player->Team()->Title;
|
||||||
// returns the 'Title' column on the 'Team' or `getTitle` if it exists.
|
// returns the 'Title' column on the 'Team' or `getTitle` if it exists.
|
||||||
|
|
||||||
The relationship can also be navigated in [templates](../templates).
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% with $Player %>
|
<% with $Player %>
|
||||||
<% if $Team %>
|
<% if $Team %>
|
||||||
Plays for $Team.Title
|
Plays for $Team.Title
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
<% end_with %>
|
<% end_with %>
|
||||||
|
|
||||||
## Polymorphic has_one
|
```
|
||||||
|
|
||||||
A has_one can also be polymorphic, which allows any type of object to be associated.
|
A has_one can also be polymorphic, which allows any type of object to be associated.
|
||||||
This is useful where there could be many use cases for a particular data structure.
|
This is useful where there could be many use cases for a particular data structure.
|
||||||
@ -70,6 +72,7 @@ with the ID column identifies the object.
|
|||||||
To specify that a has_one relation is polymorphic set the type to 'DataObject'.
|
To specify that a has_one relation is polymorphic set the type to 'DataObject'.
|
||||||
Ideally, the associated has_many (or belongs_to) should be specified with dot notation.
|
Ideally, the associated has_many (or belongs_to) should be specified with dot notation.
|
||||||
|
|
||||||
|
```
|
||||||
::php
|
::php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -93,24 +96,24 @@ Ideally, the associated has_many (or belongs_to) should be specified with dot no
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
```
|
||||||
Note: The use of polymorphic relationships can affect query performance, especially
|
Note: The use of polymorphic relationships can affect query performance, especially
|
||||||
on joins, and also increases the complexity of the database and necessary user code.
|
on joins, and also increases the complexity of the database and necessary user code.
|
||||||
They should be used sparingly, and only where additional complexity would otherwise
|
They should be used sparingly, and only where additional complexity would otherwise
|
||||||
be necessary. E.g. Additional parent classes for each respective relationship, or
|
be necessary. E.g. Additional parent classes for each respective relationship, or
|
||||||
duplication of code.
|
duplication of code.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
## has_many
|
## has_many
|
||||||
|
|
||||||
Defines 1-to-many joins. As you can see from the previous example, `$has_many` goes hand in hand with `$has_one`.
|
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'>
|
[alert]
|
||||||
Please specify a $has_one-relationship on the related child-class as well, in order to have the necessary accessors
|
Please specify a $has_one-relationship on the related child-class as well, in order to have the necessary accessors
|
||||||
available on both ends.
|
available on both ends.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Team extends DataObject {
|
class Team extends DataObject {
|
||||||
@ -131,10 +134,10 @@ available on both ends.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
you will get an instance of [api:HasManyList] rather than the object.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$team = Team::get()->first();
|
$team = Team::get()->first();
|
||||||
|
|
||||||
echo $team->Players();
|
echo $team->Players();
|
||||||
@ -147,9 +150,9 @@ you will get an instance of [api:HasManyList] rather than the object.
|
|||||||
echo $player->FirstName;
|
echo $player->FirstName;
|
||||||
}
|
}
|
||||||
|
|
||||||
To specify multiple `$has_many` to the same object you can use dot notation to distinguish them like below:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Person extends DataObject {
|
class Person extends DataObject {
|
||||||
@ -168,13 +171,13 @@ To specify multiple `$has_many` to the same object you can use dot notation to d
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Multiple `$has_one` relationships are okay if they aren't linking to the same object type. Otherwise, they have to be
|
Multiple `$has_one` relationships are okay if they aren't linking to the same object type. Otherwise, they have to be
|
||||||
named.
|
named.
|
||||||
|
|
||||||
If you're using the default scaffolded form fields with multiple `has_one` relationships, you will end up with a CMS field for each relation. If you don't want these you can remove them by their IDs:
|
If you're using the default scaffolded form fields with multiple `has_one` relationships, you will end up with a CMS field for each relation. If you don't want these you can remove them by their IDs:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function getCMSFields()
|
public function getCMSFields()
|
||||||
{
|
{
|
||||||
$fields = parent::getCMSFields();
|
$fields = parent::getCMSFields();
|
||||||
@ -182,7 +185,7 @@ If you're using the default scaffolded form fields with multiple `has_one` relat
|
|||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
## belongs_to
|
## belongs_to
|
||||||
|
|
||||||
Defines a 1-to-1 relationship with another object, which declares the other end of the relationship with a
|
Defines a 1-to-1 relationship with another object, which declares the other end of the relationship with a
|
||||||
@ -193,7 +196,7 @@ declaring the `$belongs_to`.
|
|||||||
Similarly with `$has_many`, dot notation can be used to explicitly specify the `$has_one` which refers to this relation.
|
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.
|
This is not mandatory unless the relationship would be otherwise ambiguous.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Team extends DataObject {
|
class Team extends DataObject {
|
||||||
@ -210,17 +213,17 @@ This is not mandatory unless the relationship would be otherwise ambiguous.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
## many_many
|
## many_many
|
||||||
|
|
||||||
Defines many-to-many joins. A new table, (this-class)_(relationship-name), will be created with a pair of ID fields.
|
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'>
|
[warning]
|
||||||
Please specify a $belongs_many_many-relationship on the related class as well, in order to have the necessary accessors
|
Please specify a $belongs_many_many-relationship on the related class as well, in order to have the necessary accessors
|
||||||
available on both ends.
|
available on both ends.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Team extends DataObject {
|
class Team extends DataObject {
|
||||||
@ -237,28 +240,28 @@ available on both ends.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
you will get an instance of [api:ManyManyList] rather than the object.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$team = Team::get()->byId(1);
|
$team = Team::get()->byId(1);
|
||||||
|
|
||||||
$supporters = $team->Supporters();
|
$supporters = $team->Supporters();
|
||||||
// returns a 'ManyManyList' instance.
|
// returns a 'ManyManyList' instance.
|
||||||
|
|
||||||
|
```
|
||||||
The relationship can also be navigated in [templates](../templates).
|
The relationship can also be navigated in [templates](../templates).
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% with $Supporter %>
|
<% with $Supporter %>
|
||||||
<% loop $Supports %>
|
<% loop $Supports %>
|
||||||
Supports $Title
|
Supports $Title
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
<% end_with %>
|
<% end_with %>
|
||||||
|
|
||||||
To specify multiple $many_manys between the same classes, use the dot notation to distinguish them like below:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Category extends DataObject {
|
class Category extends DataObject {
|
||||||
@ -277,7 +280,7 @@ To specify multiple $many_manys between the same classes, use the dot notation t
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
## 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.
|
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.
|
||||||
|
|
||||||
@ -288,7 +291,7 @@ Adding new items to a relations works the same, regardless if you're editing a *
|
|||||||
encapsulated by [api:HasManyList] and [api:ManyManyList], both of which provide very similar APIs, e.g. an `add()`
|
encapsulated by [api:HasManyList] and [api:ManyManyList], both of which provide very similar APIs, e.g. an `add()`
|
||||||
and `remove()` method.
|
and `remove()` method.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$team = Team::get()->byId(1);
|
$team = Team::get()->byId(1);
|
||||||
|
|
||||||
// create a new supporter
|
// create a new supporter
|
||||||
@ -299,7 +302,7 @@ and `remove()` method.
|
|||||||
// add the supporter.
|
// add the supporter.
|
||||||
$team->Supporters()->add($supporter);
|
$team->Supporters()->add($supporter);
|
||||||
|
|
||||||
|
```
|
||||||
## Custom Relations
|
## Custom Relations
|
||||||
|
|
||||||
You can use the ORM to get a filtered result list without writing any SQL. For example, this snippet gets you the
|
You can use the ORM to get a filtered result list without writing any SQL. For example, this snippet gets you the
|
||||||
@ -307,7 +310,7 @@ You can use the ORM to get a filtered result list without writing any SQL. For e
|
|||||||
|
|
||||||
See [api:DataObject::$has_many] for more info on the described relations.
|
See [api:DataObject::$has_many] for more info on the described relations.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Team extends DataObject {
|
class Team extends DataObject {
|
||||||
@ -321,10 +324,10 @@ See [api:DataObject::$has_many] for more info on the described relations.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
Adding new records to a filtered `RelationList` like in the example above doesn't automatically set the filtered
|
Adding new records to a filtered `RelationList` like in the example above doesn't automatically set the filtered
|
||||||
criteria on the added record.
|
criteria on the added record.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Relations on Unsaved Objects
|
## Relations on Unsaved Objects
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Managing Lists
|
title: Managing Lists
|
||||||
summary: The SS_List interface allows you to iterate through and manipulate a list of objects.
|
summary: The SS_List interface allows you to iterate through and manipulate a list of objects.
|
||||||
|
icon: list
|
||||||
|
---
|
||||||
# Managing Lists
|
# Managing Lists
|
||||||
|
|
||||||
Whenever using the ORM to fetch records or navigate relationships you will receive an [api:SS_List] instance commonly as
|
Whenever using the ORM to fetch records or navigate relationships you will receive an [api:SS_List] instance commonly as
|
||||||
@ -11,23 +13,23 @@ modify.
|
|||||||
|
|
||||||
[api:SS_List] implements `IteratorAggregate`, allowing you to loop over the instance.
|
[api:SS_List] implements `IteratorAggregate`, allowing you to loop over the instance.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$members = Member::get();
|
$members = Member::get();
|
||||||
|
|
||||||
foreach($members as $member) {
|
foreach($members as $member) {
|
||||||
echo $member->Name;
|
echo $member->Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
Or in the template engine:
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% loop $Members %>
|
<% loop $Members %>
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
|
||||||
## Finding an item by value.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// $list->find($key, $value);
|
// $list->find($key, $value);
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -36,12 +38,12 @@ Or in the template engine:
|
|||||||
echo $members->find('ID', 4)->FirstName;
|
echo $members->find('ID', 4)->FirstName;
|
||||||
// returns 'Sam'
|
// returns 'Sam'
|
||||||
|
|
||||||
|
```
|
||||||
## Maps
|
## 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
|
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
|
```php
|
||||||
$members = Member::get()->map('ID', 'FirstName');
|
$members = Member::get()->map('ID', 'FirstName');
|
||||||
|
|
||||||
// $members = array(
|
// $members = array(
|
||||||
@ -50,15 +52,17 @@ A map is an array where the array indexes contain data as well as the values. Yo
|
|||||||
// 3 => 'Will'
|
// 3 => 'Will'
|
||||||
// );
|
// );
|
||||||
|
|
||||||
|
```
|
||||||
This functionality is provided by the [api:SS_Map] class, which can be used to build a map around any `SS_List`.
|
This functionality is provided by the [api:SS_Map] class, which can be used to build a map around any `SS_List`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$members = Member::get();
|
$members = Member::get();
|
||||||
$map = new SS_Map($members, 'ID', 'FirstName');
|
$map = new SS_Map($members, 'ID', 'FirstName');
|
||||||
|
|
||||||
|
```
|
||||||
## Column
|
## Column
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$members = Member::get();
|
$members = Member::get();
|
||||||
|
|
||||||
echo $members->column('Email');
|
echo $members->column('Email');
|
||||||
@ -69,11 +73,11 @@ This functionality is provided by the [api:SS_Map] class, which can be used to b
|
|||||||
// 'will@silverstripe.com'
|
// 'will@silverstripe.com'
|
||||||
// );
|
// );
|
||||||
|
|
||||||
## ArrayList
|
```
|
||||||
|
|
||||||
[api:ArrayList] exists to wrap a standard PHP array in the same API as a database backed list.
|
[api:ArrayList] exists to wrap a standard PHP array in the same API as a database backed list.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$sam = Member::get()->byId(5);
|
$sam = Member::get()->byId(5);
|
||||||
$sig = Member::get()->byId(6);
|
$sig = Member::get()->byId(6);
|
||||||
|
|
||||||
@ -84,7 +88,7 @@ This functionality is provided by the [api:SS_Map] class, which can be used to b
|
|||||||
echo $list->Count();
|
echo $list->Count();
|
||||||
// returns '2'
|
// returns '2'
|
||||||
|
|
||||||
|
```
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
|
||||||
* [api:SS_List]
|
* [api:SS_List]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Data Types, Overloading and Casting
|
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.
|
summary: Learn how how data is stored going in and coming out of the ORM and how to modify it.
|
||||||
|
icon: code
|
||||||
|
---
|
||||||
# Data Types and Casting
|
# Data Types and Casting
|
||||||
|
|
||||||
Each model in a SilverStripe [api:DataObject] will handle data at some point. This includes database columns such as
|
Each model in a SilverStripe [api:DataObject] will handle data at some point. This includes database columns such as
|
||||||
@ -13,7 +15,7 @@ In the `Player` example, we have four database columns each with a different dat
|
|||||||
|
|
||||||
**mysite/code/Player.php**
|
**mysite/code/Player.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -26,7 +28,7 @@ In the `Player` example, we have four database columns each with a different dat
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
## Available Types
|
```
|
||||||
|
|
||||||
* [api:Boolean]: A boolean field.
|
* [api:Boolean]: A boolean field.
|
||||||
* [api:Currency]: A number with 2 decimal points of precision, designed to store currency values.
|
* [api:Currency]: A number with 2 decimal points of precision, designed to store currency values.
|
||||||
@ -51,7 +53,7 @@ See the [API documentation](api:DBField) for a full list of available Data Types
|
|||||||
For complex default values for newly instantiated objects see [Dynamic Default Values](how_tos/dynamic_default_fields).
|
For complex default values for newly instantiated objects see [Dynamic Default Values](how_tos/dynamic_default_fields).
|
||||||
For simple values you can make use of the `$defaults` array. For example:
|
For simple values you can make use of the `$defaults` array. For example:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Car extends DataObject {
|
class Car extends DataObject {
|
||||||
@ -67,7 +69,7 @@ For simple values you can make use of the `$defaults` array. For example:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
### Default values for new database columns
|
```
|
||||||
|
|
||||||
When adding a new `$db` field to a DataObject you can specify a default value
|
When adding a new `$db` field to a DataObject you can specify a default value
|
||||||
to be applied to all existing records when the column is added in the database
|
to be applied to all existing records when the column is added in the database
|
||||||
@ -75,7 +77,7 @@ for the first time. This will also be applied to any newly created objects
|
|||||||
going forward. You do this be passing an argument for the default value in your
|
going forward. You do this be passing an argument for the default value in your
|
||||||
`$db` items. For example:
|
`$db` items. For example:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Car extends DataObject {
|
class Car extends DataObject {
|
||||||
@ -86,7 +88,7 @@ going forward. You do this be passing an argument for the default value in your
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
## Formatting Output
|
```
|
||||||
|
|
||||||
The Data Type does more than setup the correct database schema. They can also define methods and formatting helpers for
|
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.
|
output. You can manually create instances of a Data Type and pass it through to the template.
|
||||||
@ -96,7 +98,7 @@ object we can control the formatting and it allows us to call methods defined fr
|
|||||||
|
|
||||||
**mysite/code/Player.php**
|
**mysite/code/Player.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -108,9 +110,9 @@ object we can control the formatting and it allows us to call methods defined fr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
```php
|
||||||
$player = Player::get()->byId(1);
|
$player = Player::get()->byId(1);
|
||||||
|
|
||||||
echo $player->Name;
|
echo $player->Name;
|
||||||
@ -122,11 +124,11 @@ Then we can refer to a new `Name` column on our `Player` instances. In templates
|
|||||||
echo $player->getName()->LimitCharacters(2);
|
echo $player->getName()->LimitCharacters(2);
|
||||||
// returns "Sa.."
|
// returns "Sa.."
|
||||||
|
|
||||||
## Casting
|
```
|
||||||
|
|
||||||
Rather than manually returning objects from your custom functions. You can use the `$casting` property.
|
Rather than manually returning objects from your custom functions. You can use the `$casting` property.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -140,28 +142,28 @@ Rather than manually returning objects from your custom functions. You can use t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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]
|
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.
|
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
|
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.
|
number.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
DBField::create_field('Double', 1.23456)->Round(2); // results in 1.23
|
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:
|
sophisticated helpers, like showing the time difference to the current date:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
DBField::create_field('Date', '1982-01-01')->TimeDiff(); // shows "30 years ago"
|
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
|
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:
|
context. Through a `$casting` array, arbitrary properties and getters can be casted:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyObject extends ViewableData {
|
class MyObject extends ViewableData {
|
||||||
@ -181,7 +183,7 @@ context. Through a `$casting` array, arbitrary properties and getters can be cas
|
|||||||
$obj->obj('MyDate'); // returns object
|
$obj->obj('MyDate'); // returns object
|
||||||
$obj->obj('MyDate')->InPast(); // returns boolean
|
$obj->obj('MyDate')->InPast(); // returns boolean
|
||||||
|
|
||||||
|
```
|
||||||
## Casting HTML Text
|
## Casting HTML Text
|
||||||
|
|
||||||
The database field types [api:HTMLVarchar]/[api:HTMLText] and [api:Varchar]/[api:Text] are exactly the same in
|
The database field types [api:HTMLVarchar]/[api:HTMLText] and [api:Varchar]/[api:Text] are exactly the same in
|
||||||
@ -189,10 +191,10 @@ the database. However, the template engine knows to escape fields without the `
|
|||||||
to prevent them from rendering HTML interpreted by browsers. This escaping prevents attacks like CSRF or XSS (see
|
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.
|
"[security](../security)"), which is important if these fields store user-provided data.
|
||||||
|
|
||||||
<div class="hint" markdown="1">
|
[hint]
|
||||||
You can disable this auto-escaping by using the `$MyField.RAW` escaping hints, or explicitly request escaping of HTML
|
You can disable this auto-escaping by using the `$MyField.RAW` escaping hints, or explicitly request escaping of HTML
|
||||||
content via `$MyHtmlField.XML`.
|
content via `$MyHtmlField.XML`.
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
## Overloading
|
## Overloading
|
||||||
|
|
||||||
@ -203,7 +205,7 @@ We can overload the default behavior by making a function called "get`<fieldname
|
|||||||
The following example will use the result of `getStatus` instead of the 'Status' database column. We can refer to the
|
The following example will use the result of `getStatus` instead of the 'Status' database column. We can refer to the
|
||||||
database column using `dbObject`.
|
database column using `dbObject`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -216,7 +218,7 @@ database column using `dbObject`.
|
|||||||
return (!$this->obj("Birthday")->InPast()) ? "Unborn" : $this->dbObject('Status')->Value();
|
return (!$this->obj("Birthday")->InPast()) ? "Unborn" : $this->dbObject('Status')->Value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
|
||||||
* [api:DataObject]
|
* [api:DataObject]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: Extending DataObjects
|
title: Extending DataObjects
|
||||||
summary: Modify the data model without using subclasses.
|
summary: Modify the data model without using subclasses.
|
||||||
|
---
|
||||||
# Extending DataObjects
|
# Extending DataObjects
|
||||||
|
|
||||||
You can add properties and methods to existing [api:DataObject]s like [api:Member] without hacking core code or sub
|
You can add properties and methods to existing [api:DataObject]s like [api:Member] without hacking core code or sub
|
||||||
@ -18,7 +19,7 @@ a `ModelAdmin` record.
|
|||||||
|
|
||||||
Example: Disallow creation of new players if the currently logged-in player is not a team-manager.
|
Example: Disallow creation of new players if the currently logged-in player is not a team-manager.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -50,14 +51,14 @@ Example: Disallow creation of new players if the currently logged-in player is n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## onBeforeDelete
|
```
|
||||||
|
|
||||||
Triggered before executing *delete()* on an existing object.
|
Triggered before executing *delete()* on an existing object.
|
||||||
|
|
||||||
Example: Checking for a specific [permission](permissions) to delete this type of object. It checks if a
|
Example: Checking for a specific [permission](permissions) to delete this type of object. It checks if a
|
||||||
member is logged in who belongs to a group containing the permission "PLAYER_DELETE".
|
member is logged in who belongs to a group containing the permission "PLAYER_DELETE".
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -76,9 +77,9 @@ member is logged in who belongs to a group containing the permission "PLAYER_DEL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
[notice]
|
||||||
<div class="notice" markdown='1'>
|
|
||||||
Note: There are no separate methods for *onBeforeCreate* and *onBeforeUpdate*. Please check `$this->isInDb()` to toggle
|
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.
|
these two modes, as shown in the example above.
|
||||||
</div>
|
[/notice]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: SearchFilter Modifiers
|
title: SearchFilter Modifiers
|
||||||
summary: Use suffixes on your ORM queries.
|
summary: Use suffixes on your ORM queries.
|
||||||
|
icon: search
|
||||||
|
---
|
||||||
# SearchFilter Modifiers
|
# SearchFilter Modifiers
|
||||||
|
|
||||||
The `filter` and `exclude` operations specify exact matches by default. However, there are a number of suffixes that
|
The `filter` and `exclude` operations specify exact matches by default. However, there are a number of suffixes that
|
||||||
@ -16,7 +18,7 @@ you can put on field names to change this behavior. These are represented as `Se
|
|||||||
|
|
||||||
An example of a `SearchFilter` in use:
|
An example of a `SearchFilter` in use:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// fetch any player that starts with a S
|
// fetch any player that starts with a S
|
||||||
$players = Player::get()->filter(array(
|
$players = Player::get()->filter(array(
|
||||||
'FirstName:StartsWith' => 'S',
|
'FirstName:StartsWith' => 'S',
|
||||||
@ -29,7 +31,7 @@ An example of a `SearchFilter` in use:
|
|||||||
'LastName: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
|
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
|
`":case"`. These negate the filter, make it case-insensitive and make it case-sensitive, respectively. The default
|
||||||
@ -38,7 +40,7 @@ case-sensitive.
|
|||||||
|
|
||||||
The following is a query which will return everyone whose first name starts with "S", either lowercase or uppercase:
|
The following is a query which will return everyone whose first name starts with "S", either lowercase or uppercase:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$players = Player::get()->filter(array(
|
$players = Player::get()->filter(array(
|
||||||
'FirstName:StartsWith:nocase' => 'S'
|
'FirstName:StartsWith:nocase' => 'S'
|
||||||
));
|
));
|
||||||
@ -48,6 +50,6 @@ The following is a query which will return everyone whose first name starts with
|
|||||||
'FirstName:StartsWith:not' => 'W'
|
'FirstName:StartsWith:not' => 'W'
|
||||||
));
|
));
|
||||||
|
|
||||||
## API Documentation
|
```
|
||||||
|
|
||||||
* [api:SearchFilter]
|
* [api:SearchFilter]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Model-Level Permissions
|
title: Model-Level Permissions
|
||||||
summary: Reduce risk by securing models.
|
summary: Reduce risk by securing models.
|
||||||
|
icon: lock
|
||||||
|
---
|
||||||
# Model-Level Permissions
|
# Model-Level Permissions
|
||||||
|
|
||||||
Models can be modified in a variety of controllers and user interfaces, all of which can implement their own security
|
Models can be modified in a variety of controllers and user interfaces, all of which can implement their own security
|
||||||
@ -11,12 +13,12 @@ The API provides four methods for this purpose: `canEdit()`, `canCreate()`, `can
|
|||||||
Since they're PHP methods, they can contain arbitrary logic matching your own requirements. They can optionally receive
|
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()`).
|
a `$member` argument, and default to the currently logged in member (through `Member::currentUser()`).
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
By default, all `DataObject` subclasses can only be edited, created and viewed by users with the 'ADMIN' permission
|
By default, all `DataObject` subclasses can only be edited, created and viewed by users with the 'ADMIN' permission
|
||||||
code.
|
code.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyDataObject extends DataObject {
|
class MyDataObject extends DataObject {
|
||||||
@ -38,11 +40,11 @@ code.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<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
|
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
|
checked in the invoking code. The CMS default sections as well as custom interfaces like [api:ModelAdmin] or
|
||||||
[api:GridField] already enforce these permissions.
|
[api:GridField] already enforce these permissions.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: SQL Queries
|
title: SQL Queries
|
||||||
summary: Write and modify direct database queries through SQLExpression subclasses.
|
summary: Write and modify direct database queries through SQLExpression subclasses.
|
||||||
|
icon: database
|
||||||
|
---
|
||||||
# SQLSelect
|
# SQLSelect
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
@ -18,7 +20,7 @@ such as counts or returning a single column.
|
|||||||
For example, if you want to run a simple `COUNT` SQL statement,
|
For example, if you want to run a simple `COUNT` SQL statement,
|
||||||
the following three statements are functionally equivalent:
|
the following three statements are functionally equivalent:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// Through raw SQL.
|
// Through raw SQL.
|
||||||
$count = DB::query('SELECT COUNT(*) FROM "Member"')->value();
|
$count = DB::query('SELECT COUNT(*) FROM "Member"')->value();
|
||||||
|
|
||||||
@ -29,7 +31,7 @@ the following three statements are functionally equivalent:
|
|||||||
// Through the ORM.
|
// Through the ORM.
|
||||||
$count = Member::get()->count();
|
$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:
|
various assumptions the ORM and code based on it have:
|
||||||
|
|
||||||
* Custom getters/setters (object property can differ from database column)
|
* Custom getters/setters (object property can differ from database column)
|
||||||
@ -41,10 +43,10 @@ various assumptions the ORM and code based on it have:
|
|||||||
We'll explain some ways to use *SELECT* with the full power of SQL,
|
We'll explain some ways to use *SELECT* with the full power of SQL,
|
||||||
but still maintain a connection to the ORM where possible.
|
but still maintain a connection to the ORM where possible.
|
||||||
|
|
||||||
<div class="warning" markdown="1">
|
[warning]
|
||||||
Please read our [security topic](/developer_guides/security) to find out
|
Please read our [security topic](/developer_guides/security) to find out
|
||||||
how to properly prepare user input and variables for use in queries
|
how to properly prepare user input and variables for use in queries
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -56,7 +58,7 @@ conditional filters, grouping, limiting, and sorting.
|
|||||||
|
|
||||||
E.g.
|
E.g.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
$sqlQuery = new SQLSelect();
|
$sqlQuery = new SQLSelect();
|
||||||
@ -82,7 +84,7 @@ E.g.
|
|||||||
echo $row['BirthYear'];
|
echo $row['BirthYear'];
|
||||||
}
|
}
|
||||||
|
|
||||||
The result of `SQLSelect::execute()` is an array lightly wrapped in a database-specific subclass of [api:SS_Query].
|
```
|
||||||
This class implements the *Iterator*-interface, and provides convenience-methods for accessing the data.
|
This class implements the *Iterator*-interface, and provides convenience-methods for accessing the data.
|
||||||
|
|
||||||
### DELETE
|
### DELETE
|
||||||
@ -93,7 +95,7 @@ object instead.
|
|||||||
|
|
||||||
For example, creating a `SQLDelete` object
|
For example, creating a `SQLDelete` object
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
$query = SQLDelete::create()
|
$query = SQLDelete::create()
|
||||||
@ -101,9 +103,9 @@ For example, creating a `SQLDelete` object
|
|||||||
->setWhere(array('"SiteTree"."ShowInMenus"' => 0));
|
->setWhere(array('"SiteTree"."ShowInMenus"' => 0));
|
||||||
$query->execute();
|
$query->execute();
|
||||||
|
|
||||||
Alternatively, turning an existing `SQLQuery` into a delete
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
$query = SQLQuery::create()
|
$query = SQLQuery::create()
|
||||||
@ -112,14 +114,14 @@ Alternatively, turning an existing `SQLQuery` into a delete
|
|||||||
->toDelete();
|
->toDelete();
|
||||||
$query->execute();
|
$query->execute();
|
||||||
|
|
||||||
Directly querying the database
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
DB::prepared_query('DELETE FROM "SiteTree" WHERE "SiteTree"."ShowInMenus" = ?', array(0));
|
DB::prepared_query('DELETE FROM "SiteTree" WHERE "SiteTree"."ShowInMenus" = ?', array(0));
|
||||||
|
|
||||||
### INSERT/UPDATE
|
```
|
||||||
|
|
||||||
INSERT and UPDATE can be performed using the `SQLInsert` and `SQLUpdate` classes.
|
INSERT and UPDATE can be performed using the `SQLInsert` and `SQLUpdate` classes.
|
||||||
These both have similar aspects in that they can modify content in
|
These both have similar aspects in that they can modify content in
|
||||||
@ -163,7 +165,7 @@ SQLInsert also includes the following api methods:
|
|||||||
|
|
||||||
E.g.
|
E.g.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
$update = SQLUpdate::create('"SiteTree"')->addWhere(array('ID' => 3));
|
$update = SQLUpdate::create('"SiteTree"')->addWhere(array('ID' => 3));
|
||||||
|
|
||||||
@ -189,13 +191,13 @@ E.g.
|
|||||||
// Perform the update
|
// Perform the update
|
||||||
$update->execute();
|
$update->execute();
|
||||||
|
|
||||||
In addition to assigning values, the SQLInsert object also supports multi-row
|
```
|
||||||
inserts. For database connectors and API that don't have multi-row insert support
|
inserts. For database connectors and API that don't have multi-row insert support
|
||||||
these are translated internally as multiple single row inserts.
|
these are translated internally as multiple single row inserts.
|
||||||
|
|
||||||
For example,
|
For example,
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
$insert = SQLInsert::create('"SiteTree"');
|
$insert = SQLInsert::create('"SiteTree"');
|
||||||
|
|
||||||
@ -217,14 +219,14 @@ For example,
|
|||||||
|
|
||||||
$insert->execute();
|
$insert->execute();
|
||||||
|
|
||||||
### Value Checks
|
```
|
||||||
|
|
||||||
Raw SQL is handy for performance-optimized calls,
|
Raw SQL is handy for performance-optimized calls,
|
||||||
e.g. when you want a single column rather than a full-blown object representation.
|
e.g. when you want a single column rather than a full-blown object representation.
|
||||||
|
|
||||||
Example: Get the count from a relationship.
|
Example: Get the count from a relationship.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$sqlQuery = new SQLQuery();
|
$sqlQuery = new SQLQuery();
|
||||||
$sqlQuery->setFrom('Player');
|
$sqlQuery->setFrom('Player');
|
||||||
$sqlQuery->addSelect('COUNT("Player"."ID")');
|
$sqlQuery->addSelect('COUNT("Player"."ID")');
|
||||||
@ -232,19 +234,19 @@ Example: Get the count from a relationship.
|
|||||||
$sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"');
|
$sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"');
|
||||||
$count = $sqlQuery->execute()->value();
|
$count = $sqlQuery->execute()->value();
|
||||||
|
|
||||||
Note that in the ORM, this call would be executed in an efficient manner as well:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$count = $myTeam->Players()->count();
|
$count = $myTeam->Players()->count();
|
||||||
|
|
||||||
### Mapping
|
```
|
||||||
|
|
||||||
Creates a map based on the first two columns of the query result.
|
Creates a map based on the first two columns of the query result.
|
||||||
This can be useful for creating dropdowns.
|
This can be useful for creating dropdowns.
|
||||||
|
|
||||||
Example: Show player names with their birth year, but set their birth dates as values.
|
Example: Show player names with their birth year, but set their birth dates as values.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$sqlQuery = new SQLSelect();
|
$sqlQuery = new SQLSelect();
|
||||||
$sqlQuery->setFrom('Player');
|
$sqlQuery->setFrom('Player');
|
||||||
$sqlQuery->setSelect('Birthdate');
|
$sqlQuery->setSelect('Birthdate');
|
||||||
@ -252,11 +254,11 @@ Example: Show player names with their birth year, but set their birth dates as v
|
|||||||
$map = $sqlQuery->execute()->map();
|
$map = $sqlQuery->execute()->map();
|
||||||
$field = new DropdownField('Birthdates', 'Birthdates', $map);
|
$field = new DropdownField('Birthdates', 'Birthdates', $map);
|
||||||
|
|
||||||
Note that going through SQLSelect is just necessary here
|
```
|
||||||
because of the custom SQL value transformation (`YEAR()`).
|
because of the custom SQL value transformation (`YEAR()`).
|
||||||
An alternative approach would be a custom getter in the object definition.
|
An alternative approach would be a custom getter in the object definition.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
'Name' => 'Varchar',
|
'Name' => 'Varchar',
|
||||||
@ -269,7 +271,7 @@ An alternative approach would be a custom getter in the object definition.
|
|||||||
$players = Player::get();
|
$players = Player::get();
|
||||||
$map = $players->map('Name', 'NameWithBirthyear');
|
$map = $players->map('Name', 'NameWithBirthyear');
|
||||||
|
|
||||||
## Related
|
```
|
||||||
|
|
||||||
* [Introduction to the Data Model and ORM](data_model_and_orm)
|
* [Introduction to the Data Model and ORM](data_model_and_orm)
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Model Validation and Constraints
|
title: Model Validation and Constraints
|
||||||
summary: Validate your data at the model level
|
summary: Validate your data at the model level
|
||||||
|
icon: check-square
|
||||||
|
---
|
||||||
# Validation and Constraints
|
# Validation and Constraints
|
||||||
|
|
||||||
Traditionally, validation in SilverStripe has been mostly handled on the controller through [form validation](../forms).
|
Traditionally, validation in SilverStripe has been mostly handled on the controller through [form validation](../forms).
|
||||||
@ -21,7 +23,7 @@ write, and respond appropriately if it isn't.
|
|||||||
|
|
||||||
The return value of `validate()` is a [api:ValidationResult] object.
|
The return value of `validate()` is a [api:ValidationResult] object.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyObject extends DataObject {
|
class MyObject extends DataObject {
|
||||||
@ -42,7 +44,7 @@ The return value of `validate()` is a [api:ValidationResult] object.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## API Documentation
|
```
|
||||||
|
|
||||||
* [api:DataObject]
|
* [api:DataObject]
|
||||||
* [api:ValidationResult];
|
* [api:ValidationResult];
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: Versioning
|
title: Versioning
|
||||||
summary: Add versioning to your database content through the Versioned extension.
|
summary: Add versioning to your database content through the Versioned extension.
|
||||||
|
---
|
||||||
# Versioning
|
# Versioning
|
||||||
|
|
||||||
Database content in SilverStripe can be "staged" before its publication, as well as track all changes through the
|
Database content in SilverStripe can be "staged" before its publication, as well as track all changes through the
|
||||||
@ -17,30 +18,30 @@ Adding Versioned to your `DataObject` subclass works the same as any other exten
|
|||||||
denoting the different "stages", which map to different database tables.
|
denoting the different "stages", which map to different database tables.
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
:::yml
|
```yml
|
||||||
MyRecord:
|
MyRecord:
|
||||||
extensions:
|
extensions:
|
||||||
- Versioned("Stage","Live")
|
- Versioned("Stage","Live")
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
The extension is automatically applied to `SiteTree` class. For more information on extensions see
|
The extension is automatically applied to `SiteTree` class. For more information on extensions see
|
||||||
[Extending](../extending) and the [Configuration](../configuration) documentation.
|
[Extending](../extending) and the [Configuration](../configuration) documentation.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
<div class="warning" markdown="1">
|
[warning]
|
||||||
Versioning only works if you are adding the extension to the base class. That is, the first subclass
|
Versioning only works if you are adding the extension to the base class. That is, the first subclass
|
||||||
of `DataObject`. Adding this extension to children of the base class will have unpredictable behaviour.
|
of `DataObject`. Adding this extension to children of the base class will have unpredictable behaviour.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
## Database Structure
|
## Database Structure
|
||||||
|
|
||||||
Depending on how many stages you configured, two or more new tables will be created for your records. In the above, this
|
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 `MyRecord_Live` table once you've rebuilt the database.
|
will create a new `MyRecord_Live` table once you've rebuilt the database.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
Note that the "Stage" naming has a special meaning here, it will leave the original table name unchanged, rather than
|
Note that the "Stage" naming has a special meaning here, it will leave the original table name unchanged, rather than
|
||||||
adding a suffix.
|
adding a suffix.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
* `MyRecord` table: Contains staged data
|
* `MyRecord` table: Contains staged data
|
||||||
* `MyRecord_Live` table: Contains live data
|
* `MyRecord_Live` table: Contains live data
|
||||||
@ -60,7 +61,7 @@ automatically joined as required:
|
|||||||
By default, all records are retrieved from the "Draft" stage (so the `MyRecord` table in our example). You can
|
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.
|
explicitly request a certain stage through various getters on the `Versioned` class.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// Fetching multiple records
|
// Fetching multiple records
|
||||||
$stageRecords = Versioned::get_by_stage('MyRecord', 'Stage');
|
$stageRecords = Versioned::get_by_stage('MyRecord', 'Stage');
|
||||||
$liveRecords = Versioned::get_by_stage('MyRecord', 'Live');
|
$liveRecords = Versioned::get_by_stage('MyRecord', 'Live');
|
||||||
@ -69,29 +70,29 @@ explicitly request a certain stage through various getters on the `Versioned` cl
|
|||||||
$stageRecord = Versioned::get_by_stage('MyRecord', 'Stage')->byID(99);
|
$stageRecord = Versioned::get_by_stage('MyRecord', 'Stage')->byID(99);
|
||||||
$liveRecord = Versioned::get_by_stage('MyRecord', 'Live')->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
|
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.
|
in the `<class>_versions` tables.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$historicalRecord = Versioned::get_version('MyRecord', <record-id>, <version-id>);
|
$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,
|
The record is retrieved as a `DataObject`, but saving back modifications via `write()` will create a new version,
|
||||||
rather than modifying the existing one.
|
rather than modifying the existing one.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
In order to get a list of all versions for a specific record, we need to generate specialized [api:Versioned_Version]
|
In order to get a list of all versions for a specific record, we need to generate specialized [api:Versioned_Version]
|
||||||
objects, which expose the same database information as a `DataObject`, but also include information about when and how
|
objects, which expose the same database information as a `DataObject`, but also include information about when and how
|
||||||
a record was published.
|
a record was published.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$record = MyRecord::get()->byID(99); // stage doesn't matter here
|
$record = MyRecord::get()->byID(99); // stage doesn't matter here
|
||||||
$versions = $record->allVersions();
|
$versions = $record->allVersions();
|
||||||
echo $versions->First()->Version; // instance of Versioned_Version
|
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
|
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
|
`Versioned::current_stage()` global setting. Each call will automatically create a new version in the
|
||||||
@ -101,7 +102,7 @@ To move a saved version from one stage to another, call [writeToStage(<stage>)](
|
|||||||
object. The process of moving a version to a different stage is also called "publishing", so we've created a shortcut
|
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>)`.
|
for this: `publish(<from-stage>, <to-stage>)`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$record = Versioned::get_by_stage('MyRecord', 'Stage')->byID(99);
|
$record = Versioned::get_by_stage('MyRecord', 'Stage')->byID(99);
|
||||||
$record->MyField = 'changed';
|
$record->MyField = 'changed';
|
||||||
// will update `MyRecord` table (assuming Versioned::current_stage() == 'Stage'),
|
// will update `MyRecord` table (assuming Versioned::current_stage() == 'Stage'),
|
||||||
@ -110,26 +111,26 @@ for this: `publish(<from-stage>, <to-stage>)`.
|
|||||||
// will copy the saved record information to the `MyRecord_Live` table
|
// will copy the saved record information to the `MyRecord_Live` table
|
||||||
$record->publish('Stage', 'Live');
|
$record->publish('Stage', 'Live');
|
||||||
|
|
||||||
Similarly, an "unpublish" operation does the reverse, and removes a record from a specific stage.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$record = MyRecord::get()->byID(99); // stage doesn't matter here
|
$record = MyRecord::get()->byID(99); // stage doesn't matter here
|
||||||
// will remove the row from the `MyRecord_Live` table
|
// will remove the row from the `MyRecord_Live` table
|
||||||
$record->deleteFromStage('Live');
|
$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
|
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.
|
is initialized. But it can also be set and reset temporarily to force a specific operation to run on a certain stage.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$origMode = Versioned::get_reading_mode(); // save current mode
|
$origMode = Versioned::get_reading_mode(); // save current mode
|
||||||
$obj = MyRecord::getComplexObjectRetrieval(); // returns 'Live' records
|
$obj = MyRecord::getComplexObjectRetrieval(); // returns 'Live' records
|
||||||
Versioned::set_reading_mode('Stage'); // temporarily overwrite mode
|
Versioned::set_reading_mode('Stage'); // temporarily overwrite mode
|
||||||
$obj = MyRecord::getComplexObjectRetrieval(); // returns 'Stage' records
|
$obj = MyRecord::getComplexObjectRetrieval(); // returns 'Stage' records
|
||||||
Versioned::set_reading_mode($origMode); // reset current mode
|
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
|
We generally discourage writing `Versioned` queries from scratch, due to the complexities involved through joining
|
||||||
multiple tables across an inherited table scheme (see [api:Versioned::augmentSQL()]). If possible, try to stick to
|
multiple tables across an inherited table scheme (see [api:Versioned::augmentSQL()]). If possible, try to stick to
|
||||||
@ -137,20 +138,20 @@ smaller modifications of the generated `DataList` objects.
|
|||||||
|
|
||||||
Example: Get the first 10 live records, filtered by creation date:
|
Example: Get the first 10 live records, filtered by creation date:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$records = Versioned::get_by_stage('MyRecord', 'Live')->limit(10)->sort('Created', 'ASC');
|
$records = Versioned::get_by_stage('MyRecord', 'Live')->limit(10)->sort('Created', 'ASC');
|
||||||
|
|
||||||
### Permissions
|
```
|
||||||
|
|
||||||
By default, `Versioned` will come out of the box with security extensions which restrict
|
By default, `Versioned` will come out of the box with security extensions which restrict
|
||||||
the visibility of objects in Draft (stage) or Archive viewing mode.
|
the visibility of objects in Draft (stage) or Archive viewing mode.
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
As is standard practice, user code should always invoke `canView()` on any object before
|
As is standard practice, user code should always invoke `canView()` on any object before
|
||||||
rendering it. DataLists do not filter on `canView()` automatically, so this must be
|
rendering it. DataLists do not filter on `canView()` automatically, so this must be
|
||||||
done via user code. This be be achieved either by wrapping `<% if $canView %>` in
|
done via user code. This be be achieved either by wrapping `<% if $canView %>` in
|
||||||
your template, or by implementing your visibility check in PHP.
|
your template, or by implementing your visibility check in PHP.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
Versioned object visibility can be customised in one of the following ways by editing your user code:
|
Versioned object visibility can be customised in one of the following ways by editing your user code:
|
||||||
|
|
||||||
@ -160,7 +161,7 @@ Versioned object visibility can be customised in one of the following ways by ed
|
|||||||
|
|
||||||
E.g.
|
E.g.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyObject extends DataObject {
|
class MyObject extends DataObject {
|
||||||
private static $extensions = array(
|
private static $extensions = array(
|
||||||
'Versioned'
|
'Versioned'
|
||||||
@ -179,7 +180,7 @@ E.g.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
If you want to control permissions of an object in an extension, you can also use
|
```
|
||||||
one of the below extension points in your `DataExtension` subclass:
|
one of the below extension points in your `DataExtension` subclass:
|
||||||
|
|
||||||
* `canView` to update the visibility of the object's `canView`
|
* `canView` to update the visibility of the object's `canView`
|
||||||
@ -190,19 +191,19 @@ only be invoked if the object is in a non-published state.
|
|||||||
|
|
||||||
E.g.
|
E.g.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyObjectExtension extends DataExtension {
|
class MyObjectExtension extends DataExtension {
|
||||||
public function canViewNonLive($member = null) {
|
public function canViewNonLive($member = null) {
|
||||||
return Permission::check($member, 'DRAFT_STATUS');
|
return Permission::check($member, 'DRAFT_STATUS');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
If none of the above checks are overridden, visibility will be determined by the
|
```
|
||||||
permissions in the `TargetObject.non_live_permissions` config.
|
permissions in the `TargetObject.non_live_permissions` config.
|
||||||
|
|
||||||
E.g.
|
E.g.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyObject extends DataObject {
|
class MyObject extends DataObject {
|
||||||
private static $extensions = array(
|
private static $extensions = array(
|
||||||
'Versioned'
|
'Versioned'
|
||||||
@ -210,7 +211,7 @@ E.g.
|
|||||||
private static $non_live_permissions = array('ADMIN');
|
private static $non_live_permissions = array('ADMIN');
|
||||||
}
|
}
|
||||||
|
|
||||||
Versioned applies no additional permissions to `canEdit` or `canCreate`, and such
|
```
|
||||||
these permissions should be implemented as per standard unversioned DataObjects.
|
these permissions should be implemented as per standard unversioned DataObjects.
|
||||||
|
|
||||||
### Page Specific Operations
|
### Page Specific Operations
|
||||||
@ -225,13 +226,13 @@ default, and only preview draft content if explicitly requested (e.g. by the "pr
|
|||||||
to force a specific stage, we recommend the `Controller->init()` method for this purpose, for example:
|
to force a specific stage, we recommend the `Controller->init()` method for this purpose, for example:
|
||||||
|
|
||||||
**mysite/code/MyController.php**
|
**mysite/code/MyController.php**
|
||||||
:::php
|
```php
|
||||||
public function init() {
|
public function init() {
|
||||||
parent::init();
|
parent::init();
|
||||||
Versioned::set_reading_mode('Stage.Stage');
|
Versioned::set_reading_mode('Stage.Stage');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
### Controllers
|
### Controllers
|
||||||
|
|
||||||
The current stage for each request is determined by `VersionedRequestFilter` before any controllers initialize, through
|
The current stage for each request is determined by `VersionedRequestFilter` before any controllers initialize, through
|
||||||
@ -239,11 +240,11 @@ The current stage for each request is determined by `VersionedRequestFilter` bef
|
|||||||
`?stage=Stage` to your request. The setting is "sticky" in the PHP session, so any subsequent requests will also be in
|
`?stage=Stage` to your request. The setting is "sticky" in the PHP session, so any subsequent requests will also be in
|
||||||
draft stage.
|
draft stage.
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
The `choose_site_stage()` call only deals with setting the default stage, and doesn't check if the user is
|
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
|
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.
|
permissions, and avoid exposing unpublished content to your users.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Building Model and Search Interfaces around Scaffolding
|
title: Building Model and Search Interfaces around Scaffolding
|
||||||
summary: A Model-driven approach to defining your application UI.
|
summary: A Model-driven approach to defining your application UI.
|
||||||
|
icon: hammer
|
||||||
|
---
|
||||||
# Scaffolding
|
# Scaffolding
|
||||||
|
|
||||||
The ORM already has a lot of information about the data represented by a `DataObject` through its `$db` property, so
|
The ORM already has a lot of information about the data represented by a `DataObject` through its `$db` property, so
|
||||||
@ -12,7 +14,7 @@ customise those fields as required.
|
|||||||
|
|
||||||
An example is `DataObject`, SilverStripe will automatically create your CMS interface so you can modify what you need.
|
An example is `DataObject`, SilverStripe will automatically create your CMS interface so you can modify what you need.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyDataObject extends DataObject {
|
class MyDataObject extends DataObject {
|
||||||
@ -32,9 +34,9 @@ An example is `DataObject`, SilverStripe will automatically create your CMS inte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
To fully customise your form fields, start with an empty FieldList.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
public function getCMSFields() {
|
public function getCMSFields() {
|
||||||
@ -50,7 +52,7 @@ To fully customise your form fields, start with an empty FieldList.
|
|||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
You can also alter the fields of built-in and module `DataObject` classes through your own
|
You can also alter the fields of built-in and module `DataObject` classes through your own
|
||||||
[DataExtension](/developer_guides/extending/extensions), and a call to `DataExtension->updateCMSFields`.
|
[DataExtension](/developer_guides/extending/extensions), and a call to `DataExtension->updateCMSFields`.
|
||||||
@ -60,7 +62,7 @@ You can also alter the fields of built-in and module `DataObject` classes throug
|
|||||||
The `$searchable_fields` property uses a mixed array format that can be used to further customise your generated admin
|
The `$searchable_fields` property uses a mixed array format that can be used to further customise your generated admin
|
||||||
system. The default is a set of array values listing the fields.
|
system. The default is a set of array values listing the fields.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyDataObject extends DataObject {
|
class MyDataObject extends DataObject {
|
||||||
@ -71,12 +73,12 @@ system. The default is a set of array values listing the fields.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Searchable fields will be appear in the search interface with a default form field (usually a [api:TextField]) and a
|
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
|
default search filter assigned (usually an [api:ExactMatchFilter]). To override these defaults, you can specify
|
||||||
additional information on `$searchable_fields`:
|
additional information on `$searchable_fields`:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyDataObject extends DataObject {
|
class MyDataObject extends DataObject {
|
||||||
@ -87,10 +89,10 @@ additional information on `$searchable_fields`:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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:
|
both, you can assign an array:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyDataObject extends DataObject {
|
class MyDataObject extends DataObject {
|
||||||
@ -108,10 +110,10 @@ both, you can assign an array:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
To include relations (`$has_one`, `$has_many` and `$many_many`) in your search, you can use a dot-notation.
|
To include relations (`$has_one`, `$has_many` and `$many_many`) in your search, you can use a dot-notation.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Team extends DataObject {
|
class Team extends DataObject {
|
||||||
@ -142,13 +144,13 @@ To include relations (`$has_one`, `$has_many` and `$many_many`) in your search,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
### Summary Fields
|
### 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
|
Summary fields can be used to show a quick overview of the data for a specific [api:DataObject] record. The most common use
|
||||||
is their display as table columns, e.g. in the search results of a [api:ModelAdmin] CMS interface.
|
is their display as table columns, e.g. in the search results of a [api:ModelAdmin] CMS interface.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyDataObject extends DataObject {
|
class MyDataObject extends DataObject {
|
||||||
@ -165,10 +167,10 @@ is their display as table columns, e.g. in the search results of a [api:ModelAdm
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
To include relations or field manipulations in your summaries, you can use a dot-notation.
|
To include relations or field manipulations in your summaries, you can use a dot-notation.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class OtherObject extends DataObject {
|
class OtherObject extends DataObject {
|
||||||
@ -196,10 +198,10 @@ To include relations or field manipulations in your summaries, you can use a dot
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Non-textual elements (such as images and their manipulations) can also be used in summaries.
|
Non-textual elements (such as images and their manipulations) can also be used in summaries.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyDataObject extends DataObject {
|
class MyDataObject extends DataObject {
|
||||||
@ -218,7 +220,7 @@ Non-textual elements (such as images and their manipulations) can also be used i
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
## Related Documentation
|
```
|
||||||
|
|
||||||
* [SearchFilters](searchfilters)
|
* [SearchFilters](searchfilters)
|
||||||
|
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
|
---
|
||||||
title: Indexes
|
title: Indexes
|
||||||
summary: Add Indexes to your Data Model to optimize database queries.
|
summary: Add Indexes to your Data Model to optimize database queries.
|
||||||
|
icon: database
|
||||||
|
---
|
||||||
# Indexes
|
# Indexes
|
||||||
|
|
||||||
It is sometimes desirable to add indexes to your data model, whether to optimize queries or add a uniqueness constraint
|
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
|
to a field. This is done through the `DataObject::$indexes` map, which maps index names to descriptor arrays that
|
||||||
represent each index. There're several supported notations:
|
represent each index. There're several supported notations:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyObject extends DataObject {
|
class MyObject extends DataObject {
|
||||||
@ -19,6 +21,7 @@ represent each index. There're several supported notations:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
The `<index-name>` can be an arbitrary identifier in order to allow for more than one index on a specific database
|
The `<index-name>` can be 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
|
column. The "advanced" notation supports more `<type>` notations. These vary between database drivers, but all of them
|
||||||
support the following:
|
support the following:
|
||||||
@ -32,7 +35,7 @@ In order to use more database specific or complex index notations, we also suppo
|
|||||||
|
|
||||||
**mysite/code/MyTestObject.php**
|
**mysite/code/MyTestObject.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyTestObject extends DataObject {
|
class MyTestObject extends DataObject {
|
||||||
@ -50,7 +53,7 @@ In order to use more database specific or complex index notations, we also suppo
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
As of 3.7.0 `default_sort` fields will automatically become database indexes as this provides significant performance
|
```
|
||||||
benefits.
|
benefits.
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Dynamic Default Fields
|
||||||
|
summary: Learn how to add default values to your models
|
||||||
|
---
|
||||||
|
|
||||||
# Dynamic Default Values
|
# Dynamic Default Values
|
||||||
|
|
||||||
The [api:DataObject::$defaults] array allows you to specify simple static values to be the default values when a
|
The [api:DataObject::$defaults] array allows you to specify simple static values to be the default values when a
|
||||||
@ -9,7 +14,7 @@ object!
|
|||||||
|
|
||||||
A simple example is to set a field to the current date and time:
|
A simple example is to set a field to the current date and time:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
/**
|
/**
|
||||||
* Sets the Date field to the current date.
|
* Sets the Date field to the current date.
|
||||||
*/
|
*/
|
||||||
@ -18,19 +23,5 @@ A simple example is to set a field to the current date and time:
|
|||||||
parent::populateDefaults();
|
parent::populateDefaults();
|
||||||
}
|
}
|
||||||
|
|
||||||
It's also possible to get the data from any other source, or another object, just by using the usual data retrieval
|
```
|
||||||
methods. For example:
|
methods. For example:
|
||||||
|
|
||||||
:::php
|
|
||||||
/**
|
|
||||||
* This method combines the Title of the parent object with the Title of this
|
|
||||||
* object in the FullTitle field.
|
|
||||||
*/
|
|
||||||
public function populateDefaults() {
|
|
||||||
if($parent = $this->Parent()) {
|
|
||||||
$this->FullTitle = $parent->Title . ': ' . $this->Title;
|
|
||||||
} else {
|
|
||||||
$this->FullTitle = $this->Title;
|
|
||||||
}
|
|
||||||
parent::populateDefaults();
|
|
||||||
}
|
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Grouping DataObject sets
|
||||||
|
summary: Learn how to split the results of a query into subgroups
|
||||||
|
---
|
||||||
|
|
||||||
# Grouping lists of records
|
# Grouping lists of records
|
||||||
|
|
||||||
The [api:SS_List] class is designed to return a flat list of records.
|
The [api:SS_List] class is designed to return a flat list of records.
|
||||||
@ -22,19 +27,22 @@ Let's say you have a set of Module objects, each representing a SilverStripe mod
|
|||||||
these in alphabetical order, with each letter as a heading; something like the following list:
|
these in alphabetical order, with each letter as a heading; something like the following list:
|
||||||
|
|
||||||
* B
|
* B
|
||||||
|
```
|
||||||
* Blog
|
* Blog
|
||||||
* C
|
```
|
||||||
|
```
|
||||||
* CMS Workflow
|
* CMS Workflow
|
||||||
* Custom Translations
|
* Custom Translations
|
||||||
* D
|
```
|
||||||
|
```
|
||||||
* Database Plumber
|
* Database Plumber
|
||||||
* ...
|
* ...
|
||||||
|
|
||||||
The first step is to set up the basic data model,
|
```
|
||||||
along with a method that returns the first letter of the title. This
|
along with a method that returns the first letter of the title. This
|
||||||
will be used both for grouping and for the title in the template.
|
will be used both for grouping and for the title in the template.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class Module extends DataObject {
|
class Module extends DataObject {
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
'Title' => 'Text'
|
'Title' => 'Text'
|
||||||
@ -49,10 +57,10 @@ will be used both for grouping and for the title in the template.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
The next step is to create a method or variable that will contain/return all the objects,
|
```
|
||||||
sorted by title. For this example this will be a method on the `Page` class.
|
sorted by title. For this example this will be a method on the `Page` class.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class Page extends SiteTree {
|
class Page extends SiteTree {
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
@ -67,11 +75,11 @@ sorted by title. For this example this will be a method on the `Page` class.
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
The final step is to render this into a template. The `GroupedBy()` method breaks up the set into
|
```
|
||||||
a number of sets, grouped by the field that is passed as the parameter.
|
a number of sets, grouped by the field that is passed as the parameter.
|
||||||
In this case, the `getTitleFirstLetter()` method defined earlier is used to break them up.
|
In this case, the `getTitleFirstLetter()` method defined earlier is used to break them up.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<%-- Modules list grouped by TitleFirstLetter --%>
|
<%-- Modules list grouped by TitleFirstLetter --%>
|
||||||
<h2>Modules</h2>
|
<h2>Modules</h2>
|
||||||
<% loop $GroupedModules.GroupedBy(TitleFirstLetter) %>
|
<% loop $GroupedModules.GroupedBy(TitleFirstLetter) %>
|
||||||
@ -83,7 +91,7 @@ In this case, the `getTitleFirstLetter()` method defined earlier is used to brea
|
|||||||
</ul>
|
</ul>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
|
||||||
## Grouping Sets By Month
|
```
|
||||||
|
|
||||||
Grouping a set by month is a very similar process.
|
Grouping a set by month is a very similar process.
|
||||||
The only difference would be to sort the records by month name, and
|
The only difference would be to sort the records by month name, and
|
||||||
@ -95,7 +103,7 @@ but grouping by its built-in `Created` property instead,
|
|||||||
which is automatically set when the record is first written to the database.
|
which is automatically set when the record is first written to the database.
|
||||||
This will have a method which returns the month it was posted in:
|
This will have a method which returns the month it was posted in:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class Module extends DataObject {
|
class Module extends DataObject {
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
@ -110,10 +118,10 @@ This will have a method which returns the month it was posted in:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
The next step is to create a method that will return all records that exist,
|
```
|
||||||
sorted by month name from January to December. This can be accomplshed by sorting by the `Created` field:
|
sorted by month name from January to December. This can be accomplshed by sorting by the `Created` field:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class Page extends SiteTree {
|
class Page extends SiteTree {
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
@ -128,9 +136,9 @@ sorted by month name from January to December. This can be accomplshed by sortin
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
The final step is the render this into the template using the [api:GroupedList::GroupedBy()] method.
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
// Modules list grouped by the Month Posted
|
// Modules list grouped by the Month Posted
|
||||||
<h2>Modules</h2>
|
<h2>Modules</h2>
|
||||||
<% loop $GroupedModulesByDate.GroupedBy(MonthCreated) %>
|
<% loop $GroupedModulesByDate.GroupedBy(MonthCreated) %>
|
||||||
@ -142,7 +150,7 @@ The final step is the render this into the template using the [api:GroupedList::
|
|||||||
</ul>
|
</ul>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
|
||||||
## Related
|
```
|
||||||
|
|
||||||
* [Howto: "Pagination"](/developer_guides/templates/how_tos/pagination)
|
* [Howto: "Pagination"](/developer_guides/templates/how_tos/pagination)
|
||||||
|
|
||||||
|
6
docs/en/02_Developer_Guides/00_Model/How_Tos/index.md
Normal file
6
docs/en/02_Developer_Guides/00_Model/How_Tos/index.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: How To's
|
||||||
|
---
|
||||||
|
# How To's: Model and Databases
|
||||||
|
|
||||||
|
[CHILDREN]
|
@ -1,7 +1,9 @@
|
|||||||
|
---
|
||||||
title: Model and Databases
|
title: Model and Databases
|
||||||
summary: Learn how SilverStripe manages database tables, ways to query your database and how to publish data.
|
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.
|
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.
|
||||||
|
icon: database
|
||||||
|
---
|
||||||
In SilverStripe, application data will be represented by a [api:DataObject] class. A `DataObject` subclass defines the
|
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`
|
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.
|
which stores information about a person, CMS user or mail subscriber.
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Template Syntax
|
title: Template Syntax
|
||||||
summary: A look at the operations, variables and language controls you can use within templates.
|
summary: A look at the operations, variables and language controls you can use within templates.
|
||||||
|
icon: code
|
||||||
|
---
|
||||||
# Template Syntax
|
# Template Syntax
|
||||||
|
|
||||||
SilverStripe templates are plain text files that have `.ss` extension and located within the `templates` directory of
|
SilverStripe templates are plain text files that have `.ss` extension and located within the `templates` directory of
|
||||||
@ -12,7 +14,7 @@ An example of a SilverStripe template is below:
|
|||||||
|
|
||||||
**mysite/templates/Page.ss**
|
**mysite/templates/Page.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<% base_tag %>
|
<% base_tag %>
|
||||||
@ -40,10 +42,10 @@ An example of a SilverStripe template is below:
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</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
|
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.
|
text-based format.
|
||||||
</div>
|
[/note]
|
||||||
|
|
||||||
## Variables
|
## Variables
|
||||||
|
|
||||||
@ -51,19 +53,19 @@ Variables are placeholders that will be replaced with data from the [DataModel](
|
|||||||
[Controller](../controllers). Variables are prefixed with a `$` character. Variable names must start with an
|
[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:
|
alphabetic character or underscore, with subsequent characters being alphanumeric or underscore:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Title
|
$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.
|
Variables can be chained together, and include arguments.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Foo
|
$Foo
|
||||||
$Foo(param)
|
$Foo(param)
|
||||||
$Foo.Bar
|
$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` will call `$obj->Foo()` (or the field `$obj->Foo`)
|
||||||
* `$Foo(param)` will call `$obj->Foo("param")`
|
* `$Foo(param)` will call `$obj->Foo("param")`
|
||||||
@ -73,28 +75,29 @@ If a variable returns a string, that string will be inserted into the template.
|
|||||||
the system will attempt to render the object through its `forTemplate()` method. If the `forTemplate()` method has not
|
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.
|
been defined, the system will return an error.
|
||||||
|
|
||||||
<div class="note" markdown="1">
|
[note]
|
||||||
For more detail around how variables are inserted and formatted into a template see
|
For more detail around how variables are inserted and formatted into a template see
|
||||||
[Formating, Modifying and Casting Variables](casting)
|
[Formating, Modifying and Casting Variables](casting)
|
||||||
</div>
|
[/note]
|
||||||
|
|
||||||
Variables can come from your database fields, or custom methods you define on your objects.
|
Variables can come from your database fields, or custom methods you define on your objects.
|
||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function UsersIpAddress() {
|
public function UsersIpAddress() {
|
||||||
return $this->getRequest()->getIP();
|
return $this->getRequest()->getIP();
|
||||||
}
|
}
|
||||||
|
|
||||||
**mysite/code/Page.ss**
|
```
|
||||||
|
|
||||||
:::html
|
```html
|
||||||
<p>You are coming from $UsersIpAddress.</p>
|
<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()`.
|
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 variables that can be used in a template vary based on the object currently in [scope](#scope). Scope defines what
|
The variables 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]
|
object the methods get called on. For the standard `Page.ss` template the scope is the current [api:Page_Controller]
|
||||||
@ -103,46 +106,48 @@ record and any subclasses of those two.
|
|||||||
|
|
||||||
**mysite/code/Layout/Page.ss**
|
**mysite/code/Layout/Page.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Title
|
$Title
|
||||||
// returns the page `Title` property
|
// returns the page `Title` property
|
||||||
|
|
||||||
$Content
|
$Content
|
||||||
// returns the page `Content` property
|
// returns the page `Content` property
|
||||||
|
|
||||||
|
```
|
||||||
## Conditional Logic
|
## Conditional Logic
|
||||||
|
|
||||||
The simplest conditional block is to check for the presence of a value (does not equal 0, null, false).
|
The simplest conditional block is to check for the presence of a value (does not equal 0, null, false).
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $CurrentMember %>
|
<% if $CurrentMember %>
|
||||||
<p>You are logged in as $CurrentMember.FirstName $CurrentMember.Surname.</p>
|
<p>You are logged in as $CurrentMember.FirstName $CurrentMember.Surname.</p>
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
|
```
|
||||||
A conditional can also check for a value other than falsy.
|
A conditional can also check for a value other than falsy.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $MyDinner == "kipper" %>
|
<% if $MyDinner == "kipper" %>
|
||||||
Yummy, kipper for tea.
|
Yummy, kipper for tea.
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
|
[notice]
|
||||||
When inside template tags variables should have a '$' prefix, and literals should have quotes.
|
When inside template tags variables should have a '$' prefix, and literals should have quotes.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
Conditionals can also provide the `else` case.
|
Conditionals can also provide the `else` case.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $MyDinner == "kipper" %>
|
<% if $MyDinner == "kipper" %>
|
||||||
Yummy, kipper for tea
|
Yummy, kipper for tea
|
||||||
<% else %>
|
<% else %>
|
||||||
I wish I could have kipper :-(
|
I wish I could have kipper :-(
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
`else_if` commands can be used to handle multiple `if` statements.
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $MyDinner == "quiche" %>
|
<% if $MyDinner == "quiche" %>
|
||||||
Real men don't eat quiche
|
Real men don't eat quiche
|
||||||
<% else_if $MyDinner == $YourDinner %>
|
<% else_if $MyDinner == $YourDinner %>
|
||||||
@ -151,74 +156,74 @@ Conditionals can also provide the `else` case.
|
|||||||
Can I have some of your chips?
|
Can I have some of your chips?
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
### Negation
|
```
|
||||||
|
|
||||||
The inverse of `<% if %>` is `<% if not %>`.
|
The inverse of `<% if %>` is `<% if not %>`.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if not $DinnerInOven %>
|
<% if not $DinnerInOven %>
|
||||||
I'm going out for dinner tonight.
|
I'm going out for dinner tonight.
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
### Boolean Logic
|
```
|
||||||
|
|
||||||
Multiple checks can be done using `||`, `or`, `&&` or `and`.
|
Multiple checks can be done using `||`, `or`, `&&` or `and`.
|
||||||
|
|
||||||
If *either* of the conditions is true.
|
If *either* of the conditions is true.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $MyDinner == "kipper" || $MyDinner == "salmon" %>
|
<% if $MyDinner == "kipper" || $MyDinner == "salmon" %>
|
||||||
yummy, fish for tea
|
yummy, fish for tea
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
If *both* of the conditions are true.
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $MyDinner == "quiche" && $YourDinner == "kipper" %>
|
<% if $MyDinner == "quiche" && $YourDinner == "kipper" %>
|
||||||
Lets swap dinners
|
Lets swap dinners
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
### Inequalities
|
```
|
||||||
|
|
||||||
You can use inequalities like `<`, `<=`, `>`, `>=` to compare numbers.
|
You can use inequalities like `<`, `<=`, `>`, `>=` to compare numbers.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $Number >= "5" && $Number <= "10" %>
|
<% if $Number >= "5" && $Number <= "10" %>
|
||||||
Number between 5 and 10
|
Number between 5 and 10
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
|
```
|
||||||
## Includes
|
## Includes
|
||||||
|
|
||||||
Within SilverStripe templates we have the ability to include other templates from the `template/Includes` directory
|
Within SilverStripe templates we have the ability to include other templates from the `template/Includes` directory
|
||||||
using the `<% include %>` tag.
|
using the `<% include %>` tag.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% include SideBar %>
|
<% 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.
|
the include only happens if the user is logged in.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $CurrentMember %>
|
<% if $CurrentMember %>
|
||||||
<% include MembersOnlyInclude %>
|
<% include MembersOnlyInclude %>
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
Includes can't directly access the parent scope when the include is included. However you can pass arguments to the
|
```
|
||||||
include.
|
include.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% with $CurrentMember %>
|
<% with $CurrentMember %>
|
||||||
<% include MemberDetails Top=$Top, Name=$Name %>
|
<% include MemberDetails Top=$Top, Name=$Name %>
|
||||||
<% end_with %>
|
<% end_with %>
|
||||||
|
|
||||||
|
```
|
||||||
## Looping Over Lists
|
## 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]
|
The `<% loop %>` tag is used to iterate or loop over a collection of items such as [api:DataList] or a [api:ArrayList]
|
||||||
collection.
|
collection.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<h1>Children of $Title</h1>
|
<h1>Children of $Title</h1>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
@ -227,16 +232,16 @@ collection.
|
|||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
This snippet loops over the children of a page, and generates an unordered list showing the `Title` property from each
|
```
|
||||||
page.
|
page.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
$Title inside the loop refers to the Title property on each object that is looped over, not the current page like
|
$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.
|
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
|
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.
|
object that is being looped over.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
### Altering the list
|
### Altering the list
|
||||||
|
|
||||||
@ -245,50 +250,50 @@ templates can call [api:DataList] methods.
|
|||||||
|
|
||||||
Sorting the list by a given field.
|
Sorting the list by a given field.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<% loop $Children.Sort(Title, ASC) %>
|
<% loop $Children.Sort(Title, ASC) %>
|
||||||
<li>$Title</li>
|
<li>$Title</li>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Limiting the number of items displayed.
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<% loop $Children.Limit(10) %>
|
<% loop $Children.Limit(10) %>
|
||||||
<li>$Title</li>
|
<li>$Title</li>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Reversing the loop.
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<% loop $Children.Reverse %>
|
<% loop $Children.Reverse %>
|
||||||
<li>$Title</li>
|
<li>$Title</li>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Filtering the loop.
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<% loop $Children.Filter('School', 'College') %>
|
<% loop $Children.Filter('School', 'College') %>
|
||||||
<li>$Title</li>
|
<li>$Title</li>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Methods can also be chained.
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<% loop $Children.Filter('School', 'College').Sort(Score, DESC) %>
|
<% loop $Children.Filter('School', 'College').Sort(Score, DESC) %>
|
||||||
<li>$Title</li>
|
<li>$Title</li>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
### Position Indicators
|
```
|
||||||
|
|
||||||
Inside the loop scope, there are many variables at your disposal to determine the current position in the list and
|
Inside the loop scope, there are many variables at your disposal to determine the current position in the list and
|
||||||
iteration.
|
iteration.
|
||||||
@ -303,7 +308,7 @@ iteration.
|
|||||||
Last item defaults to 1, but can be passed as a parameter.
|
Last item defaults to 1, but can be passed as a parameter.
|
||||||
* `$TotalItems`: Number of items in the list (integer).
|
* `$TotalItems`: Number of items in the list (integer).
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<% loop $Children.Reverse %>
|
<% loop $Children.Reverse %>
|
||||||
<% if First %>
|
<% if First %>
|
||||||
@ -314,16 +319,16 @@ iteration.
|
|||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</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
|
A common task is to paginate your lists. See the [Pagination](how_tos/pagination) how to for a tutorial on adding
|
||||||
pagination.
|
pagination.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
### Modulus and MultipleOf
|
### Modulus and MultipleOf
|
||||||
|
|
||||||
$Modulus and $MultipleOf can help to build column and grid layouts.
|
$Modulus and $MultipleOf can help to build column and grid layouts.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
// returns an int
|
// returns an int
|
||||||
$Modulus(value, offset)
|
$Modulus(value, offset)
|
||||||
|
|
||||||
@ -338,45 +343,45 @@ $Modulus and $MultipleOf can help to build column and grid layouts.
|
|||||||
|
|
||||||
// returns <div class="column-3">, <div class="column-2">,
|
// 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
|
`$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`.
|
`clear: both` to `.column-1`.
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
$MultipleOf(value, offset) can also be utilized to build column and grid layouts. In this case we want to add a `<br>`
|
$MultipleOf(value, offset) can also be utilized to build column and grid layouts. In this case we want to add a `<br>`
|
||||||
after every 3rd item.
|
after every 3rd item.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% loop $Children %>
|
<% loop $Children %>
|
||||||
<% if $MultipleOf(3) %>
|
<% if $MultipleOf(3) %>
|
||||||
<br>
|
<br>
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
|
||||||
### Escaping
|
```
|
||||||
|
|
||||||
Sometimes you will have template tags which need to roll into one another. Use `{}` to contain variables.
|
Sometimes you will have template tags which need to roll into one another. Use `{}` to contain variables.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Foopx // will returns "" (as it looks for a `Foopx` value)
|
$Foopx // will returns "" (as it looks for a `Foopx` value)
|
||||||
{$Foo}px // returns "3px" (CORRECT)
|
{$Foo}px // returns "3px" (CORRECT)
|
||||||
|
|
||||||
|
```
|
||||||
Or when having a `$` sign in front of the variable such as displaying money.
|
Or when having a `$` sign in front of the variable such as displaying money.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$$Foo // returns ""
|
$$Foo // returns ""
|
||||||
${$Foo} // returns "$3"
|
${$Foo} // returns "$3"
|
||||||
|
|
||||||
You can also use a backslash to escape the name of the variable, such as:
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Foo // returns "3"
|
$Foo // returns "3"
|
||||||
\$Foo // returns "$Foo"
|
\$Foo // returns "$Foo"
|
||||||
|
|
||||||
<div class="hint" markdown="1">
|
```
|
||||||
For more information on formatting and casting variables see [Formating, Modifying and Casting Variables](casting)
|
For more information on formatting and casting variables see [Formating, Modifying and Casting Variables](casting)
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
@ -389,9 +394,10 @@ 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
|
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
|
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]
|
$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.
|
classes of the current scope object, and any [api:Extension] instances you have.
|
||||||
|
|
||||||
### Navigating Scope
|
### Navigating Scope
|
||||||
@ -400,7 +406,7 @@ classes of the current scope object, and any [api:Extension] instances you have.
|
|||||||
|
|
||||||
When in a particular scope, `$Up` takes the scope back to the previous level.
|
When in a particular scope, `$Up` takes the scope back to the previous level.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<h1>Children of '$Title'</h1>
|
<h1>Children of '$Title'</h1>
|
||||||
|
|
||||||
<% loop $Children %>
|
<% loop $Children %>
|
||||||
@ -411,39 +417,41 @@ When in a particular scope, `$Up` takes the scope back to the previous level.
|
|||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
|
||||||
Given the following structure, it will output the text.
|
```
|
||||||
|
|
||||||
|
```
|
||||||
My Page
|
My Page
|
||||||
|
|
|
|
||||||
+-+ Child 1
|
+-+ Child 1
|
||||||
| |
|
```
|
||||||
| +- Grandchild 1
|
| +- Grandchild 1
|
||||||
|
|
|
|
||||||
+-+ Child 2
|
+-+ Child 2
|
||||||
|
|
||||||
|
```
|
||||||
Children of 'My Page'
|
Children of 'My Page'
|
||||||
|
|
||||||
Page 'Child 1' is a child of 'My Page'
|
Page 'Child 1' is a child of 'My Page'
|
||||||
Page 'Grandchild 1' is a grandchild of 'My Page'
|
Page 'Grandchild 1' is a grandchild of 'My Page'
|
||||||
Page 'Child 2' is a child of 'MyPage'
|
Page 'Child 2' is a child of 'MyPage'
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
Additional selectors implicitely change the scope so you need to put additional `$Up` to get what you expect.
|
Additional selectors implicitely change the scope so you need to put additional `$Up` to get what you expect.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<h1>Children of '$Title'</h1>
|
<h1>Children of '$Title'</h1>
|
||||||
<% loop $Children.Sort('Title').First %>
|
<% loop $Children.Sort('Title').First %>
|
||||||
<%-- We have two additional selectors in the loop expression so... --%>
|
<%-- We have two additional selectors in the loop expression so... --%>
|
||||||
<p>Page '$Title' is a child of '$Up.Up.Up.Title'</p>
|
<p>Page '$Title' is a child of '$Up.Up.Up.Title'</p>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
|
||||||
#### 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
|
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.
|
page. The previous example could be rewritten to use the following syntax.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<h1>Children of '$Title'</h1>
|
<h1>Children of '$Title'</h1>
|
||||||
|
|
||||||
<% loop $Children %>
|
<% loop $Children %>
|
||||||
@ -454,21 +462,21 @@ page. The previous example could be rewritten to use the following syntax.
|
|||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
|
||||||
### With
|
```
|
||||||
|
|
||||||
The `<% with %>` tag lets you change into a new scope. Consider the following example:
|
The `<% with %>` tag lets you change into a new scope. Consider the following example:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% with $CurrentMember %>
|
<% with $CurrentMember %>
|
||||||
Hello, $FirstName, welcome back. Your current balance is $Balance.
|
Hello, $FirstName, welcome back. Your current balance is $Balance.
|
||||||
<% end_with %>
|
<% end_with %>
|
||||||
|
|
||||||
This is functionalty the same as the following:
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
Hello, $CurrentMember.FirstName, welcome back. Your current balance is $CurrentMember.Balance
|
Hello, $CurrentMember.FirstName, welcome back. Your 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
|
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
|
refer directly to properties and methods of the [api:Member] object. `$FirstName` inside the scope is equivalent to
|
||||||
@ -478,24 +486,24 @@ refer directly to properties and methods of the [api:Member] object. `$FirstName
|
|||||||
|
|
||||||
`$Me` outputs the current object in scope. This will call the `forTemplate` of the object.
|
`$Me` outputs the current object in scope. This will call the `forTemplate` of the object.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Me
|
$Me
|
||||||
|
|
||||||
## Comments
|
```
|
||||||
|
|
||||||
Using standard HTML comments is supported. These comments will be included in the published site.
|
Using standard HTML comments is supported. These comments will be included in the published site.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$EditForm <!-- Some public comment about the form -->
|
$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
|
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.
|
for adding notes for other developers but for things you don't want published in the public html.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$EditForm <%-- Some hidden comment about the form --%>
|
$EditForm <%-- Some hidden comment about the form --%>
|
||||||
|
|
||||||
## Related
|
```
|
||||||
|
|
||||||
[CHILDREN]
|
[CHILDREN]
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: Common Variables
|
title: Common Variables
|
||||||
summary: Some of the common variables and methods your templates can use, including Menu, SiteConfig, and more.
|
summary: Some of the common variables and methods your templates can use, including Menu, SiteConfig, and more.
|
||||||
|
---
|
||||||
# Common Variables
|
# Common Variables
|
||||||
|
|
||||||
The page below describes a few of common variables and methods you'll see in a SilverStripe template. This is not an
|
The page below describes a few of common variables and methods you'll see in a SilverStripe template. This is not an
|
||||||
@ -12,95 +13,95 @@ explained in more detail on the [syntax](syntax#scope) page. Many of the methods
|
|||||||
scope, and you can specify additional static methods to be available globally in templates by implementing the
|
scope, and you can specify additional static methods to be available globally in templates by implementing the
|
||||||
[api:TemplateGlobalProvider] interface.
|
[api:TemplateGlobalProvider] interface.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
Want a quick way of knowing what scope you're in? Try putting `$ClassName` in your template. You should see a string
|
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
|
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**
|
properties or relations on the `Page` class, `Page_Controller` class as well as anything from their subclasses **or**
|
||||||
extensions.
|
extensions.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
Outputting these variables is only the start, if you want to format or manipulate them before adding them to the template
|
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.
|
have a read of the [Formating, Modifying and Casting Variables](casting) documentation.
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
Some of the following only apply when you have the `CMS` module installed. If you're using the `Framework` alone, this
|
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.
|
functionality may not be included.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
|
|
||||||
## Base Tag
|
## Base Tag
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<head>
|
<head>
|
||||||
<% base_tag %>
|
<% base_tag %>
|
||||||
|
|
||||||
..
|
..
|
||||||
</head>
|
</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
|
src="someimage.jpg" />) will become relative to the URI specified in the base tag. This ensures the browser knows where
|
||||||
to locate your site’s images and css files.
|
to locate your site’s images and css files.
|
||||||
|
|
||||||
It renders in the template as `<base href="http://www.yoursite.com" /><!--[if lte IE 6]></base><![endif]-->`
|
It renders in the template as `<base href="http://www.yoursite.com" /><!--[if lte IE 6]></base><![endif]-->`
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
A `<% base_tag %>` is nearly always required or assumed by SilverStripe to exist.
|
A `<% base_tag %>` is nearly always required or assumed by SilverStripe to exist.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## CurrentMember
|
## CurrentMember
|
||||||
|
|
||||||
Returns the currently logged in [api:Member] instance, if there is one logged in.
|
Returns the currently logged in [api:Member] instance, if there is one logged in.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $CurrentMember %>
|
<% if $CurrentMember %>
|
||||||
Welcome Back, $CurrentMember.FirstName
|
Welcome Back, $CurrentMember.FirstName
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
|
```
|
||||||
## Title and Menu Title
|
## Title and Menu Title
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Title
|
$Title
|
||||||
$MenuTitle
|
$MenuTitle
|
||||||
|
|
||||||
Most objects within SilverStripe will respond to `$Title` (i.e they should have a `Title` database field or at least a
|
```
|
||||||
`getTitle()` method).
|
`getTitle()` method).
|
||||||
|
|
||||||
The CMS module in particular provides two fields to label a page: `Title` and `MenuTitle`. `Title` is the title
|
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.
|
displayed on the web page, while `MenuTitle` can be a shorter version suitable for size-constrained menus.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
If `MenuTitle` is left blank by the CMS author, it'll just default to the value in `Title`.
|
If `MenuTitle` is left blank by the CMS author, it'll just default to the value in `Title`.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Page Content
|
## Page Content
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Content
|
$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.
|
but it is also the standard for any object that has a body of content to output.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
Please note that this database content can be `versioned`, meaning that draft content edited in the CMS can be different
|
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.
|
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
|
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
|
requested (e.g. by the "preview" feature in the CMS) (see the [versioning documentation](/../model/versioning) for
|
||||||
more details).
|
more details).
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
### SiteConfig: Global settings
|
### SiteConfig: Global settings
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
`SiteConfig` is a module that is bundled with the `CMS`. If you wish to include `SiteConfig` in your framework only
|
`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`.
|
web pages. You'll need to install it via `composer`.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$SiteConfig.Title
|
$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.
|
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
|
`SiteConfig` can be extended to hold other data, for example a logo image which can be uploaded through the CMS or
|
||||||
@ -113,127 +114,128 @@ The `$MetaTags` placeholder in a template returns a segment of HTML appropriate
|
|||||||
will set up title, keywords and description meta-tags, based on the CMS content and is editable in the 'Meta-data' tab
|
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.
|
on a per-page basis.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
If you don’t want to include the title tag use `$MetaTags(false)`.
|
If you don’t want to include the title tag use `$MetaTags(false)`.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
By default `$MetaTags` renders:
|
By default `$MetaTags` renders:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<title>Title of the Page</title>
|
<title>Title of the Page</title>
|
||||||
<meta name="generator" http-equiv="generator" content="SilverStripe 3.0" />
|
<meta name="generator" http-equiv="generator" content="SilverStripe 3.0" />
|
||||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||||
|
|
||||||
`$MetaTags(false)` will render
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<meta name="generator" http-equiv="generator" content="SilverStripe 3.0" />
|
<meta name="generator" http-equiv="generator" content="SilverStripe 3.0" />
|
||||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||||
|
|
||||||
If using `$MetaTags(false)` we can provide a more custom `title`.
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$MetaTags(false)
|
$MetaTags(false)
|
||||||
<title>$Title - Bob's Fantasy Football</title>
|
<title>$Title - Bob's Fantasy Football</title>
|
||||||
|
|
||||||
## Links
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<a href="$Link">..</a>
|
<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
|
returns the relative URL for the object and `AbsoluteLink` outputs your full website address along with the relative
|
||||||
link.
|
link.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Link
|
$Link
|
||||||
<!-- returns /about-us/offices/ -->
|
<!-- returns /about-us/offices/ -->
|
||||||
|
|
||||||
$AbsoluteLink
|
$AbsoluteLink
|
||||||
<!-- returns http://yoursite.com/about-us/offices/ -->
|
<!-- returns http://yoursite.com/about-us/offices/ -->
|
||||||
|
|
||||||
### Linking Modes
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$isSection
|
$isSection
|
||||||
$isCurrent
|
$isCurrent
|
||||||
|
|
||||||
When looping over a list of `SiteTree` instances through a `<% loop $Menu %>` or `<% loop $Children %>`, `$isSection` and `$isCurrent`
|
```
|
||||||
will return true or false based on page being looped over relative to the currently viewed page.
|
will return true or false based on page being looped over relative to the currently viewed page.
|
||||||
|
|
||||||
For instance, to only show the menu item linked if it's the current one:
|
For instance, to only show the menu item linked if it's the current one:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $isCurrent %>
|
<% if $isCurrent %>
|
||||||
$Title
|
$Title
|
||||||
<% else %>
|
<% else %>
|
||||||
<a href="$Link">$Title</a>
|
<a href="$Link">$Title</a>
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
|
```
|
||||||
An example for checking for `current` or `section` is as follows:
|
An example for checking for `current` or `section` is as follows:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<a class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>" href="$Link">$MenuTitle</a>
|
<a class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>" href="$Link">$MenuTitle</a>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
**Additional Utility Method**
|
**Additional Utility Method**
|
||||||
|
|
||||||
* `$InSection(page-url)`: This if block will pass if we're currently on the page-url page or one of its children.
|
* `$InSection(page-url)`: This if block will pass if we're currently on the page-url page or one of its children.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $InSection(about-us) %>
|
<% if $InSection(about-us) %>
|
||||||
<p>You are viewing the about us section</p>
|
<p>You are viewing the about us section</p>
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
|
```
|
||||||
### URLSegment
|
### 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
|
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.
|
`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.
|
It can be used within templates to generate anchors or other CSS classes.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<div id="section-$URLSegment">
|
<div id="section-$URLSegment">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- returns <div id="section-offices"> -->
|
<!-- 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
|
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
|
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:
|
behavior based on the page type used:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<body class="$ClassName">
|
<body class="$ClassName">
|
||||||
|
|
||||||
<!-- returns <body class="HomePage">, <body class="BlogPage"> -->
|
<!-- returns <body class="HomePage">, <body class="BlogPage"> -->
|
||||||
|
|
||||||
## Children Loops
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% loop $Children %>
|
<% loop $Children %>
|
||||||
|
|
||||||
<% end_loop %>
|
<% 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.
|
the `CMS` or a custom list of data. This originates in the `Versioned` extension's `getChildren` method.
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
For doing your website navigation most likely you'll want to use `$Menu` since its independent of the page
|
For doing your website navigation most likely you'll want to use `$Menu` since its independent of the page
|
||||||
context.
|
context.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
### ChildrenOf
|
### ChildrenOf
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% loop $ChildrenOf(<my-page-url>) %>
|
<% loop $ChildrenOf(<my-page-url>) %>
|
||||||
|
|
||||||
<% end_loop %>
|
<% 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
|
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.
|
pages underneath a "staff" holder on any page, regardless if its on the top level or elsewhere.
|
||||||
|
|
||||||
@ -244,44 +246,44 @@ Content authors have the ability to hide pages from menus by un-selecting the `S
|
|||||||
This option will be honored by `<% loop $Children %>` and `<% loop $Menu %>` however if you want to ignore the user
|
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`.
|
preference, `AllChildren` does not filter by `ShowInMenus`.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% loop $AllChildren %>
|
<% loop $AllChildren %>
|
||||||
...
|
...
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
|
||||||
|
```
|
||||||
### Menu Loops
|
### Menu Loops
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% loop $Menu(1) %>
|
<% loop $Menu(1) %>
|
||||||
...
|
...
|
||||||
<% end_loop %>
|
<% 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">
|
[notice]
|
||||||
Pages with the `ShowInMenus` property set to `false` will be filtered out.
|
Pages with the `ShowInMenus` property set to `false` will be filtered out.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Access to a specific Page
|
## Access to a specific Page
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% with $Page(my-page) %>
|
<% with $Page(my-page) %>
|
||||||
$Title
|
$Title
|
||||||
<% end_with %>
|
<% end_with %>
|
||||||
|
|
||||||
Page will return a single page from site, looking it up by URL.
|
```
|
||||||
|
|
||||||
## Access to Parent and Level Pages
|
## Access to Parent and Level Pages
|
||||||
|
|
||||||
### Level
|
### Level
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% with $Level(1) %>
|
<% with $Level(1) %>
|
||||||
$Title
|
$Title
|
||||||
<% end_with %>
|
<% 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.
|
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".
|
For example, imagine you're on the "bob marley" page, which is three levels in: "about us > staff > bob marley".
|
||||||
@ -292,7 +294,7 @@ For example, imagine you're on the "bob marley" page, which is three levels in:
|
|||||||
|
|
||||||
### Parent
|
### Parent
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<!-- given we're on 'Bob Marley' in "about us > staff > bob marley" -->
|
<!-- given we're on 'Bob Marley' in "about us > staff > bob marley" -->
|
||||||
|
|
||||||
$Parent.Title
|
$Parent.Title
|
||||||
@ -301,7 +303,7 @@ For example, imagine you're on the "bob marley" page, which is three levels in:
|
|||||||
$Parent.Parent.Title
|
$Parent.Parent.Title
|
||||||
<!-- returns 'about us' -->
|
<!-- returns 'about us' -->
|
||||||
|
|
||||||
|
```
|
||||||
## Navigating Scope
|
## Navigating Scope
|
||||||
|
|
||||||
See [scope](syntax#scope).
|
See [scope](syntax#scope).
|
||||||
@ -314,29 +316,29 @@ for website users.
|
|||||||
While you can achieve breadcrumbs through the `$Level(<level>)` control manually, there's a nicer shortcut: The
|
While you can achieve breadcrumbs through the `$Level(<level>)` control manually, there's a nicer shortcut: The
|
||||||
`$Breadcrumbs` variable.
|
`$Breadcrumbs` variable.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Breadcrumbs
|
$Breadcrumbs
|
||||||
|
|
||||||
By default, it uses the template defined in `cms/templates/BreadcrumbsTemplate.ss`
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $Pages %>
|
<% if $Pages %>
|
||||||
<% loop $Pages %>
|
<% loop $Pages %>
|
||||||
<% if $Last %>$Title.XML<% else %><a href="$Link">$MenuTitle.XML</a> »<% end_if %>
|
<% if $Last %>$Title.XML<% else %><a href="$Link">$MenuTitle.XML</a> »<% end_if %>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
```
|
||||||
To customise the markup that the `$Breadcrumbs` generates, copy `cms/templates/BreadcrumbsTemplate.ss` to
|
To customise 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.
|
`mysite/templates/BreadcrumbsTemplate.ss`, modify the newly copied template and flush your SilverStripe cache.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
## Forms
|
## Forms
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Form
|
$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.
|
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.
|
Placing it just below `$Content` is a good default.
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Requirements
|
title: Requirements
|
||||||
summary: How to include and require other assets in your templates such as javascript and CSS files.
|
summary: How to include and require other assets in your templates such as javascript and CSS files.
|
||||||
|
iconBrand: js
|
||||||
|
---
|
||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
The requirements class takes care of including CSS and JavaScript into your applications. This is preferred to hard
|
The requirements class takes care of including CSS and JavaScript into your applications. This is preferred to hard
|
||||||
@ -11,63 +13,35 @@ coding any references in the `<head>` tag of your template, as it enables a more
|
|||||||
|
|
||||||
**mysite/templates/Page.ss**
|
**mysite/templates/Page.ss**
|
||||||
|
|
||||||
```
|
|
||||||
<% require css("cms/css/TreeSelector.css") %>
|
|
||||||
<% require themedCSS("TreeSelector") %>
|
|
||||||
<% require javascript("cms/javascript/LeftAndMain.js") %>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
Requiring assets from the template is restricted compared to the PHP API.
|
Requiring assets from the template is restricted compared to the PHP API.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## PHP Requirements API
|
## PHP Requirements API
|
||||||
|
|
||||||
It is common practice to include most Requirements either in the *init()*-method of your [controller](../controllers/), or
|
It is common practice to include most Requirements either in the *init()*-method of your [controller](../controllers/), or
|
||||||
as close to rendering as possible (e.g. in [api:FormField]).
|
as close to rendering as possible (e.g. in [api:FormField]).
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
class MyCustomController extends Controller {
|
|
||||||
|
|
||||||
public function init() {
|
|
||||||
parent::init();
|
|
||||||
|
|
||||||
Requirements::javascript("cms/javascript/LeftAndMain.js");
|
|
||||||
Requirements::css("cms/css/TreeSelector.css");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### CSS Files
|
### 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
|
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.
|
`<link>` element, so you can define 'screen' or 'print' for example.
|
||||||
|
|
||||||
```php
|
|
||||||
Requirements::css("cms/css/TreeSelector.css", "screen,projection");
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Javascript Files
|
### Javascript Files
|
||||||
|
|
||||||
```php
|
|
||||||
Requirements::javascript($path);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
A variant on the inclusion of custom javascript is the inclusion of *templated* javascript. Here, you keep your
|
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.
|
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`.
|
In this example, `editor.template.js` is expected to contain a replaceable variable expressed as `$EditorCSS`.
|
||||||
@ -79,18 +53,6 @@ of 'configuration' from the database in a raw format. You'll need to use the `h
|
|||||||
this is generally speaking the best way to do these things - it clearly marks the copy as belonging to a different
|
this is generally speaking the best way to do these things - it clearly marks the copy as belonging to a different
|
||||||
language.
|
language.
|
||||||
|
|
||||||
```php
|
|
||||||
Requirements::customScript(<<<JS
|
|
||||||
alert("hi there");
|
|
||||||
JS
|
|
||||||
);
|
|
||||||
|
|
||||||
Requirements::customCSS(<<<CSS
|
|
||||||
.tree li.$className {
|
|
||||||
background-image: url($icon);
|
|
||||||
}
|
|
||||||
CSS
|
|
||||||
);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Combining Files
|
## Combining Files
|
||||||
@ -98,46 +60,28 @@ CSS
|
|||||||
You can concatenate several CSS or javascript files into a single dynamically generated file. This increases performance
|
You can concatenate several CSS or javascript files into a single dynamically generated file. This increases performance
|
||||||
by reducing HTTP requests.
|
by reducing HTTP requests.
|
||||||
|
|
||||||
```php
|
|
||||||
Requirements::combine_files(
|
|
||||||
'foobar.js',
|
|
||||||
array(
|
|
||||||
'mysite/javascript/foo.js',
|
|
||||||
'mysite/javascript/bar.js',
|
|
||||||
)
|
|
||||||
);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<div class="alert" markdown='1'>
|
[alert]
|
||||||
To make debugging easier in your local environment, combined files is disabled when running your application in `dev`
|
To make debugging easier in your local environment, combined files is disabled when running your application in `dev`
|
||||||
mode.
|
mode.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
By default it stores the generated file in the assets/ folder, but you can configure this by pointing the
|
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.
|
`Requirements.combined_files_folder` configuration setting to a specific folder.
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
```yaml
|
|
||||||
Requirements:
|
|
||||||
combined_files_folder: '_combined'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<div class="info" markdown='1'>
|
[info]
|
||||||
If SilverStripe doesn't have permissions on your server to write these files it will default back to including them
|
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.
|
individually. SilverStripe **will not** rewrite your paths within the file.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
You can also combine CSS files into a media-specific stylesheets as you would with the `Requirements::css` call - use
|
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:
|
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');
|
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, all requirements files are flushed (deleted) when ?flush querystring parameter is set.
|
By default, all requirements files are flushed (deleted) when ?flush querystring parameter is set.
|
||||||
@ -145,19 +89,15 @@ This can be disabled by setting the `Requirements.disable_flush_combined` config
|
|||||||
|
|
||||||
## Clearing assets
|
## Clearing assets
|
||||||
|
|
||||||
```php
|
|
||||||
Requirements::clear();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Clears all defined requirements. You can also clear specific requirements.
|
Clears all defined requirements. You can also clear specific requirements.
|
||||||
|
|
||||||
```php
|
|
||||||
Requirements::clear(THIRDPARTY_DIR.'/prototype.js');
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
Depending on where you call this command, a Requirement might be *re-included* afterwards.
|
Depending on where you call this command, a Requirement might be *re-included* afterwards.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## Blocking
|
## Blocking
|
||||||
|
|
||||||
@ -168,32 +108,28 @@ 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
|
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.
|
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">
|
[alert]
|
||||||
The CMS also uses the `Requirements` system, and its operation can be affected by `block()` calls. Avoid this by
|
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.
|
limiting the scope of your blocking operations, e.g. in `init()` of your controller.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## Inclusion Order
|
## Inclusion Order
|
||||||
|
|
||||||
Requirements acts like a stack, where everything is rendered sequentially in the order it was included. There is no way
|
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.
|
to change inclusion-order, other than using *Requirements::clear* and rebuilding the whole set of requirements.
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
Inclusion order is both relevant for CSS and Javascript files in terms of dependencies, inheritance and overlays - be
|
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.
|
careful when messing with the order of requirements.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## Javascript placement
|
## Javascript placement
|
||||||
|
|
||||||
By default, SilverStripe includes all Javascript files at the bottom of the page body, unless there's another script
|
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.
|
already loaded, then, it's inserted before the first `<script>` tag. If this causes problems, it can be configured.
|
||||||
|
|
||||||
```php
|
|
||||||
Requirements::set_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
|
`Requirements.force_js_to_bottom`, will force SilverStripe to write the Javascript to the bottom of the page body, even
|
||||||
@ -202,8 +138,6 @@ if there is an earlier script tag.
|
|||||||
If the Javascript files are preferred to be placed in the `<head>` tag rather than in the `<body>` tag,
|
If the Javascript files are preferred to be placed in the `<head>` tag rather than in the `<body>` tag,
|
||||||
`Requirements.write_js_to_body` should be set to false.
|
`Requirements.write_js_to_body` should be set to false.
|
||||||
|
|
||||||
```php
|
|
||||||
Requirements::set_write_js_to_body(false);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Rendering data to a template
|
title: Rendering data to a template
|
||||||
summary: Call and render SilverStripe templates manually.
|
summary: Call and render SilverStripe templates manually.
|
||||||
|
icon: code
|
||||||
|
---
|
||||||
# Rendering data to a template
|
# Rendering data to a template
|
||||||
|
|
||||||
Templates do nothing on their own. Rather, they are used to render a particular object. All of the `<% if %>`,
|
Templates do nothing on their own. Rather, they are used to render a particular object. All of the `<% if %>`,
|
||||||
@ -12,15 +14,15 @@ The following will render the given data into a template. Given the template:
|
|||||||
|
|
||||||
**mysite/templates/Coach_Message.ss**
|
**mysite/templates/Coach_Message.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<strong>$Name</strong> is the $Role on our team.
|
<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.
|
instance with a template name or an array of templates to render.
|
||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$arrayData = new ArrayData(array(
|
$arrayData = new ArrayData(array(
|
||||||
'Name' => 'John',
|
'Name' => 'John',
|
||||||
'Role' => 'Head Coach'
|
'Role' => 'Head Coach'
|
||||||
@ -30,20 +32,20 @@ instance with a template name or an array of templates to render.
|
|||||||
|
|
||||||
// returns "<strong>John</strong> is the Head Coach on our team."
|
// 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
|
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.
|
includes [api:Controller], [api:FormField] and [api:DataObject] instances.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$controller->renderWith(array("MyController", "MyBaseController"));
|
$controller->renderWith(array("MyController", "MyBaseController"));
|
||||||
|
|
||||||
Member::currentUser()->renderWith('Member_Profile');
|
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.
|
template.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page_Controller extends ContentController {
|
class Page_Controller extends ContentController {
|
||||||
@ -59,10 +61,10 @@ template.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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`.
|
does, such as `ArrayData` or `ArrayList`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page_Controller extends ContentController {
|
class Page_Controller extends ContentController {
|
||||||
@ -87,3 +89,4 @@ does, such as `ArrayData` or `ArrayList`.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Template Inheritance
|
title: Template Inheritance
|
||||||
summary: Override and extend module and core markup templates from your application code.
|
summary: Override and extend module and core markup templates from your application code.
|
||||||
|
icon: sitemap
|
||||||
|
---
|
||||||
# Template Inheritance
|
# Template Inheritance
|
||||||
|
|
||||||
Bundled within SilverStripe are default templates for any markup the framework outputs for things like Form templates,
|
Bundled within SilverStripe are default templates for any markup the framework outputs for things like Form templates,
|
||||||
@ -16,16 +18,16 @@ name in the `mysite/templates/email` folder or in the `themes/your_theme/templat
|
|||||||
|
|
||||||
**mysite/templates/email/GenericEmail.ss**
|
**mysite/templates/email/GenericEmail.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Body
|
$Body
|
||||||
|
|
||||||
<p>Thanks from Bob's Fantasy Football League.</p>
|
<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">
|
[alert]
|
||||||
As we've added a new file, make sure you flush your SilverStripe cache by visiting `http://yoursite.com/?flush=1`
|
As we've added a new file, make sure you flush your SilverStripe cache by visiting `http://yoursite.com/?flush=1`
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
Template inheritance works on more than email templates. All files within the `templates` directory including `includes`,
|
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
|
`layout` or anything else from core (or add-on's) template directory can be overridden by being located inside your
|
||||||
@ -51,11 +53,11 @@ It will each and prioritize templates in the following priority:
|
|||||||
4. modules (e.g. blog)
|
4. modules (e.g. blog)
|
||||||
5. framework
|
5. framework
|
||||||
|
|
||||||
<div class="warning">
|
[warning]
|
||||||
Whenever you add or remove template files, rebuild the manifest by visiting `http://yoursite.com/?flush=1`. You can
|
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
|
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.
|
only need to do it when you're developing new templates.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
## Nested Layouts through `$Layout`
|
## Nested Layouts through `$Layout`
|
||||||
|
|
||||||
@ -74,6 +76,7 @@ footer and navigation will remain the same and we don't want to replicate this w
|
|||||||
|
|
||||||
**mysite/templates/Page.ss**
|
**mysite/templates/Page.ss**
|
||||||
|
|
||||||
|
```
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
..
|
..
|
||||||
@ -88,16 +91,18 @@ footer and navigation will remain the same and we don't want to replicate this w
|
|||||||
<% include Footer %>
|
<% include Footer %>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
**mysite/templates/Layout/Page.ss**
|
```
|
||||||
|
|
||||||
|
```
|
||||||
<p>You are on a $Title page</p>
|
<p>You are on a $Title page</p>
|
||||||
|
|
||||||
$Content
|
$Content
|
||||||
|
|
||||||
**mysite/templates/Layout/HomePage.ss**
|
```
|
||||||
|
|
||||||
|
```
|
||||||
<h1>This is the homepage!</h1>
|
<h1>This is the homepage!</h1>
|
||||||
|
|
||||||
<blink>Hi!</blink>
|
<blink>Hi!</blink>
|
||||||
|
|
||||||
|
```
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Themes
|
title: Themes
|
||||||
summary: What makes up a SilverStripe Theme. How to install one or write your own theme.
|
summary: What makes up a SilverStripe Theme. How to install one or write your own theme.
|
||||||
|
icon: paint-brush
|
||||||
|
---
|
||||||
# Themes
|
# Themes
|
||||||
|
|
||||||
Themes can be used to kick start your SilverStripe projects, can be stored outside of your application code and your
|
Themes can be used to kick start your SilverStripe projects, can be stored outside of your application code and your
|
||||||
@ -23,24 +25,24 @@ theme should be accessible at `themes/theme_name`.
|
|||||||
|
|
||||||
If a theme has `composer` support you can require it directly through `composer`.
|
If a theme has `composer` support you can require it directly through `composer`.
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
composer require "author/theme_name" "dev/master"
|
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
|
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).
|
`?flush=1` to your website URL (e.g http://yoursite.com/?flush=1).
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
After installing the files through either method, update the current theme in SilverStripe. This can be done by
|
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
|
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)
|
the Site Configuration panel (http://yoursite.com/admin/settings)
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
:::yml
|
```yml
|
||||||
SSViewer:
|
SSViewer:
|
||||||
theme: theme_name
|
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.
|
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.
|
located within the `themes` directory.
|
||||||
@ -50,26 +52,6 @@ located within the `themes` directory.
|
|||||||
Your theme can also be organised into split folders for each module it caters for.
|
Your theme can also be organised into split folders for each module it caters for.
|
||||||
|
|
||||||
```
|
```
|
||||||
themes
|
|
||||||
blackcandy
|
|
||||||
css
|
|
||||||
style.css
|
|
||||||
images
|
|
||||||
templates
|
|
||||||
Page.ss
|
|
||||||
Layout
|
|
||||||
Page.ss
|
|
||||||
Includes
|
|
||||||
blackcandy_blog
|
|
||||||
css
|
|
||||||
blog.css
|
|
||||||
images
|
|
||||||
templates
|
|
||||||
Layout
|
|
||||||
BlogHolder.ss
|
|
||||||
BlogEntry.ss
|
|
||||||
Includes
|
|
||||||
```
|
|
||||||
|
|
||||||
## Submitting your theme to SilverStripe
|
## Submitting your theme to SilverStripe
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Caching
|
title: Caching
|
||||||
summary: Reduce rendering time with cached templates and understand the limitations of the ViewableData object caching.
|
summary: Reduce rendering time with cached templates and understand the limitations of the ViewableData object caching.
|
||||||
|
icon: rocket
|
||||||
|
---
|
||||||
# Caching
|
# Caching
|
||||||
|
|
||||||
## Object caching
|
## Object caching
|
||||||
@ -8,7 +10,7 @@ summary: Reduce rendering time with cached templates and understand the limitati
|
|||||||
All functions that provide data to templates must have no side effects, as the value is cached after first access. For
|
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.
|
example, this controller method will not behave as you might imagine.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
private $counter = 0;
|
private $counter = 0;
|
||||||
|
|
||||||
public function Counter() {
|
public function Counter() {
|
||||||
@ -17,13 +19,13 @@ example, this controller method will not behave as you might imagine.
|
|||||||
return $this->counter;
|
return $this->counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
:::ss
|
```ss
|
||||||
$Counter, $Counter, $Counter
|
$Counter, $Counter, $Counter
|
||||||
|
|
||||||
// returns 1, 1, 1
|
// 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.
|
`$Counter` is cached at the first access, the value of `1` is saved.
|
||||||
|
|
||||||
|
|
||||||
@ -32,8 +34,3 @@ When we render `$Counter` to the template we would expect the value to increase
|
|||||||
Partial caching is a feature that allows the caching of just a portion of a page. Instead of fetching the required data
|
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
|
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.
|
ready to go. More information about Partial caching is in the [Performance](../performance) guide.
|
||||||
|
|
||||||
:::ss
|
|
||||||
<% cached 'MyCachedContent', LastEdited %>
|
|
||||||
$Title
|
|
||||||
<% end_cached %>
|
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
|
---
|
||||||
title: Translations
|
title: Translations
|
||||||
summary: Definition of the syntax for writing i18n compatible templates.
|
summary: Definition of the syntax for writing i18n compatible templates.
|
||||||
|
icon: globe
|
||||||
|
---
|
||||||
# Translations
|
# Translations
|
||||||
|
|
||||||
Translations are easy to use with a template, and give access to SilverStripe's translation facilities. Here is an
|
Translations are easy to use with a template, and give access to SilverStripe's translation facilities. Here is an
|
||||||
example:
|
example:
|
||||||
|
|
||||||
|
```
|
||||||
<%t Foo.BAR 'Bar' %>
|
<%t Foo.BAR 'Bar' %>
|
||||||
|
|
||||||
<%t Member.WELCOME 'Welcome {name} to {site}' name=$Member.Name site="Foobar.com" %>
|
<%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.
|
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
|
`'Welcome {name} to {site}'` is the default string used, if there is no translation for Member.WELCOME in the current
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Formatting, Modifying and Casting Variables
|
title: Formatting, 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.
|
summary: Information on casting, security, modifying data before it's displayed to the user and how to format data within the template.
|
||||||
|
icon: code
|
||||||
|
---
|
||||||
# Formatting and Casting
|
# Formatting and Casting
|
||||||
|
|
||||||
All objects that are being rendered in a template should be a [api:ViewableData] instance such as `DataObject`,
|
All objects that are being rendered in a template should be a [api:ViewableData] instance such as `DataObject`,
|
||||||
@ -12,17 +14,17 @@ output the result of the [api:HtmlText::FirstParagraph()] method to the template
|
|||||||
|
|
||||||
**mysite/code/Page.ss**
|
**mysite/code/Page.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Content.FirstParagraph
|
$Content.FirstParagraph
|
||||||
<!-- returns the result of HtmlText::FirstParagragh() -->
|
<!-- returns the result of HtmlText::FirstParagragh() -->
|
||||||
|
|
||||||
$LastEdited.Format("d/m/Y")
|
$LastEdited.Format("d/m/Y")
|
||||||
<!-- returns the result of SS_Datetime::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.
|
`ViewableData` instance, you can chain the method calls.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Content.FirstParagraph.NoHTML
|
$Content.FirstParagraph.NoHTML
|
||||||
<!-- "First Paragraph" -->
|
<!-- "First Paragraph" -->
|
||||||
|
|
||||||
@ -32,10 +34,10 @@ Any public method from the object in scope can be called within the template. If
|
|||||||
<div class="$URLSegment.LowerCase">
|
<div class="$URLSegment.LowerCase">
|
||||||
<!-- <div class="about-us"> -->
|
<!-- <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
|
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.
|
your text instances. For other objects such as [api:SS_Datetime] objects see their respective API documentation pages.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## forTemplate
|
## forTemplate
|
||||||
|
|
||||||
@ -44,7 +46,7 @@ provide default template for an object.
|
|||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page extends SiteTree {
|
class Page extends SiteTree {
|
||||||
@ -54,19 +56,19 @@ provide default template for an object.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
**mysite/templates/Page.ss**
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$Me
|
$Me
|
||||||
<!-- returns Page: Home -->
|
<!-- returns Page: Home -->
|
||||||
|
|
||||||
## Casting
|
```
|
||||||
|
|
||||||
Methods which return data to the template should either return an explicit object instance describing the type of
|
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
|
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.
|
to a template, SilverStripe will ensure that the object is wrapped in the correct type and values are safely escaped.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page extends SiteTree {
|
class Page extends SiteTree {
|
||||||
@ -80,13 +82,13 @@ to a template, SilverStripe will ensure that the object is wrapped in the correc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
When calling `$MyCustomMethod` SilverStripe now has the context that this method will contain HTML and escape the data
|
```
|
||||||
accordingly.
|
accordingly.
|
||||||
|
|
||||||
<div class="note" markdown="1">
|
[note]
|
||||||
By default, all content without a type explicitly defined in a `$casting` array will be assumed to be `Text` content
|
By default, all content without a type explicitly defined in a `$casting` array will be assumed to be `Text` content
|
||||||
and HTML characters encoded.
|
and HTML characters encoded.
|
||||||
</div>
|
[/note]
|
||||||
|
|
||||||
## Escaping
|
## Escaping
|
||||||
|
|
||||||
@ -94,14 +96,10 @@ Properties are usually auto-escaped in templates to ensure consistent representa
|
|||||||
displaying un-escaped ampersands in HTML. By default, values are escaped as `XML`, which is equivalent to `HTML` for
|
displaying un-escaped ampersands in HTML. By default, values are escaped as `XML`, which is equivalent to `HTML` for
|
||||||
this purpose.
|
this purpose.
|
||||||
|
|
||||||
<div class="note" markdown="1">
|
[note]
|
||||||
There's some exceptions to this rule, see the ["security" guide](../security).
|
There's some exceptions to this rule, see the ["security" guide](../security).
|
||||||
</div>
|
[/note]
|
||||||
|
|
||||||
In case you want to explicitly allow un-escaped HTML input, the property can be cast as [api:HTMLText]. The following
|
In case you want to explicitly allow un-escaped HTML input, the property can be cast as [api:HTMLText]. The following
|
||||||
example takes the `Content` field in a `SiteTree` class, which is of this type. It forces the content into an explicitly
|
example takes the `Content` field in a `SiteTree` class, which is of this type. It forces the content into an explicitly
|
||||||
escaped format.
|
escaped format.
|
||||||
|
|
||||||
:::ss
|
|
||||||
$Content.XML
|
|
||||||
// transforms e.g. "<em>alert</em>" to "<em>alert</em>"
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
---
|
||||||
title: How to Create a Navigation Menu
|
title: How to Create a Navigation Menu
|
||||||
|
summary: Build a multi-tiered navigation UI.
|
||||||
|
---
|
||||||
# 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
|
In this how-to, we'll create a simple menu which you can use as the primary navigation for your website. This outputs a
|
||||||
@ -7,7 +9,7 @@ top level menu with a nested second level using the `Menu` loop and a `Children`
|
|||||||
|
|
||||||
**mysite/templates/Page.ss**
|
**mysite/templates/Page.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<% loop $Menu(1) %>
|
<% loop $Menu(1) %>
|
||||||
<li>
|
<li>
|
||||||
@ -28,7 +30,7 @@ top level menu with a nested second level using the `Menu` loop and a `Children`
|
|||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
## Related
|
```
|
||||||
|
|
||||||
* [Template Syntax](../syntax)
|
* [Template Syntax](../syntax)
|
||||||
* [Common Variables](../common_variables)
|
* [Common Variables](../common_variables)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
---
|
||||||
title: How to Create a Paginated List
|
title: How to Create a Paginated List
|
||||||
|
summary: Break up the result of a database query into multiple pages
|
||||||
|
---
|
||||||
# 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
|
In order to create a paginated list, create a method on your controller that first creates a `SS_List` that contains
|
||||||
@ -10,7 +12,7 @@ The `PaginatedList` will automatically set up query limits and read the request
|
|||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
/**
|
/**
|
||||||
* Returns a paginated list of all pages in the site.
|
* Returns a paginated list of all pages in the site.
|
||||||
*/
|
*/
|
||||||
@ -20,10 +22,10 @@ The `PaginatedList` will automatically set up query limits and read the request
|
|||||||
return new PaginatedList($list, $this->getRequest());
|
return new PaginatedList($list, $this->getRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
<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,
|
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.
|
it's just a term to describe a sub-collection of the list.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
There are two ways to generate pagination controls: [api:PaginatedList::Pages()] and
|
There are two ways to generate pagination controls: [api:PaginatedList::Pages()] and
|
||||||
[api:PaginatedList::PaginationSummary()]. In this example we will use `PaginationSummary()`.
|
[api:PaginatedList::PaginationSummary()]. In this example we will use `PaginationSummary()`.
|
||||||
@ -32,19 +34,19 @@ The first step is to simply list the objects in the template:
|
|||||||
|
|
||||||
**mysite/templates/Page.ss**
|
**mysite/templates/Page.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<% loop $PaginatedPages %>
|
<% loop $PaginatedPages %>
|
||||||
<li><a href="$Link">$Title</a></li>
|
<li><a href="$Link">$Title</a></li>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</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:
|
switch between pages:
|
||||||
|
|
||||||
**mysite/templates/Page.ss**
|
**mysite/templates/Page.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $PaginatedPages.MoreThanOnePage %>
|
<% if $PaginatedPages.MoreThanOnePage %>
|
||||||
<% if $PaginatedPages.NotFirstPage %>
|
<% if $PaginatedPages.NotFirstPage %>
|
||||||
<a class="prev" href="$PaginatedPages.PrevLink">Prev</a>
|
<a class="prev" href="$PaginatedPages.PrevLink">Prev</a>
|
||||||
@ -65,7 +67,7 @@ switch between pages:
|
|||||||
<% end_if %>
|
<% end_if %>
|
||||||
<% 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]`.
|
`[1] ... [3] [4] [5] [6] [7] ... [10]`.
|
||||||
|
|
||||||
## Paginating Custom Lists
|
## Paginating Custom Lists
|
||||||
@ -75,20 +77,20 @@ that you wish to display on the current page. In this situation the automatic li
|
|||||||
will break the pagination. You can disable automatic limiting using the [api:PaginatedList::setLimitItems()] method
|
will break the pagination. You can disable automatic limiting using the [api:PaginatedList::setLimitItems()] method
|
||||||
when using custom lists.
|
when using custom lists.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$myPreLimitedList = Page::get()->limit(10);
|
$myPreLimitedList = Page::get()->limit(10);
|
||||||
|
|
||||||
$pages = new PaginatedList($myPreLimitedList, $this->getRequest());
|
$pages = new PaginatedList($myPreLimitedList, $this->getRequest());
|
||||||
$pages->setLimitItems(false);
|
$pages->setLimitItems(false);
|
||||||
|
|
||||||
|
```
|
||||||
## Setting the limit of items
|
## Setting the limit of items
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$pages = new PaginatedList(Page::get(), $this->getRequest());
|
$pages = new PaginatedList(Page::get(), $this->getRequest());
|
||||||
$pages->setPageLength(25);
|
$pages->setPageLength(25);
|
||||||
|
|
||||||
If you set this limit to 0 it will disable paging entirely, effectively causing it to appear as a single page
|
```
|
||||||
list.
|
list.
|
||||||
|
|
||||||
## Template Variables
|
## Template Variables
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
|
---
|
||||||
title: Disable Anchor Rewriting
|
title: Disable Anchor Rewriting
|
||||||
|
summary: Get more control over how hash links are rendered.
|
||||||
|
---
|
||||||
# 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
|
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:
|
the current page. For example, we might have this in our template:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#section1">Section 1</a></li>
|
<li><a href="#section1">Section 1</a></li>
|
||||||
<li><a href="#section2">Section 2</a></li>
|
<li><a href="#section2">Section 2</a></li>
|
||||||
</ul>
|
</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
|
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
|
first link you will be sent to http://yoursite.com/#section1 instead of http://yoursite.com/my-long-page/#section1
|
||||||
|
|
||||||
@ -19,13 +21,13 @@ In order to prevent this situation, the SSViewer template renderer will automati
|
|||||||
doesn't specify a URL before the anchor, prefixing the URL of the current page. For our example above, the following
|
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
|
would be created in the final HTML
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="my-long-page/#section1">Section 1</a></li>
|
<li><a href="my-long-page/#section1">Section 1</a></li>
|
||||||
<li><a href="my-long-page/#section2">Section 2</a></li>
|
<li><a href="my-long-page/#section2">Section 2</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
```
|
||||||
There are cases where this can be unhelpful. HTML anchors created from Ajax responses are the most common. In these
|
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
|
situations, you can disable anchor link rewriting by setting the `SSViewer.rewrite_hash_links` configuration value to
|
||||||
`false`.
|
`false`.
|
||||||
@ -35,12 +37,3 @@ SSViewer:
|
|||||||
rewrite_hash_links: false
|
rewrite_hash_links: false
|
||||||
|
|
||||||
Or, a better way is to call this just for the rendering phase of this particular file:
|
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;
|
|
||||||
}
|
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: How To's
|
||||||
|
---
|
||||||
|
# How To's: Templates and Views
|
||||||
|
|
||||||
|
[CHILDREN]
|
@ -1,7 +1,9 @@
|
|||||||
|
---
|
||||||
title: Templates and Views
|
title: Templates and Views
|
||||||
summary: This guide showcases the SilverStripe template engine and learn how to build your own themes.
|
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 customise your data output.
|
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 customise your data output.
|
||||||
|
icon: file-code
|
||||||
|
---
|
||||||
Most of what will be public on your website comes from template files that are defined in SilverStripe. Either in the
|
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.
|
core framework, the modules or themes you install, and your own custom templates.
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: Introduction to a Controller
|
title: Introduction to a Controller
|
||||||
summary: A brief look at the definition of a Controller, creating actions and how to respond to requests.
|
summary: A brief look at the definition of a Controller, creating actions and how to respond to requests.
|
||||||
|
---
|
||||||
# Introduction to Controllers
|
# Introduction to Controllers
|
||||||
|
|
||||||
The following example is for a simple [api:Controller] class. When building off the SilverStripe Framework you will
|
The following example is for a simple [api:Controller] class. When building off the SilverStripe Framework you will
|
||||||
@ -8,7 +9,7 @@ subclass the base `Controller` class.
|
|||||||
|
|
||||||
**mysite/code/controllers/TeamController.php**
|
**mysite/code/controllers/TeamController.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class TeamController extends Controller {
|
class TeamController extends Controller {
|
||||||
@ -27,33 +28,35 @@ subclass the base `Controller` class.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## Routing
|
```
|
||||||
|
|
||||||
We need to define the URL that this controller can be accessed on. In our case, the `TeamsController` should be visible
|
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/.
|
at http://yoursite.com/teams/ and the `players` custom action is at http://yoursite.com/team/players/.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
If you're using the `cms` module with and dealing with `Page` objects then for your custom `Page Type` controllers you
|
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
|
would extend `ContentController` or `Page_Controller`. You don't need to define the routes value as the `cms` handles
|
||||||
routing.
|
routing.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
Make sure that after you have modified the `routes.yml` file, that you clear your SilverStripe caches using `?flush=1`.
|
Make sure that after you have modified the `routes.yml` file, that you clear your SilverStripe caches using `?flush=1`.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
**mysite/_config/routes.yml**
|
**mysite/_config/routes.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
---
|
```
|
||||||
|
```
|
||||||
Name: mysiteroutes
|
Name: mysiteroutes
|
||||||
After: framework/routes#coreroutes
|
After: framework/routes#coreroutes
|
||||||
---
|
```
|
||||||
|
```
|
||||||
Director:
|
Director:
|
||||||
rules:
|
rules:
|
||||||
'teams//$Action/$ID/$Name': 'TeamController'
|
'teams//$Action/$ID/$Name': 'TeamController'
|
||||||
|
|
||||||
|
```
|
||||||
For more information about creating custom routes, see the [Routing](routing) documentation.
|
For more information about creating custom routes, see the [Routing](routing) documentation.
|
||||||
|
|
||||||
## Actions
|
## Actions
|
||||||
@ -61,9 +64,9 @@ For more information about creating custom routes, see the [Routing](routing) do
|
|||||||
Controllers respond by default to an `index` method. You don't need to define this method (as it's assumed) but you
|
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).
|
can override the `index()` response to provide custom data back to the [Template and Views](../templates).
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
It is standard in SilverStripe for your controller actions to be `lowercasewithnospaces`
|
It is standard in SilverStripe for your controller actions to be `lowercasewithnospaces`
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
Action methods can return one of four main things:
|
Action methods can return one of four main things:
|
||||||
|
|
||||||
@ -74,7 +77,7 @@ Action methods can return one of four main things:
|
|||||||
|
|
||||||
**mysite/code/controllers/TeamController.php**
|
**mysite/code/controllers/TeamController.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
/**
|
/**
|
||||||
* Return some additional data to the current response that is waiting to go out, this makes $Title set to
|
* 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.
|
* 'MyTeamName' and continues on with generating the response.
|
||||||
@ -127,7 +130,7 @@ Action methods can return one of four main things:
|
|||||||
return $this->getResponse().
|
return $this->getResponse().
|
||||||
}
|
}
|
||||||
|
|
||||||
For more information on how a URL gets mapped to an action see the [Routing](routing) documentation.
|
```
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
@ -153,14 +156,14 @@ Each controller should define a `Link()` method. This should be used to avoid ha
|
|||||||
|
|
||||||
**mysite/code/controllers/TeamController.php**
|
**mysite/code/controllers/TeamController.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function Link($action = null) {
|
public function Link($action = null) {
|
||||||
return Controller::join_links('teams', $action);
|
return Controller::join_links('teams', $action);
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
```
|
||||||
The [api:Controller::join_links()] is optional, but makes `Link()` more flexible by allowing an `$action` argument, and concatenates the path segments with slashes. The action should map to a method on your controller.
|
The [api:Controller::join_links()] is optional, but makes `Link()` more flexible by allowing an `$action` argument, and concatenates the path segments with slashes. The action should map to a method on your controller.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
|
@ -1,55 +1,58 @@
|
|||||||
|
---
|
||||||
title: Routing
|
title: Routing
|
||||||
summary: A more in depth look at how to map requests to particular controllers and actions.
|
summary: A more in depth look at how to map requests to particular controllers and actions.
|
||||||
|
---
|
||||||
# Routing
|
# Routing
|
||||||
|
|
||||||
Routing is the process of mapping URL's to [api:Controllers] and actions. In the introduction we defined a new custom route
|
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`
|
for our `TeamsController` mapping any `teams` URL to our `TeamsController`
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
If you're using the `cms` module with and dealing with `Page` objects then for your custom `Page Type` controllers you
|
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
|
would extend `ContentController` or `Page_Controller`. You don't need to define the routes value as the `cms` handles
|
||||||
routing.
|
routing.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
These routes by standard, go into a `routes.yml` file in your applications `_config` folder alongside your other
|
These routes by standard, go into a `routes.yml` file in your applications `_config` folder alongside your other
|
||||||
[Configuration](../configuration) information.
|
[Configuration](../configuration) information.
|
||||||
|
|
||||||
**mysite/_config/routes.yml**
|
**mysite/_config/routes.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
---
|
```
|
||||||
|
```
|
||||||
Name: mysiteroutes
|
Name: mysiteroutes
|
||||||
After: framework/routes#coreroutes
|
After: framework/routes#coreroutes
|
||||||
---
|
```
|
||||||
|
```
|
||||||
Director:
|
Director:
|
||||||
rules:
|
rules:
|
||||||
'teams//$Action/$ID/$Name': 'TeamController'
|
'teams//$Action/$ID/$Name': 'TeamController'
|
||||||
'player/': 'PlayerController'
|
'player/': 'PlayerController'
|
||||||
'': 'HomeController'
|
'': 'HomeController'
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
To understand the syntax for the `routes.yml` file better, read the [Configuration](../configuration) documentation.
|
To understand the syntax for the `routes.yml` file better, read the [Configuration](../configuration) documentation.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
'teams//$Action/$ID/$Name': 'TeamController'
|
'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
|
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
|
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->getRequest()->param($name)`.
|
and able to be pulled out from a controller using `$this->getRequest()->param($name)`.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
All Controllers have access to `$this->getRequest()` for the request object and `$this->getResponse()` for the response.
|
All Controllers have access to `$this->getRequest()` for the request object and `$this->getResponse()` for the response.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
Here is what those parameters would look like for certain requests
|
Here is what those parameters would look like for certain requests
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// GET /teams/
|
// GET /teams/
|
||||||
|
|
||||||
print_r($this->getRequest()->params());
|
print_r($this->getRequest()->params());
|
||||||
@ -83,24 +86,24 @@ Here is what those parameters would look like for certain requests
|
|||||||
// [Name] => null
|
// [Name] => null
|
||||||
// )
|
// )
|
||||||
|
|
||||||
You can also fetch one parameter at a time.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
|
|
||||||
// GET /teams/players/1/
|
// GET /teams/players/1/
|
||||||
|
|
||||||
echo $this->getRequest()->param('ID');
|
echo $this->getRequest()->param('ID');
|
||||||
// returns '1'
|
// returns '1'
|
||||||
|
|
||||||
|
```
|
||||||
## URL Patterns
|
## URL Patterns
|
||||||
|
|
||||||
The [api:RequestHandler] class will parse all rules you specify against the following patterns. The most specific rule
|
The [api:RequestHandler] class will parse all rules you specify against the following patterns. The most specific rule
|
||||||
will be the one followed for the response.
|
will be the one followed for the response.
|
||||||
|
|
||||||
<div class="alert">
|
[alert]
|
||||||
A rule must always start with alphabetical ([A-Za-z]) characters or a $Variable declaration
|
A rule must always start with alphabetical ([A-Za-z]) characters or a $Variable declaration
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
| Pattern | Description |
|
| Pattern | Description |
|
||||||
| ----------- | --------------- |
|
| ----------- | --------------- |
|
||||||
@ -108,40 +111,40 @@ A rule must always start with alphabetical ([A-Za-z]) characters or a $Variable
|
|||||||
| `!` | **Require Variable** - Placing this after a parameter variable requires data to be present for the rule to match |
|
| `!` | **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 |
|
| `//` | **Shift Point** - Declares that only variables denoted with a $ are parsed into the $params AFTER this point in the regex |
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
'teams/$Action/$ID/$OtherID': 'TeamController'
|
'teams/$Action/$ID/$OtherID': 'TeamController'
|
||||||
|
|
||||||
# /teams/
|
# /teams/
|
||||||
# /teams/players/
|
# /teams/players/
|
||||||
# /teams/
|
# /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
|
matching controller. The `TeamsController` is passed an optional action, id and other id parameters to do any more
|
||||||
decision making.
|
decision making.
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
'teams/$Action!/$ID!/': 'TeamController'
|
'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
|
`$Action` and `$ID` are required. Any requests to `team/` will result in a `404` error rather than being handed off to
|
||||||
the `TeamController`.
|
the `TeamController`.
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
`admin/help//$Action/$ID`: 'AdminHelp'
|
`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 `//`).
|
start parsing variables and the appropriate controller action AFTER the `//`).
|
||||||
|
|
||||||
|
|
||||||
## URL Handlers
|
## URL Handlers
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
You **must** use the **$url_handlers** static array described here if your URL
|
You **must** use the **$url_handlers** static array described here if your URL
|
||||||
pattern does not use the Controller class's default pattern of
|
pattern does not use the Controller class's default pattern of
|
||||||
`$Action//$ID/$OtherID`. If you fail to do so, and your pattern has more than
|
`$Action//$ID/$OtherID`. If you fail to do so, and your pattern has more than
|
||||||
2 parameters, your controller will throw the error "I can't handle sub-URLs of
|
2 parameters, your controller will throw the error "I can't handle sub-URLs of
|
||||||
a *class name* object" with HTTP status 404.
|
a *class name* object" with HTTP status 404.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
In the above example the URLs were configured using the [api:Director] rules in the **routes.yml** file. Alternatively
|
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
|
you can specify these in your Controller class via the **$url_handlers** static array. This array is processed by the
|
||||||
@ -152,7 +155,7 @@ This is useful when you want to provide custom actions for the mapping of `teams
|
|||||||
|
|
||||||
**mysite/code/controllers/TeamController.php**
|
**mysite/code/controllers/TeamController.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class TeamController extends Controller {
|
class TeamController extends Controller {
|
||||||
@ -166,7 +169,7 @@ This is useful when you want to provide custom actions for the mapping of `teams
|
|||||||
'coach/$ID/$Name' => 'payroll'
|
'coach/$ID/$Name' => 'payroll'
|
||||||
);
|
);
|
||||||
|
|
||||||
The syntax for the `$url_handlers` array users the same pattern matches as the `YAML` configuration rules.
|
```
|
||||||
|
|
||||||
Now let’s consider a more complex example from a real project, where using
|
Now let’s consider a more complex example from a real project, where using
|
||||||
**$url_handlers** is mandatory. In this example, the URLs are of the form
|
**$url_handlers** is mandatory. In this example, the URLs are of the form
|
||||||
@ -175,7 +178,7 @@ class specifies the URL pattern in `$url_handlers`. Notice that it defines 5
|
|||||||
parameters.
|
parameters.
|
||||||
|
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class FeedController extends ContentController {
|
class FeedController extends ContentController {
|
||||||
|
|
||||||
private static $allowed_actions = array('go');
|
private static $allowed_actions = array('go');
|
||||||
@ -190,15 +193,15 @@ parameters.
|
|||||||
/* more processing goes here */
|
/* more processing goes here */
|
||||||
}
|
}
|
||||||
|
|
||||||
The YAML rule, in contrast, is simple. It needs to provide only enough
|
```
|
||||||
information for the framework to choose the desired controller.
|
information for the framework to choose the desired controller.
|
||||||
|
|
||||||
:::yaml
|
```yaml
|
||||||
Director:
|
Director:
|
||||||
rules:
|
rules:
|
||||||
'feed': 'FeedController'
|
'feed': 'FeedController'
|
||||||
|
|
||||||
## Links
|
```
|
||||||
|
|
||||||
* [api:Controller] API documentation
|
* [api:Controller] API documentation
|
||||||
* [api:Director] API documentation
|
* [api:Director] API documentation
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Access Control
|
title: Access Control
|
||||||
summary: Define allowed behavior and add permission based checks to your Controllers.
|
summary: Define allowed behavior and add permission based checks to your Controllers.
|
||||||
|
icon: user-lock
|
||||||
|
---
|
||||||
# Access Control
|
# Access Control
|
||||||
|
|
||||||
Within your controllers you should declare and restrict what people can see and do to ensure that users cannot run
|
Within your controllers you should declare and restrict what people can see and do to ensure that users cannot run
|
||||||
@ -11,7 +13,7 @@ actions on the website they shouldn't be able to.
|
|||||||
Any action you define on a controller must be defined in a `$allowed_actions` static array. This prevents users from
|
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.
|
directly calling methods that they shouldn't.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
@ -37,14 +39,14 @@ directly calling methods that they shouldn't.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="info">
|
```
|
||||||
If the permission check fails, SilverStripe will return a `403` Forbidden HTTP status.
|
If the permission check fails, SilverStripe will return a `403` Forbidden HTTP status.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
An action named "index" is white listed by default, unless `allowed_actions` is defined as an empty array, or the action
|
An action named "index" is white listed by default, unless `allowed_actions` is defined as an empty array, or the action
|
||||||
is specifically restricted.
|
is specifically restricted.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
@ -54,9 +56,9 @@ is specifically restricted.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
`$allowed_actions` can be defined on `Extension` classes applying to the controller.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyExtension extends Extension {
|
class MyExtension extends Extension {
|
||||||
@ -66,9 +68,9 @@ is specifically restricted.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Only public methods can be made accessible.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
@ -87,9 +89,9 @@ 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.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
@ -114,16 +116,16 @@ If a method on a parent class is overwritten, access control for it has to be re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
Access checks on parent classes need to be overwritten via the [Configuration API](../configuration).
|
Access checks on parent classes need to be overwritten via the [Configuration API](../configuration).
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Forms
|
## Forms
|
||||||
|
|
||||||
Form action methods should **not** be included in `$allowed_actions`. However, the form method **should** be included
|
Form action methods should **not** be included in `$allowed_actions`. However, the form method **should** be included
|
||||||
as an `allowed_action`.
|
as an `allowed_action`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
@ -141,12 +143,12 @@ as an `allowed_action`.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## Action Level Checks
|
```
|
||||||
|
|
||||||
Each method responding to a URL can also implement custom permission checks, e.g. to handle responses conditionally on
|
Each method responding to a URL can also implement custom permission checks, e.g. to handle responses conditionally on
|
||||||
the passed request data.
|
the passed request data.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
@ -164,10 +166,10 @@ the passed request data.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
This is recommended as an addition for `$allowed_actions`, in order to handle more complex checks, rather than a
|
This is recommended as an addition for `$allowed_actions`, in order to handle more complex checks, rather than a
|
||||||
replacement.
|
replacement.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Controller Level Checks
|
## Controller Level Checks
|
||||||
|
|
||||||
@ -175,10 +177,10 @@ After checking for allowed_actions, each controller invokes its `init()` method,
|
|||||||
common state, If an `init()` method returns a `SS_HTTPResponse` with either a 3xx or 4xx HTTP status code, it'll abort
|
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.
|
execution. This behavior can be used to implement permission checks.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
`init` is called for any possible action on the controller and before any specific method such as `index`.
|
`init` is called for any possible action on the controller and before any specific method such as `index`.
|
||||||
</div>
|
[/info]
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
@ -194,7 +196,7 @@ execution. This behavior can be used to implement permission checks.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## Related Documentation
|
```
|
||||||
|
|
||||||
* [Security](../security)
|
* [Security](../security)
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Redirection
|
title: Redirection
|
||||||
summary: Move users around your site using automatic redirection.
|
summary: Move users around your site using automatic redirection.
|
||||||
|
icon: reply
|
||||||
|
---
|
||||||
# Redirection
|
# Redirection
|
||||||
|
|
||||||
Controllers can facilitate redirecting users from one place to another using `HTTP` redirection using the `Location`
|
Controllers can facilitate redirecting users from one place to another using `HTTP` redirection using the `Location`
|
||||||
@ -8,7 +10,7 @@ HTTP header.
|
|||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$this->redirect('goherenow');
|
$this->redirect('goherenow');
|
||||||
// redirect to Page::goherenow(), i.e on the contact-us page this will redirect to /contact-us/goherenow/
|
// redirect to Page::goherenow(), i.e on the contact-us page this will redirect to /contact-us/goherenow/
|
||||||
|
|
||||||
@ -21,26 +23,26 @@ HTTP header.
|
|||||||
$this->redirectBack();
|
$this->redirectBack();
|
||||||
// go back to the previous page.
|
// 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
|
The `redirect()` method takes an optional HTTP status code, either `301` for permanent redirects, or `302` for
|
||||||
temporary redirects (default).
|
temporary redirects (default).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$this->redirect('/', 302);
|
$this->redirect('/', 302);
|
||||||
// go back to the homepage, don't cache that this page has moved
|
// 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 '~'
|
Controllers can specify redirections in the `$url_handlers` property rather than defining a method by using the '~'
|
||||||
operator.
|
operator.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
private static $url_handlers = array(
|
private static $url_handlers = array(
|
||||||
'players/john' => '~>coach'
|
'players/john' => '~>coach'
|
||||||
);
|
);
|
||||||
|
|
||||||
For more information on `$url_handlers` see the [Routing](routing) documenation.
|
```
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Request Filters
|
title: Request Filters
|
||||||
summary: Create objects for modifying request and response objects across controllers.
|
summary: Create objects for modifying request and response objects across controllers.
|
||||||
|
icon: filter
|
||||||
|
---
|
||||||
# Request Filters
|
# Request Filters
|
||||||
|
|
||||||
[api:RequestFilter] is an interface that provides two key methods. `preRequest` and `postRequest`. These methods are
|
[api:RequestFilter] is an interface that provides two key methods. `preRequest` and `postRequest`. These methods are
|
||||||
@ -9,7 +11,7 @@ perform operations wrapped around responses and request objects. A `RequestFilte
|
|||||||
|
|
||||||
**mysite/code/CustomRequestFilter.php**
|
**mysite/code/CustomRequestFilter.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class CustomRequestFilter implements RequestFilter {
|
class CustomRequestFilter implements RequestFilter {
|
||||||
@ -38,18 +40,18 @@ perform operations wrapped around responses and request objects. A `RequestFilte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
After defining the `RequestFilter`, add it as an allowed `filter` through the [Configuration API](../configuration)
|
```
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Injector:
|
Injector:
|
||||||
RequestProcessor:
|
RequestProcessor:
|
||||||
properties:
|
properties:
|
||||||
filters:
|
filters:
|
||||||
- '%$CustomRequestFilter'
|
- '%$CustomRequestFilter'
|
||||||
|
|
||||||
## API Documentation
|
```
|
||||||
|
|
||||||
* [api:RequestFilter]
|
* [api:RequestFilter]
|
||||||
* [api:RequestProcessor]
|
* [api:RequestProcessor]
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
---
|
||||||
title: Controllers
|
title: Controllers
|
||||||
summary: Controllers form the backbone of your SilverStripe application. They handle routing URLs to your templates.
|
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.
|
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
|
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
|
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
|
information like the URL, any parameters and where they've come from. The response on the other hand is the actual
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
|
---
|
||||||
title: Introduction to Forms
|
title: Introduction to Forms
|
||||||
summary: An introduction to creating a Form instance and handling submissions.
|
summary: An introduction to creating a Form instance and handling submissions.
|
||||||
|
iconBrand: wpforms
|
||||||
|
---
|
||||||
# Forms
|
# Forms
|
||||||
|
|
||||||
The HTML `Form` is the most used way to interact with a user. SilverStripe provides classes to generate forms through
|
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].
|
the [api:Form] class, [api:FormField] instances to capture data and submissions through [api:FormAction].
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
See the [Forms Tutorial](../../tutorials/forms/) for a step by step process of creating a `Form`
|
See the [Forms Tutorial](../../tutorials/forms/) for a step by step process of creating a `Form`
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Creating a Form
|
## Creating a Form
|
||||||
|
|
||||||
Creating a [api:Form] has the following signature.
|
Creating a [api:Form] has the following signature.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$form = new Form(
|
$form = new Form(
|
||||||
$controller, // the Controller to render this form on
|
$controller, // the Controller to render this form on
|
||||||
$name, // name of the method that returns this form on the controller
|
$name, // name of the method that returns this form on the controller
|
||||||
@ -23,11 +25,11 @@ Creating a [api:Form] has the following signature.
|
|||||||
$required // optional use of RequiredFields object
|
$required // optional use of RequiredFields object
|
||||||
);
|
);
|
||||||
|
|
||||||
In practice, this looks like:
|
```
|
||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page_Controller extends ContentController {
|
class Page_Controller extends ContentController {
|
||||||
@ -59,17 +61,17 @@ In practice, this looks like:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
**mysite/templates/Page.ss**
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$HelloForm
|
$HelloForm
|
||||||
|
|
||||||
|
```
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
The examples above use `FormField::create()` instead of the `new` operator (`new FormField()`). These are functionally
|
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
|
equivalent, but allows PHP to chain operations like `setTitle()` without assigning the field instance to a temporary
|
||||||
variable.
|
variable.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
When constructing the `Form` instance (`new Form($controller, $name)`) both controller and name are required. The
|
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
|
`$controller` and `$name` are used to allow SilverStripe to calculate the origin of the `Form object`. When a user
|
||||||
@ -80,15 +82,15 @@ the [api:FormActions]. The URL is known as the `$controller` instance will know
|
|||||||
Because the `HelloForm()` method will be the location the user is taken to, it needs to be handled like any other
|
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.
|
controller action. To grant it access through URLs, we add it to the `$allowed_actions` array.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
private static $allowed_actions = array(
|
private static $allowed_actions = array(
|
||||||
'HelloForm'
|
'HelloForm'
|
||||||
);
|
);
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
Form actions (`doSayHello`), on the other hand, should _not_ be included in `$allowed_actions`; these are handled
|
Form actions (`doSayHello`), on the other hand, should _not_ be included in `$allowed_actions`; these are handled
|
||||||
separately through [api:Form::httpSubmission()].
|
separately through [api:Form::httpSubmission()].
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
|
|
||||||
## Adding FormFields
|
## Adding FormFields
|
||||||
@ -96,17 +98,17 @@ separately through [api:Form::httpSubmission()].
|
|||||||
Fields in a [api:Form] are represented as a single [api:FieldList] instance containing subclasses of [api:FormField].
|
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].
|
Some common examples are [api:TextField] or [api:DropdownField].
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
TextField::create($name, $title, $value);
|
TextField::create($name, $title, $value);
|
||||||
|
|
||||||
<div class="info" markdown='1'>
|
```
|
||||||
A list of the common FormField subclasses is available on the [Common Subclasses](field_types/common_subclasses/) page.
|
A list of the common FormField subclasses is available on the [Common Subclasses](field_types/common_subclasses/) page.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
The fields are added to the [api:FieldList] `fields` property on the `Form` and can be modified at up to the point the
|
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.
|
`Form` is rendered.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields = new FieldList(
|
$fields = new FieldList(
|
||||||
TextField::create('Name'),
|
TextField::create('Name'),
|
||||||
EmailField::create('Email')
|
EmailField::create('Email')
|
||||||
@ -120,9 +122,9 @@ The fields are added to the [api:FieldList] `fields` property on the `Form` and
|
|||||||
// to fetch the current fields..
|
// to fetch the current fields..
|
||||||
$fields = $form->getFields();
|
$fields = $form->getFields();
|
||||||
|
|
||||||
A field can be appended to the [api:FieldList].
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields = $form->Fields();
|
$fields = $form->Fields();
|
||||||
|
|
||||||
// add a field
|
// add a field
|
||||||
@ -138,33 +140,33 @@ A field can be appended to the [api:FieldList].
|
|||||||
$fields->insertBefore(Tab::create(...), 'Main');
|
$fields->insertBefore(Tab::create(...), 'Main');
|
||||||
// Note: you need to create and position the new tab prior to adding fields via addFieldToTab()
|
// Note: you need to create and position the new tab prior to adding fields via addFieldToTab()
|
||||||
|
|
||||||
Fields can be fetched after they have been added in.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$email = $form->Fields()->dataFieldByName('Email');
|
$email = $form->Fields()->dataFieldByName('Email');
|
||||||
$email->setTitle('Your Email Address');
|
$email->setTitle('Your Email Address');
|
||||||
|
|
||||||
Fields can be removed from the form.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$form->getFields()->removeByName('Email');
|
$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`
|
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
|
and `removeFieldByTab` to ensure the fields are on the correct interface. See [Tabbed Forms](tabbed_forms) for more
|
||||||
information on the CMS interface.
|
information on the CMS interface.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## Modifying FormFields
|
## Modifying FormFields
|
||||||
|
|
||||||
Each [api:FormField] subclass has a number of methods you can call on it to customise its' behavior or HTML markup. The
|
Each [api:FormField] subclass has a number of methods you can call on it to customise its' behavior or HTML markup. The
|
||||||
default `FormField` object has several methods for doing common operations.
|
default `FormField` object has several methods for doing common operations.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
Most of the `set` operations will return the object back so methods can be chained.
|
Most of the `set` operations will return the object back so methods can be chained.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$field = new TextField(..);
|
$field = new TextField(..);
|
||||||
|
|
||||||
$field
|
$field
|
||||||
@ -172,13 +174,13 @@ Most of the `set` operations will return the object back so methods can be chain
|
|||||||
->setAttribute('placeholder', 'Enter a value..')
|
->setAttribute('placeholder', 'Enter a value..')
|
||||||
->setTitle('');
|
->setTitle('');
|
||||||
|
|
||||||
### Custom Templates
|
```
|
||||||
|
|
||||||
The [api:Form] HTML markup and each of the [api:FormField] instances are rendered into templates. You can provide custom
|
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 by using the `setTemplate` method on either the `Form` or `FormField`. For more details on providing custom
|
||||||
templates see [Form Templates](form_templates)
|
templates see [Form Templates](form_templates)
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$form = new Form(..);
|
$form = new Form(..);
|
||||||
|
|
||||||
$form->setTemplate('CustomForm');
|
$form->setTemplate('CustomForm');
|
||||||
@ -189,18 +191,18 @@ templates see [Form Templates](form_templates)
|
|||||||
$field->setTemplate('CustomTextField');
|
$field->setTemplate('CustomTextField');
|
||||||
$field->setFieldHolderTemplate('CustomTextField_Holder');
|
$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
|
[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.
|
user presses the button, the form is submitted to the corresponding method.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
FormAction::create($action, $title);
|
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.
|
on the form.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function MyForm() {
|
public function MyForm() {
|
||||||
$fields = new FieldList(..);
|
$fields = new FieldList(..);
|
||||||
|
|
||||||
@ -234,7 +236,7 @@ on the 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
|
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:
|
`doSecondaryFormAction` method. This action can be defined (in order) on either:
|
||||||
|
|
||||||
@ -242,17 +244,17 @@ with the particular button. In the previous example, clicking the 'Another Butto
|
|||||||
* The `Form` instance.
|
* The `Form` instance.
|
||||||
* The `Controller` instance.
|
* The `Controller` instance.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
If the `$action` method cannot be found on any of those or is marked as `private` or `protected`, an error will be
|
If the `$action` method cannot be found on any of those or is marked as `private` or `protected`, an error will be
|
||||||
thrown.
|
thrown.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
The `$action` method takes two arguments:
|
The `$action` method takes two arguments:
|
||||||
|
|
||||||
* `$data` an array containing the values of the form mapped from `$name => $value`
|
* `$data` an array containing the values of the form mapped from `$name => $value`
|
||||||
* `$form` the submitted [api:Form] instance.
|
* `$form` the submitted [api:Form] instance.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page_Controller extends ContentController {
|
class Page_Controller extends ContentController {
|
||||||
@ -292,7 +294,7 @@ The `$action` method takes two arguments:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## Validation
|
```
|
||||||
|
|
||||||
Form validation is handled by the [api:Validator] class and the `validator` property on the `Form` object. The validator
|
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
|
is provided with a name of each of the [api:FormField]s to validate and each `FormField` instance is responsible for
|
||||||
@ -300,14 +302,14 @@ validating its' own data value.
|
|||||||
|
|
||||||
For more information, see the [Form Validation](validation) documentation.
|
For more information, see the [Form Validation](validation) documentation.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$validator = new RequiredFields(array(
|
$validator = new RequiredFields(array(
|
||||||
'Name', 'Email'
|
'Name', 'Email'
|
||||||
));
|
));
|
||||||
|
|
||||||
$form = new Form($this, 'MyForm', $fields, $actions, $validator);
|
$form = new Form($this, 'MyForm', $fields, $actions, $validator);
|
||||||
|
|
||||||
## API Documentation
|
```
|
||||||
|
|
||||||
* [api:Form]
|
* [api:Form]
|
||||||
* [api:FormField]
|
* [api:FormField]
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
|
---
|
||||||
title: Form Validation
|
title: Form Validation
|
||||||
summary: Validate form data through the server side validation API.
|
summary: Validate form data through the server side validation API.
|
||||||
|
icon: check-square
|
||||||
|
---
|
||||||
# Form Validation
|
# Form Validation
|
||||||
|
|
||||||
SilverStripe provides server-side form validation out of the box through the [api:Validator] class and its' child class
|
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
|
[api:RequiredFields]. A single `Validator` instance is set on each `Form`. Validators are implemented as an argument to
|
||||||
the [api:Form] constructor or through the function `setValidator`.
|
the [api:Form] constructor or through the function `setValidator`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page_Controller extends ContentController {
|
class Page_Controller extends ContentController {
|
||||||
@ -45,25 +47,25 @@ the [api:Form] constructor or through the function `setValidator`.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
`doSubmitForm` method is called.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
Each individual [api:FormField] instance is responsible for validating the submitted content through the
|
Each individual [api:FormField] instance is responsible for validating the submitted content through the
|
||||||
[api:FormField::validate()] method. By default, this just checks the value exists. Fields like `EmailField` override
|
[api:FormField::validate()] method. By default, this just checks the value exists. Fields like `EmailField` override
|
||||||
`validate` to check for a specific format.
|
`validate` to check for a specific format.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
Subclasses of `FormField` can define their own version of `validate` to provide custom validation rules such as the
|
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
|
above example with the `Email` validation. The `validate` method on `FormField` takes a single argument of the current
|
||||||
`Validator` instance.
|
`Validator` instance.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
The data value of the `FormField` submitted is not passed into validate. It is stored in the `value` property through
|
The data value of the `FormField` submitted is not passed into validate. It is stored in the `value` property through
|
||||||
the `setValue` method.
|
the `setValue` method.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function validate($validator) {
|
public function validate($validator) {
|
||||||
if($this->value == 10) {
|
if($this->value == 10) {
|
||||||
return false;
|
return false;
|
||||||
@ -72,12 +74,12 @@ the `setValue` method.
|
|||||||
return true;
|
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.
|
a validation error on the page.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
You can also override the entire `Form` validation by subclassing `Form` and defining a `validate` method on the form.
|
You can also override the entire `Form` validation by subclassing `Form` and defining a `validate` method on the form.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
Say we need a custom `FormField` which requires the user input a value in a `TextField` between 2 and 5. There would be
|
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:
|
two ways to go about this:
|
||||||
@ -87,7 +89,7 @@ the same validation logic applied to it throughout.
|
|||||||
|
|
||||||
**mysite/code/formfields/CustomNumberField.php**
|
**mysite/code/formfields/CustomNumberField.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class CustomNumberField extends TextField {
|
class CustomNumberField extends TextField {
|
||||||
@ -112,11 +114,11 @@ the same validation logic applied to it throughout.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
reusable and would not be possible within the `CMS` or other automated `UI` but does not rely on creating custom
|
||||||
`FormField` classes.
|
`FormField` classes.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page_Controller extends ContentController {
|
class Page_Controller extends ContentController {
|
||||||
@ -152,34 +154,34 @@ reusable and would not be possible within the `CMS` or other automated `UI` but
|
|||||||
return $this->redirectBack();
|
return $this->redirectBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$form->sessionMessage("You have been added to our mailing list", 'good');
|
$form->sessionMessage("You have been added to our mailing list", 'good');
|
||||||
|
|
||||||
return $this->redirectBack();
|
return $this->redirectBack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
## Server-side validation messages
|
## Server-side validation messages
|
||||||
|
|
||||||
If a `FormField` fails to pass `validate()` the default error message is returned.
|
If a `FormField` fails to pass `validate()` the default error message is returned.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
'$Name' is required
|
'$Name' is required
|
||||||
|
|
||||||
Use `setCustomValidationMessage` to provide a custom message.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$field = new TextField(..);
|
$field = new TextField(..);
|
||||||
$field->setCustomValidationMessage('Whoops, looks like you have missed me!');
|
$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
|
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
|
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
|
[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.
|
classes added to each input. For Parsley we can structure the form like.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$form = new Form(..);
|
$form = new Form(..);
|
||||||
$form->setAttribute('data-parsley-validate', true);
|
$form->setAttribute('data-parsley-validate', true);
|
||||||
|
|
||||||
@ -188,7 +190,7 @@ classes added to each input. For Parsley we can structure the form like.
|
|||||||
$field->setAttribute('required', true);
|
$field->setAttribute('required', true);
|
||||||
$field->setAttribute('data-parsley-mincheck', '2');
|
$field->setAttribute('data-parsley-mincheck', '2');
|
||||||
|
|
||||||
|
```
|
||||||
## Model Validation
|
## Model Validation
|
||||||
|
|
||||||
An alternative (or additional) approach to validation is to place it directly on the database model. SilverStripe
|
An alternative (or additional) approach to validation is to place it directly on the database model. SilverStripe
|
||||||
@ -202,11 +204,11 @@ call `setValidator` easily. However, a `DataObject` can provide its' own `Valida
|
|||||||
`getCMSValidator()` method. The CMS interfaces such as [api:LeftAndMain], [api:ModelAdmin] and [api:GridField] will
|
`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.
|
respect the provided `Validator` and handle displaying error and success responses to the user.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
Again, custom error messages can be provided through the `FormField`
|
Again, custom error messages can be provided through the `FormField`
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page extends SiteTree {
|
class Page extends SiteTree {
|
||||||
@ -229,7 +231,7 @@ Again, custom error messages can be provided through the `FormField`
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
## API Documentation
|
```
|
||||||
|
|
||||||
* [api:RequiredFields]
|
* [api:RequiredFields]
|
||||||
* [api:Validator]
|
* [api:Validator]
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
|
---
|
||||||
title: Form Templates
|
title: Form Templates
|
||||||
summary: Customize the generated HTML for a FormField or an entire Form.
|
summary: Customize the generated HTML for a FormField or an entire Form.
|
||||||
|
icon: file-code
|
||||||
|
---
|
||||||
# Form Templates
|
# Form Templates
|
||||||
|
|
||||||
Most markup generated in SilverStripe can be replaced by custom templates. Both [api:Form] and [api:FormField] instances
|
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`.
|
can be rendered out using custom templates using `setTemplate`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$form = new Form(..);
|
$form = new Form(..);
|
||||||
$form->setTemplate('MyCustomFormTemplate');
|
$form->setTemplate('MyCustomFormTemplate');
|
||||||
|
|
||||||
@ -14,26 +16,26 @@ can be rendered out using custom templates using `setTemplate`.
|
|||||||
$field = new TextField(..);
|
$field = new TextField(..);
|
||||||
$field->setTemplate('MyCustomTextField');
|
$field->setTemplate('MyCustomTextField');
|
||||||
|
|
||||||
Both `MyCustomTemplate.ss` and `MyCustomTextField.ss` should be located in **mysite/templates/forms/** or the same directory as the core.
|
```
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
It's recommended to copy the contents of the template you're going to replace and use that as a start. For instance, if
|
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
|
you want to create a `MyCustomFormTemplate` copy the contents of `Form.ss` to a `MyCustomFormTemplate.ss` file and
|
||||||
modify as you need.
|
modify as you need.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
By default, Form and Fields follow the SilverStripe Template convention and are rendered into templates of the same
|
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
|
class name (i.e EmailField will attempt to render into `EmailField.ss` and if that isn't found, `TextField.ss` or
|
||||||
finally `FormField.ss`).
|
finally `FormField.ss`).
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
While you can override all templates using normal view inheritance (i.e defining a `Form.ss`) other modules may rely on
|
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.
|
the core template structure. It is recommended to use `setTemplate` and unique templates for specific forms.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
For [api:FormField] instances, there are several other templates that are used on top of the main `setTemplate`.
|
For [api:FormField] instances, there are several other templates that are used on top of the main `setTemplate`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$field = new TextField();
|
$field = new TextField();
|
||||||
|
|
||||||
$field->setTemplate('CustomTextField');
|
$field->setTemplate('CustomTextField');
|
||||||
@ -56,7 +58,7 @@ For [api:FormField] instances, there are several other templates that are used o
|
|||||||
// field is embedded within another field. For example, if the field is
|
// field is embedded within another field. For example, if the field is
|
||||||
// part of a `FieldGroup` or `CompositeField` alongside other fields.
|
// 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.
|
well as the available syntax, see the [Templates](../templates) documentation.
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Form Security
|
title: Form Security
|
||||||
summary: Ensure Forms are secure against Cross-Site Request Forgery attacks, bots and other malicious intent.
|
summary: Ensure Forms are secure against Cross-Site Request Forgery attacks, bots and other malicious intent.
|
||||||
|
icon: shield-alt
|
||||||
|
---
|
||||||
# Form Security
|
# Form Security
|
||||||
|
|
||||||
Whenever you are accepting or asking users to input data to your application there comes an added responsibility that it
|
Whenever you are accepting or asking users to input data to your application there comes an added responsibility that it
|
||||||
@ -13,37 +15,37 @@ SilverStripe protect users against [Cross-Site Request Forgery](https://www.owas
|
|||||||
random string generated by [api:SecurityToken] to identify the particular user request vs a third-party forging fake
|
random string generated by [api:SecurityToken] to identify the particular user request vs a third-party forging fake
|
||||||
requests.
|
requests.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
For more information on Cross-Site Request Forgery, consult the [OWASP](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
|
For more information on Cross-Site Request Forgery, consult the [OWASP](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
|
||||||
website.
|
website.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
The `SecurityToken` automatically added looks something like:
|
The `SecurityToken` automatically added looks something like:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$form = new Form(..);
|
$form = new Form(..);
|
||||||
echo $form->getSecurityToken()->getValue();
|
echo $form->getSecurityToken()->getValue();
|
||||||
|
|
||||||
// 'c443076989a7f24cf6b35fe1360be8683a753e2c'
|
// 'c443076989a7f24cf6b35fe1360be8683a753e2c'
|
||||||
|
|
||||||
This token value is passed through the rendered Form HTML as a [api:HiddenField].
|
```
|
||||||
|
|
||||||
:::html
|
```html
|
||||||
<input type="hidden" name="SecurityID" value="c443076989a7f24cf6b35fe1360be8683a753e2c" class="hidden" />
|
<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
|
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).
|
normally require a security token).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$form = new Form(..);
|
$form = new Form(..);
|
||||||
$form->disableSecurityToken();
|
$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
|
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.
|
application up to `CSRF` security holes.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## Strict Form Submission
|
## Strict Form Submission
|
||||||
|
|
||||||
@ -51,7 +53,7 @@ Forms should be limited to the intended HTTP verb (mostly `GET` or `POST`) to fu
|
|||||||
this check, forms that rely on `GET` can be submitted via `POST` or `PUT` or vice-versa potentially leading to
|
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.
|
application errors or edge cases.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$form = new Form(..);
|
$form = new Form(..);
|
||||||
|
|
||||||
$form->setFormMethod('POST');
|
$form->setFormMethod('POST');
|
||||||
@ -60,7 +62,7 @@ application errors or edge cases.
|
|||||||
// or alternative short notation..
|
// or alternative short notation..
|
||||||
$form->setFormMethod('POST', true);
|
$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
|
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)
|
functionality is available as an additional [Spam Protection](https://github.com/silverstripe/silverstripe-spamprotection)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Form Transformations
|
title: Form Transformations
|
||||||
summary: Provide read-only and disabled views of your Form data.
|
summary: Provide read-only and disabled views of your Form data.
|
||||||
|
icon: random
|
||||||
|
---
|
||||||
# Read-only and Disabled Forms
|
# 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
|
[api:Form] and [api:FormField] instances can be turned into a read-only version for things like confirmation pages or
|
||||||
@ -9,21 +11,21 @@ when certain fields cannot be edited due to permissions. Creating the form is do
|
|||||||
|
|
||||||
To make an entire [api:Form] read-only.
|
To make an entire [api:Form] read-only.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$form = new Form(..);
|
$form = new Form(..);
|
||||||
$form->makeReadonly();
|
$form->makeReadonly();
|
||||||
|
|
||||||
|
```
|
||||||
To make all the fields within a [api:FieldList] read-only (i.e to make fields read-only but not buttons).
|
To make all the fields within a [api:FieldList] read-only (i.e to make fields read-only but not buttons).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields = new FieldList(..);
|
$fields = new FieldList(..);
|
||||||
$fields = $fields->makeReadonly();
|
$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
|
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
|
```php
|
||||||
$field = new TextField(..);
|
$field = new TextField(..);
|
||||||
$field = $field->performReadonlyTransformation();
|
$field = $field->performReadonlyTransformation();
|
||||||
|
|
||||||
@ -39,15 +41,7 @@ To make a [api:FormField] read-only you need to know the name of the form field
|
|||||||
$field
|
$field
|
||||||
);
|
);
|
||||||
|
|
||||||
## Disabled FormFields
|
```
|
||||||
|
|
||||||
Disabling [api:FormField] instances, sets the `disabled` property on the class. This will use the same HTML markup as
|
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.
|
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" />'
|
|
@ -1,55 +1,56 @@
|
|||||||
|
---
|
||||||
title: Tabbed Forms
|
title: Tabbed Forms
|
||||||
summary: Find out how CMS interfaces use jQuery UI tabs to provide nested FormFields.
|
summary: Find out how CMS interfaces use jQuery UI tabs to provide nested FormFields.
|
||||||
|
---
|
||||||
# Tabbed Forms
|
# Tabbed Forms
|
||||||
|
|
||||||
SilverStripe's [api:FormScaffolder] can automatically generate [api:Form] instances for certain database models. In the
|
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
|
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.
|
parts of the data model.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
All interfaces within the CMS such as [api:ModelAdmin] and [api:LeftAndMain] use tabbed interfaces by default.
|
All interfaces within the CMS such as [api:ModelAdmin] and [api:LeftAndMain] use tabbed interfaces by default.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
When dealing with tabbed forms, modifying the fields in the form has a few differences. Each [api:Tab] will be given a
|
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].
|
name, and normally they all exist under the `Root` [api:TabSet].
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
[api:TabSet] instances can contain child [api:Tab] and further [api:TabSet] instances, however the CMS UI will only
|
[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].
|
display up to two levels of tabs in the interface. If you want to group data further than that, try [api:ToggleField].
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Adding a field to a tab
|
## Adding a field to a tab
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields->addFieldToTab('Root.Main', new TextField(..));
|
$fields->addFieldToTab('Root.Main', new TextField(..));
|
||||||
|
|
||||||
## Removing a field from a tab
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields->removeFieldFromTab('Root.Main', 'Content');
|
$fields->removeFieldFromTab('Root.Main', 'Content');
|
||||||
|
|
||||||
## Creating a new tab
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields->addFieldToTab('Root.MyNewTab', new TextField(..));
|
$fields->addFieldToTab('Root.MyNewTab', new TextField(..));
|
||||||
|
|
||||||
## Moving a field between tabs
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$content = $fields->dataFieldByName('Content');
|
$content = $fields->dataFieldByName('Content');
|
||||||
|
|
||||||
$fields->removeFieldFromTab('Root.Main', 'Content');
|
$fields->removeFieldFromTab('Root.Main', 'Content');
|
||||||
$fields->addFieldToTab('Root.MyContent', $content);
|
$fields->addFieldToTab('Root.MyContent', $content);
|
||||||
|
|
||||||
## Add multiple fields at once
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields->addFieldsToTab('Root.Content', array(
|
$fields->addFieldsToTab('Root.Content', array(
|
||||||
TextField::create('Name'),
|
TextField::create('Name'),
|
||||||
TextField::create('Email')
|
TextField::create('Email')
|
||||||
));
|
));
|
||||||
|
|
||||||
## API Documentation
|
```
|
||||||
|
|
||||||
* [api:FormScaffolder]
|
* [api:FormScaffolder]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: Common FormField type subclasses
|
title: Common FormField type subclasses
|
||||||
summary: A table containing a list of the common FormField subclasses.
|
summary: A table containing a list of the common FormField subclasses.
|
||||||
|
---
|
||||||
# Common FormField type subclasses
|
# Common FormField type subclasses
|
||||||
|
|
||||||
This is a high level overview of available [api:FormField] subclasses. An automatically generated list is available
|
This is a high level overview of available [api:FormField] subclasses. An automatically generated list is available
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: DateField
|
title: DateField
|
||||||
summary: How to format and use the DateField class.
|
summary: How to format and use the DateField class.
|
||||||
|
---
|
||||||
# DateField
|
# DateField
|
||||||
|
|
||||||
This `FormField` subclass lets you display an editable date, either in a single text input field, or in three separate
|
This `FormField` subclass lets you display an editable date, either in a single text input field, or in three separate
|
||||||
@ -10,7 +11,7 @@ The following example will add a simple DateField to your Page, allowing you to
|
|||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page extends SiteTree {
|
class Page extends SiteTree {
|
||||||
@ -31,17 +32,17 @@ The following example will add a simple DateField to your Page, allowing you to
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## Custom Date Format
|
```
|
||||||
|
|
||||||
A custom date format for a [api:DateField] can be provided through `setConfig`.
|
A custom date format for a [api:DateField] can be provided through `setConfig`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// will display a date in the following format: 31-06-2012
|
// will display a date in the following format: 31-06-2012
|
||||||
DateField::create('MyDate')->setConfig('dateformat', 'dd-MM-yyyy');
|
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).
|
The formats are based on [Zend_Date constants](http://framework.zend.com/manual/1.12/en/zend.date.constants.html).
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
|
|
||||||
## Min and Max Dates
|
## Min and Max Dates
|
||||||
@ -49,35 +50,36 @@ The formats are based on [Zend_Date constants](http://framework.zend.com/manual/
|
|||||||
Sets the minimum and maximum allowed date values using the `min` and `max` configuration settings (in ISO format or
|
Sets the minimum and maximum allowed date values using the `min` and `max` configuration settings (in ISO format or
|
||||||
strtotime()).
|
strtotime()).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
DateField::create('MyDate')
|
DateField::create('MyDate')
|
||||||
->setConfig('min', '-7 days')
|
->setConfig('min', '-7 days')
|
||||||
->setConfig('max', '2012-12-31')
|
->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
|
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.
|
placeholders 'day', 'month' and 'year' are enabled by default.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
DateField::create('MyDate')
|
DateField::create('MyDate')
|
||||||
->setConfig('dmyfields', true)
|
->setConfig('dmyfields', true)
|
||||||
->setConfig('dmyseparator', '/') // set the separator
|
->setConfig('dmyseparator', '/') // set the separator
|
||||||
->setConfig('dmyplaceholders', 'true'); // enable HTML 5 Placeholders
|
->setConfig('dmyplaceholders', 'true'); // enable HTML 5 Placeholders
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
```
|
||||||
Any custom date format settings will be ignored.
|
Any custom date format settings will be ignored.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## Calendar Picker
|
## Calendar Picker
|
||||||
|
|
||||||
The following setting will add a Calendar to a single DateField, using the jQuery UI DatePicker widget.
|
The following setting will add a Calendar to a single DateField, using the jQuery UI DatePicker widget.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
DateField::create('MyDate')
|
DateField::create('MyDate')
|
||||||
->setConfig('showcalendar', true);
|
->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:
|
following constants should at least be safe:
|
||||||
|
|
||||||
Constant | xxxxx
|
Constant | xxxxx
|
||||||
@ -98,10 +100,10 @@ Unfortunately the day- and monthname values in Zend Date do not always match tho
|
|||||||
files, so constants like `EEE` or `MMM`, for day and month names could break validation. To fix this we had to slightly
|
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.
|
alter the jQuery locale files, situated in */framework/thirdparty/jquery-ui/datepicker/i18n/*, to match Zend_Date.
|
||||||
|
|
||||||
<div class="info">
|
[info]
|
||||||
At this moment not all locale files may be present. If a locale file is missing, the DatePicker calendar will fallback
|
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.
|
to 'yyyy-MM-dd' whenever day - and/or monthnames are used. After saving, the correct format will be displayed.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
## Formatting Hints
|
## Formatting Hints
|
||||||
|
|
||||||
@ -109,7 +111,7 @@ It's often not immediate apparent which format a field accepts, and showing the
|
|||||||
of limited use to the average user. An alternative is to show the current date in the desired format alongside the
|
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.
|
field description as an example.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$dateField = DateField::create('MyDate');
|
$dateField = DateField::create('MyDate');
|
||||||
|
|
||||||
// Show long format as text below the field
|
// Show long format as text below the field
|
||||||
@ -121,9 +123,9 @@ field description as an example.
|
|||||||
// Alternatively, set short format as a placeholder in the field
|
// Alternatively, set short format as a placeholder in the field
|
||||||
$dateField->setAttribute('placeholder', $dateField->getConfig('dateformat'));
|
$dateField->setAttribute('placeholder', $dateField->getConfig('dateformat'));
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
Fields scaffolded through [api:DataObject::scaffoldCMSFields()] automatically have a description attached to them.
|
Fields scaffolded through [api:DataObject::scaffoldCMSFields()] automatically have a description attached to them.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Rich-text editing (WYSIWYG)
|
title: Rich-text editing (WYSIWYG)
|
||||||
summary: SilverStripe's use and configuration of TinyMCE html editor.
|
summary: Silverstripe CMS's use and configuration of TinyMCE html editor.
|
||||||
|
icon: file-code
|
||||||
|
---
|
||||||
# Rich-text editing (WYSIWYG)
|
# Rich-text editing (WYSIWYG)
|
||||||
|
|
||||||
Editing and formatting content is the bread and butter of every content management system, which is why SilverStripe
|
Editing and formatting content is the bread and butter of every content management system, which is why SilverStripe
|
||||||
@ -15,7 +17,7 @@ functionality. It is usually added through the [api:DataObject::getCMSFields()]
|
|||||||
|
|
||||||
**mysite/code/MyObject.php**
|
**mysite/code/MyObject.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyObject extends DataObject {
|
class MyObject extends DataObject {
|
||||||
@ -31,7 +33,7 @@ functionality. It is usually added through the [api:DataObject::getCMSFields()]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
### Specify which configuration to use
|
```
|
||||||
|
|
||||||
By default, a config named 'cms' is used in any new [api:HTMLEditorField].
|
By default, a config named 'cms' is used in any new [api:HTMLEditorField].
|
||||||
|
|
||||||
@ -42,7 +44,7 @@ will use the configuration with the name 'myConfig'.
|
|||||||
You can also specify which [api:HtmlEditorConfig] to use on a per field basis via the construct argument.
|
You can also specify which [api:HtmlEditorConfig] to use on a per field basis via the construct argument.
|
||||||
This is particularly useful if you need different configurations for multiple [api:HTMLEditorField] on the same page or form.
|
This is particularly useful if you need different configurations for multiple [api:HTMLEditorField] on the same page or form.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyObject extends DataObject {
|
class MyObject extends DataObject {
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
'Content' => 'HTMLText',
|
'Content' => 'HTMLText',
|
||||||
@ -57,7 +59,7 @@ This is particularly useful if you need different configurations for multiple [a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
In the above example, the 'Content' field will use the default 'cms' config while 'OtherContent' will be using 'myConfig'.
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@ -68,14 +70,14 @@ 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
|
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()`.
|
then set the currently active config using `set_active()`.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
Currently the order in which the `_config.php` files are executed depends on the module directory names. Execution
|
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
|
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.
|
`framework/admin/_config.php` and your modification will disappear.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Adding and removing capabilities
|
## Adding and removing capabilities
|
||||||
|
|
||||||
@ -85,33 +87,33 @@ You can add plugins to the editor using the Framework's [api:HtmlEditorConfig::e
|
|||||||
transparently generate the relevant underlying TinyMCE code.
|
transparently generate the relevant underlying TinyMCE code.
|
||||||
|
|
||||||
**mysite/_config.php**
|
**mysite/_config.php**
|
||||||
:::php
|
```php
|
||||||
HtmlEditorConfig::get('cms')->enablePlugins('media');
|
HtmlEditorConfig::get('cms')->enablePlugins('media');
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
This utilities the TinyMCE's `PluginManager::load` function under the hood (check the
|
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
|
[TinyMCE documentation on plugin loading](http://www.tinymce.com/wiki.php/API3:method.tinymce.AddOnManager.load) for
|
||||||
details).
|
details).
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
Plugins and advanced themes can provide additional buttons that can be added (or removed) through the
|
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:
|
configuration. Here is an example of adding a `ssmacron` button after the `charmap` button:
|
||||||
|
|
||||||
**mysite/_config.php**
|
**mysite/_config.php**
|
||||||
:::php
|
```php
|
||||||
HtmlEditorConfig::get('cms')->insertButtonsAfter('charmap', 'ssmacron');
|
HtmlEditorConfig::get('cms')->insertButtonsAfter('charmap', 'ssmacron');
|
||||||
|
|
||||||
Buttons can also be removed:
|
```
|
||||||
|
|
||||||
**mysite/_config.php**
|
**mysite/_config.php**
|
||||||
:::php
|
```php
|
||||||
HtmlEditorConfig::get('cms')->removeButtons('tablecontrols', 'blockquote', 'hr');
|
HtmlEditorConfig::get('cms')->removeButtons('tablecontrols', 'blockquote', 'hr');
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
Internally [api:HtmlEditorConfig] uses the TinyMCE's `theme_advanced_buttons` option to configure these. See the
|
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)
|
[TinyMCE documentation of this option](http://www.tinymce.com/wiki.php/Configuration:theme_advanced_buttons_1_n)
|
||||||
for more details.
|
for more details.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
### Setting options
|
### Setting options
|
||||||
|
|
||||||
@ -123,7 +125,7 @@ tags](http://www.tinymce.com/wiki.php/Configuration:extended_valid_elements) - t
|
|||||||
from the HTML source by the editor.
|
from the HTML source by the editor.
|
||||||
|
|
||||||
**mysite/_config.php**
|
**mysite/_config.php**
|
||||||
:::php
|
```php
|
||||||
// Add start and type attributes for <ol>, add <object> and <embed> with all attributes.
|
// Add start and type attributes for <ol>, add <object> and <embed> with all attributes.
|
||||||
HtmlEditorConfig::get('cms')->setOption(
|
HtmlEditorConfig::get('cms')->setOption(
|
||||||
'extended_valid_elements',
|
'extended_valid_elements',
|
||||||
@ -137,10 +139,10 @@ from the HTML source by the editor.
|
|||||||
'ol[start|type]'
|
'ol[start|type]'
|
||||||
);
|
);
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
The default setting for the CMS's `extended_valid_elements` we are overriding here can be found in
|
The default setting for the CMS's `extended_valid_elements` we are overriding here can be found in
|
||||||
`framework/admin/_config.php`.
|
`framework/admin/_config.php`.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Writing custom plugins
|
## Writing custom plugins
|
||||||
|
|
||||||
@ -151,7 +153,7 @@ Here is how we can create a project-specific plugin. Create a `mysite/javascript
|
|||||||
button icon - here `myplugin.png` - and the source code - here `editor_plugin.js`. Here is a very simple example of a
|
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:
|
plugin that adds a button to the editor:
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
(function() {
|
(function() {
|
||||||
tinymce.create('tinymce.plugins.myplugin', {
|
tinymce.create('tinymce.plugins.myplugin', {
|
||||||
|
|
||||||
@ -182,13 +184,13 @@ plugin that adds a button to the editor:
|
|||||||
tinymce.PluginManager.add('myplugin', tinymce.plugins.myplugin);
|
tinymce.PluginManager.add('myplugin', tinymce.plugins.myplugin);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
You can then enable this plugin through the [api:HtmlEditorConfig::enablePlugins()]:
|
```
|
||||||
|
|
||||||
**mysite/_config.php**
|
**mysite/_config.php**
|
||||||
:::php
|
```php
|
||||||
HtmlEditorConfig::get('cms')->enablePlugins(array('myplugin' => '../../../mysite/javascript/myplugin/editor_plugin.js'));
|
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`.
|
documentation, or browse through plugins that come with the Framework at `thirdparty/tinymce/plugins`.
|
||||||
|
|
||||||
## Image and media insertion
|
## Image and media insertion
|
||||||
@ -212,9 +214,9 @@ queries to a list of external services if it finds a matching URL. These service
|
|||||||
`Oembed.providers` configuration. Since these requests are performed on page rendering, they typically have a long
|
`Oembed.providers` configuration. Since these requests are performed on page rendering, they typically have a long
|
||||||
cache time (multiple days).
|
cache time (multiple days).
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
To refresh a oEmbed cache, append `?flush=1` to a URL.
|
To refresh a oEmbed cache, append `?flush=1` to a URL.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
To disable oEmbed usage, set the `Oembed.enabled` configuration property to "false".
|
To disable oEmbed usage, set the `Oembed.enabled` configuration property to "false".
|
||||||
|
|
||||||
@ -238,10 +240,10 @@ defaults to the stricter 'xhtml' setting, for example rendering self closing tag
|
|||||||
|
|
||||||
In case you want to adhere to HTML4 instead, use the following configuration:
|
In case you want to adhere to HTML4 instead, use the following configuration:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
HtmlEditorConfig::get('cms')->setOption('element_format', 'html');
|
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
|
`<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.
|
[valid_elements](http://www.tinymce.com/wiki.php/Configuration:valid_elements) configuration setting.
|
||||||
|
|
||||||
@ -261,19 +263,11 @@ back and forth between a content representation the editor can understand, prese
|
|||||||
|
|
||||||
Example: Remove field for "image captions"
|
Example: Remove field for "image captions"
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// File: mysite/code/MyToolbarExtension.php
|
|
||||||
class MyToolbarExtension extends Extension {
|
|
||||||
public function updateFieldsForImage(&$fields, $url, $file) {
|
|
||||||
$fields->removeByName('CaptionText');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:::php
|
|
||||||
// File: mysite/_config.php
|
// File: mysite/_config.php
|
||||||
HtmlEditorField_Toolbar::add_extension('MyToolbarExtension');
|
HtmlEditorField_Toolbar::add_extension('MyToolbarExtension');
|
||||||
|
|
||||||
Adding functionality is a bit more advanced, you'll most likely
|
```
|
||||||
need to add some fields to the PHP forms, as well as write some
|
need to add some fields to the PHP forms, as well as write some
|
||||||
JavaScript to ensure the values from those fields make it into the content
|
JavaScript to ensure the values from those fields make it into the content
|
||||||
elements (and back out in case an existing element gets edited).
|
elements (and back out in case an existing element gets edited).
|
||||||
@ -296,15 +290,7 @@ encapsulated in the [api:HtmlEditorField_Toolbar] class.
|
|||||||
In the CMS, those dialogs are automatically instantiate, but in your own interfaces outside
|
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:
|
of the CMS you have to take care of instantiate yourself:
|
||||||
|
|
||||||
:::php
|
```ss
|
||||||
// File: mysite/code/MyController.php
|
|
||||||
class MyObjectController extends Controller {
|
|
||||||
public function EditorToolbar() {
|
|
||||||
return HtmlEditorField_Toolbar::create($this, "EditorToolbar");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:::ss
|
|
||||||
// File: mysite/templates/MyController.ss
|
// File: mysite/templates/MyController.ss
|
||||||
$Form
|
$Form
|
||||||
<% with $EditorToolbar %>
|
<% with $EditorToolbar %>
|
||||||
@ -312,16 +298,16 @@ of the CMS you have to take care of instantiate yourself:
|
|||||||
$LinkForm
|
$LinkForm
|
||||||
<% end_with %>
|
<% end_with %>
|
||||||
|
|
||||||
Note: The dialogs rely on CMS-access, e.g. for uploading and browsing files,
|
```
|
||||||
so this is considered advanced usage of the field.
|
so this is considered advanced usage of the field.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// File: mysite/_config.php
|
// File: mysite/_config.php
|
||||||
HtmlEditorConfig::get('cms')->disablePlugins('ssbuttons');
|
HtmlEditorConfig::get('cms')->disablePlugins('ssbuttons');
|
||||||
HtmlEditorConfig::get('cms')->removeButtons('sslink', 'ssmedia');
|
HtmlEditorConfig::get('cms')->removeButtons('sslink', 'ssmedia');
|
||||||
HtmlEditorConfig::get('cms')->addButtonsToLine(2, 'link', 'media');
|
HtmlEditorConfig::get('cms')->addButtonsToLine(2, 'link', 'media');
|
||||||
|
|
||||||
### Developing a wrapper to use a different WYSIWYG editors with HTMLEditorField
|
```
|
||||||
|
|
||||||
WYSIWYG editors are complex beasts, so replacing it completely is a difficult task.
|
WYSIWYG editors are complex beasts, so replacing it completely is a difficult task.
|
||||||
The framework provides a wrapper implementation for the basic required functionality,
|
The framework provides a wrapper implementation for the basic required functionality,
|
||||||
@ -339,7 +325,7 @@ Most modern browsers support it, although Internet Explorer only has limited
|
|||||||
support in IE10. Alternatively, you can use the PSpell PHP module for server side checks.
|
support in IE10. Alternatively, you can use the PSpell PHP module for server side checks.
|
||||||
Assuming you have the module installed, here's how you enable its use in `mysite/_config.php`:
|
Assuming you have the module installed, here's how you enable its use in `mysite/_config.php`:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
HtmlEditorConfig::get('cms')->enablePlugins('spellchecker', 'contextmenu');
|
HtmlEditorConfig::get('cms')->enablePlugins('spellchecker', 'contextmenu');
|
||||||
HtmlEditorConfig::get('cms')->addButtonsToLine(2, 'spellchecker');
|
HtmlEditorConfig::get('cms')->addButtonsToLine(2, 'spellchecker');
|
||||||
HtmlEditorConfig::get('cms')->setOption(
|
HtmlEditorConfig::get('cms')->setOption(
|
||||||
@ -348,8 +334,4 @@ Assuming you have the module installed, here's how you enable its use in `mysite
|
|||||||
);
|
);
|
||||||
HtmlEditorConfig::get('cms')->setOption('browser_spellcheck', false);
|
HtmlEditorConfig::get('cms')->setOption('browser_spellcheck', false);
|
||||||
|
|
||||||
Now change the default spellchecker in `framework/thirdparty/tinymce-spellchecker/config.php`:
|
```
|
||||||
|
|
||||||
:::php
|
|
||||||
// ...
|
|
||||||
$config['general.engine'] = 'PSpell';
|
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
|
---
|
||||||
title: GridField
|
title: GridField
|
||||||
summary: How to use the GridField class for managing tabular data.
|
summary: How to use the GridField class for managing tabular data.
|
||||||
|
icon: table
|
||||||
|
---
|
||||||
# GridField
|
# GridField
|
||||||
|
|
||||||
[api:GridField] is SilverStripe's implementation of data grids. The main purpose of the `FormField` is to display
|
[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.
|
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
|
```php
|
||||||
$field = new GridField($name, $title, $list);
|
$field = new GridField($name, $title, $list);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
[hint]
|
||||||
<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`.
|
GridField can only be used with `$list` data sets that are of the type `SS_List` such as `DataList` or `ArrayList`.
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
[api:GridField] powers the automated data UI of [api:ModelAdmin]. For more information about `ModelAdmin` see the
|
[api:GridField] powers the automated data UI of [api:ModelAdmin]. For more information about `ModelAdmin` see the
|
||||||
[Customizing the CMS](/developer_guides/customising_the_admin_interface) guide.
|
[Customizing the CMS](/developer_guides/customising_the_admin_interface) guide.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
Each `GridField` is built from a number of components grouped into the [api:GridFieldConfig]. Without any components,
|
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
|
a `GridField` has almost no functionality. The `GridFieldConfig` instance and the attached [api:GridFieldComponent] are
|
||||||
@ -27,7 +29,7 @@ actions such as deleting records.
|
|||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page extends SiteTree {
|
class Page extends SiteTree {
|
||||||
@ -43,7 +45,7 @@ actions such as deleting records.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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:
|
`GridField` configuration, the default configuration is an instance of [api:GridFieldConfig_Base] which provides:
|
||||||
|
|
||||||
* [api:GridFieldToolbarHeader]
|
* [api:GridFieldToolbarHeader]
|
||||||
@ -58,7 +60,7 @@ the `getConfig()` method on `GridField`.
|
|||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page extends SiteTree {
|
class Page extends SiteTree {
|
||||||
@ -90,10 +92,10 @@ the `getConfig()` method on `GridField`.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
With the `GridFieldConfig` instance, we can modify the behavior of the `GridField`.
|
With the `GridFieldConfig` instance, we can modify the behavior of the `GridField`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// `GridFieldConfig::create()` will create an empty configuration (no components).
|
// `GridFieldConfig::create()` will create an empty configuration (no components).
|
||||||
$config = GridFieldConfig::create();
|
$config = GridFieldConfig::create();
|
||||||
|
|
||||||
@ -103,31 +105,31 @@ With the `GridFieldConfig` instance, we can modify the behavior of the `GridFiel
|
|||||||
// Update the GridField with our custom configuration
|
// Update the GridField with our custom configuration
|
||||||
$gridField->setConfig($config);
|
$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.
|
before another component by passing the second parameter.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$config->addComponent(new GridFieldFilterHeader(), 'GridFieldDataColumns');
|
$config->addComponent(new GridFieldFilterHeader(), 'GridFieldDataColumns');
|
||||||
|
|
||||||
We can add multiple components in one call.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$config->addComponents(
|
$config->addComponents(
|
||||||
new GridFieldDataColumns(),
|
new GridFieldDataColumns(),
|
||||||
new GridFieldToolbarHeader()
|
new GridFieldToolbarHeader()
|
||||||
);
|
);
|
||||||
|
|
||||||
Or, remove a component.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$config->removeComponentsByType('GridFieldDeleteAction');
|
$config->removeComponentsByType('GridFieldDeleteAction');
|
||||||
|
|
||||||
Fetch a component to modify it later on.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$component = $config->getComponentByType('GridFieldFilterHeader')
|
$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
|
Here is a list of components for use bundled with the core framework. Many more components are provided by third-party
|
||||||
modules and extensions.
|
modules and extensions.
|
||||||
|
|
||||||
@ -152,7 +154,7 @@ developers manually adding each component.
|
|||||||
|
|
||||||
A simple read-only and paginated view of records with sortable and searchable headers.
|
A simple read-only and paginated view of records with sortable and searchable headers.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$config = GridFieldConfig_Base::create();
|
$config = GridFieldConfig_Base::create();
|
||||||
|
|
||||||
$gridField->setConfig($config);
|
$gridField->setConfig($config);
|
||||||
@ -165,22 +167,22 @@ A simple read-only and paginated view of records with sortable and searchable he
|
|||||||
// .. new GridFieldPageCount('toolbar-header-right')
|
// .. new GridFieldPageCount('toolbar-header-right')
|
||||||
// .. new GridFieldPaginator($itemsPerPage)
|
// .. new GridFieldPaginator($itemsPerPage)
|
||||||
|
|
||||||
### GridFieldConfig_RecordViewer
|
```
|
||||||
|
|
||||||
Similar to `GridFieldConfig_Base` with the addition support of the ability to view a `GridFieldDetailForm` containing
|
Similar to `GridFieldConfig_Base` with the addition support of the ability to view a `GridFieldDetailForm` containing
|
||||||
a read-only view of the data record.
|
a read-only view of the data record.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
The data row show must be a `DataObject` subclass. The fields displayed in the read-only view come from
|
The data row show must be a `DataObject` subclass. The fields displayed in the read-only view come from
|
||||||
`DataObject::getCMSFields()`.
|
`DataObject::getCMSFields()`.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
The `DataObject` class displayed must define a `canView()` method that returns a boolean on whether the user can view
|
The `DataObject` class displayed must define a `canView()` method that returns a boolean on whether the user can view
|
||||||
this record.
|
this record.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$config = GridFieldConfig_RecordViewer::create();
|
$config = GridFieldConfig_RecordViewer::create();
|
||||||
|
|
||||||
$gridField->setConfig($config);
|
$gridField->setConfig($config);
|
||||||
@ -189,21 +191,21 @@ this record.
|
|||||||
// .. new GridFieldViewButton(),
|
// .. new GridFieldViewButton(),
|
||||||
// .. new GridFieldDetailForm()
|
// .. new GridFieldDetailForm()
|
||||||
|
|
||||||
### GridFieldConfig_RecordEditor
|
```
|
||||||
|
|
||||||
Similar to `GridFieldConfig_RecordViewer` with the addition support to edit or delete each of the records.
|
Similar to `GridFieldConfig_RecordViewer` with the addition support to edit or delete each of the records.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
The data row show must be a `DataObject` subclass. The fields displayed in the edit view come from
|
The data row show must be a `DataObject` subclass. The fields displayed in the edit view come from
|
||||||
`DataObject::getCMSFields()`.
|
`DataObject::getCMSFields()`.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
Permission control for editing and deleting the record uses the `canEdit()` and `canDelete()` methods on the
|
Permission control for editing and deleting the record uses the `canEdit()` and `canDelete()` methods on the
|
||||||
`DataObject` object.
|
`DataObject` object.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$config = GridFieldConfig_RecordEditor::create();
|
$config = GridFieldConfig_RecordEditor::create();
|
||||||
|
|
||||||
$gridField->setConfig($config);
|
$gridField->setConfig($config);
|
||||||
@ -213,17 +215,17 @@ Permission control for editing and deleting the record uses the `canEdit()` and
|
|||||||
// .. new GridFieldEditButton(),
|
// .. new GridFieldEditButton(),
|
||||||
// .. new GridFieldDeleteAction()
|
// .. new GridFieldDeleteAction()
|
||||||
|
|
||||||
### GridFieldConfig_RelationEditor
|
```
|
||||||
|
|
||||||
Similar to `GridFieldConfig_RecordEditor`, but adds features to work on a record's has-many or many-many relationships.
|
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`.
|
As such, it expects the list used with the `GridField` to be a instance of `RelationList`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$config = GridFieldConfig_RelationEditor::create();
|
$config = GridFieldConfig_RelationEditor::create();
|
||||||
|
|
||||||
$gridField->setConfig($config);
|
$gridField->setConfig($config);
|
||||||
|
|
||||||
This configuration adds the ability to searched for existing records and add a relationship
|
```
|
||||||
(`GridFieldAddExistingAutocompleter`).
|
(`GridFieldAddExistingAutocompleter`).
|
||||||
|
|
||||||
Records created or deleted through the `GridFieldConfig_RelationEditor` automatically update the relationship in the
|
Records created or deleted through the `GridFieldConfig_RelationEditor` automatically update the relationship in the
|
||||||
@ -235,13 +237,13 @@ The `GridFieldDetailForm` component drives the record viewing and editing form.
|
|||||||
`DataObject->getCMSFields()` method but can be customised to accept different fields via the
|
`DataObject->getCMSFields()` method but can be customised to accept different fields via the
|
||||||
[api:GridFieldDetailForm::setFields()] method.
|
[api:GridFieldDetailForm::setFields()] method.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$form = $gridField->getConfig()->getComponentByType('GridFieldDetailForm');
|
$form = $gridField->getConfig()->getComponentByType('GridFieldDetailForm');
|
||||||
$form->setFields(new FieldList(
|
$form->setFields(new FieldList(
|
||||||
new TextField('Title')
|
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
|
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
|
"many_many" relationship, as defined through [api:DataObject::$many_many_extraFields]. While loading and saving works
|
||||||
@ -252,7 +254,7 @@ them as fields for relation extra data, and to avoid clashes with the other form
|
|||||||
|
|
||||||
The namespace notation is `ManyMany[<extradata-field-name>]`, so for example `ManyMany[MyExtraField]`.
|
The namespace notation is `ManyMany[<extradata-field-name>]`, so for example `ManyMany[MyExtraField]`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Team extends DataObject {
|
class Team extends DataObject {
|
||||||
@ -304,7 +306,7 @@ The namespace notation is `ManyMany[<extradata-field-name>]`, so for example `Ma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
## Flexible Area Assignment through Fragments
|
## Flexible Area Assignment through Fragments
|
||||||
|
|
||||||
`GridField` layouts can contain many components other than the table itself, for example a search bar to find existing
|
`GridField` layouts can contain many components other than the table itself, for example a search bar to find existing
|
||||||
@ -316,24 +318,25 @@ The goal is for multiple components to share the same space, for example a heade
|
|||||||
- `header`/`footer`: Renders in a `<thead>`/`<tfoot>`, should contain table markup
|
- `header`/`footer`: Renders in a `<thead>`/`<tfoot>`, should contain table markup
|
||||||
- `before`/`after`: Renders before/after the actual `<table>`
|
- `before`/`after`: Renders before/after the actual `<table>`
|
||||||
- `buttons-before-left`/`buttons-before-right`/`buttons-after-left`/`buttons-after-right`:
|
- `buttons-before-left`/`buttons-before-right`/`buttons-after-left`/`buttons-after-right`:
|
||||||
|
```
|
||||||
Renders in a shared row before the table. Requires [api:GridFieldButtonRow].
|
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
|
[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.
|
bottom right of the table.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$config->addComponent(new GridFieldButtonRow('after'));
|
$config->addComponent(new GridFieldButtonRow('after'));
|
||||||
$config->addComponent(new GridFieldPrintButton('buttons-after-right'));
|
$config->addComponent(new GridFieldPrintButton('buttons-after-right'));
|
||||||
|
|
||||||
|
```
|
||||||
### Creating your own Fragments
|
### Creating your own Fragments
|
||||||
|
|
||||||
Fragments are designated areas within a `GridField` which can be shared between component templates. You can define
|
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
|
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>`.
|
create an area rendered before the table wrapped in a simple `<div>`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyAreaComponent implements GridField_HTMLProvider {
|
class MyAreaComponent implements GridField_HTMLProvider {
|
||||||
@ -345,15 +348,15 @@ create an area rendered before the table wrapped in a simple `<div>`.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
Please note that in templates, you'll need to escape the dollar sign on `\$DefineFragment`. These are specially
|
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.
|
processed placeholders as opposed to native template syntax.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
Now you can add other components into this area by returning them as an array from your
|
Now you can add other components into this area by returning them as an array from your
|
||||||
[api:GridFieldComponent::getHTMLFragments()] implementation:
|
[api:GridFieldComponent::getHTMLFragments()] implementation:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyShareLinkComponent implements GridField_HTMLProvider {
|
class MyShareLinkComponent implements GridField_HTMLProvider {
|
||||||
@ -365,12 +368,12 @@ Now you can add other components into this area by returning them as an array fr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Your new area can also be used by existing components, e.g. the [api:GridFieldPrintButton]
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
new GridFieldPrintButton('my-component-area');
|
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
|
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_gridfieldcomponent).
|
functionality. See [How to Create a GridFieldComponent](../how_tos/create_a_gridfieldcomponent).
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: UploadField
|
||||||
|
summary: Use a highly configurable form field to upload files
|
||||||
|
icon: upload
|
||||||
|
---
|
||||||
|
|
||||||
# UploadField
|
# UploadField
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
@ -16,27 +22,6 @@ Care should be taken as invalid files may remain within the filesystem until exp
|
|||||||
|
|
||||||
The following example adds an UploadField to a page for single fileupload, based on a has_one relation:
|
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 auto-detect 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.
|
The UploadField will auto-detect 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.
|
||||||
@ -44,47 +29,14 @@ The UploadField will auto-detect the relation based on it's `name` property, and
|
|||||||
### Multiple fileupload
|
### 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:
|
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 {
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```php
|
|
||||||
class GalleryImageExtension extends DataExtension {
|
|
||||||
private static $belongs_many_many = array('Galleries' => 'GalleryPage');
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```yml
|
|
||||||
Image:
|
|
||||||
extensions:
|
|
||||||
- GalleryImageExtension
|
|
||||||
```
|
```
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
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.
|
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>
|
[/notice]
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
### Overview
|
### Overview
|
||||||
@ -94,48 +46,24 @@ See the [Configuration Reference](uploadfield#configuration-reference) section f
|
|||||||
|
|
||||||
Example: mysite/_config/uploadfield.yml
|
Example: mysite/_config/uploadfield.yml
|
||||||
|
|
||||||
```yml
|
|
||||||
after: framework#uploadfield
|
|
||||||
---
|
|
||||||
UploadField:
|
|
||||||
defaultConfig:
|
|
||||||
canUpload: false
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Set a custom folder
|
### 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.
|
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
|
### Limit the allowed filetypes
|
||||||
`AllowedExtensions` defaults to the `File.allowed_extensions` configuration setting, but can be overwritten for each UploadField:
|
`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.
|
Entire groups of file extensions can be specified in order to quickly limit types to known file categories.
|
||||||
|
|
||||||
```php
|
|
||||||
$uploadField->setAllowedFileCategories('image', 'doc');
|
|
||||||
```
|
```
|
||||||
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.
|
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.
|
||||||
|
|
||||||
`AllowedExtensions` can also be set globally via the [YAML configuration](/developer_guides/configuration/configuration/#configuration-yaml-syntax-and-rules), for example you may add the following into your mysite/_config/config.yml:
|
`AllowedExtensions` can also be set globally via the [YAML configuration](/developer_guides/configuration/configuration/#configuration-yaml-syntax-and-rules), for example you may add the following into your mysite/_config/config.yml:
|
||||||
|
|
||||||
```yaml
|
|
||||||
File:
|
|
||||||
allowed_extensions:
|
|
||||||
- 7zip
|
|
||||||
- xzip
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Limit the maximum file size
|
### Limit the maximum file size
|
||||||
@ -143,49 +71,31 @@ This will limit files to the following extensions: bmp gif jpg jpeg pcx tif png
|
|||||||
|
|
||||||
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.
|
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);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also specify a default global maximum file size setting in your config for different file types. This is overridden when specifying the max allowed file size on the UploadField instance.
|
You can also specify a default global maximum file size setting in your config for different file types. This is overridden when specifying the max allowed file size on the UploadField instance.
|
||||||
|
|
||||||
```yaml
|
|
||||||
Upload_Validator:
|
|
||||||
default_max_file_size:
|
|
||||||
'[image]': '1m'
|
|
||||||
'[doc]': '5m'
|
|
||||||
'jpeg': 2000
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Preview dimensions
|
### 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.
|
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
|
### Disable attachment of existing files
|
||||||
This can force the user to upload a new file, rather than link to the already existing file library
|
This can force the user to upload a new file, rather than link to the already existing file library
|
||||||
|
|
||||||
```php
|
|
||||||
$uploadField->setCanAttachExisting(false);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Disable uploading of new files
|
### Disable uploading of new files
|
||||||
Alternatively, you can force the user to only specify already existing files in the file library
|
Alternatively, you can force the user to only specify already existing files in the file library
|
||||||
|
|
||||||
```php
|
|
||||||
$uploadField->setCanUpload(false);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Automatic or manual upload
|
### 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:
|
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
|
### Change Detection
|
||||||
@ -194,8 +104,6 @@ an UploadField instance of changes, such as a new upload,
|
|||||||
or the removal of an existing upload (through a `dirty` event).
|
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.
|
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
|
### Build a simple gallery
|
||||||
@ -203,51 +111,23 @@ A gallery most times needs more then simple images. You might want to add a desc
|
|||||||
|
|
||||||
First create a [DataExtension](/developer_guides/extending/extensions) like this:
|
First create a [DataExtension](/developer_guides/extending/extensions) 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 mysite/_config/config.yml:
|
Now register the DataExtension for the Image class in your mysite/_config/config.yml:
|
||||||
|
|
||||||
```yml
|
|
||||||
Image:
|
|
||||||
extensions:
|
|
||||||
- GalleryImage
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
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!
|
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>
|
[/notice]
|
||||||
|
|
||||||
### Edit uploaded images
|
### 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:
|
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:
|
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 (e.g. RequiredFields).
|
In a similar fashion you can use 'setFileEditActions' to set the actions for the editform, or 'fileEditValidator' to determine the validator (e.g. RequiredFields).
|
||||||
@ -291,31 +171,12 @@ In a similar fashion you can use 'setFileEditActions' to set the actions for the
|
|||||||
|
|
||||||
Certain default values for the above can be configured using the YAML config system.
|
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.
|
The above settings can also be set on a per-instance basis by using `setConfig` with the appropriate key.
|
||||||
|
|
||||||
The `Upload_Validator` class has configuration options for setting the `default_max_file_size`.
|
The `Upload_Validator` class has configuration options for setting the `default_max_file_size`.
|
||||||
|
|
||||||
```yaml
|
|
||||||
Upload_Validator:
|
|
||||||
default_max_file_size:
|
|
||||||
'[image]': '1m'
|
|
||||||
'[doc]': '5m'
|
|
||||||
'jpeg': 2000
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can specify the file extension or the app category (as specified in the `File` class) in square brackets. It supports setting the file size in bytes or using the syntax supported by `File::ini2bytes()`.
|
You can specify the file extension or the app category (as specified in the `File` class) in square brackets. It supports setting the file size in bytes or using the syntax supported by `File::ini2bytes()`.
|
||||||
@ -323,11 +184,6 @@ You can specify the file extension or the app category (as specified in the `Fil
|
|||||||
|
|
||||||
You can also configure the underlying [api:Upload] class, by using the YAML config system.
|
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
|
## Using the UploadField in a frontend form
|
||||||
@ -339,70 +195,16 @@ For instance, to generate an upload form suitable for saving images into a user-
|
|||||||
|
|
||||||
*In GalleryPage.php:*
|
*In GalleryPage.php:*
|
||||||
|
|
||||||
```php
|
|
||||||
<?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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
*Gallery.php:*
|
*Gallery.php:*
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
class Gallery extends DataObject {
|
|
||||||
private static $db = array(
|
|
||||||
'Title' => 'Varchar(255)'
|
|
||||||
);
|
|
||||||
|
|
||||||
private static $many_many = array(
|
|
||||||
'Images' => 'Image'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
*ImageExtension.php:*
|
*ImageExtension.php:*
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
class ImageExtension extends DataExtension {
|
|
||||||
|
|
||||||
private static $belongs_many_many = array(
|
|
||||||
'Gallery' => 'Gallery'
|
|
||||||
);
|
|
||||||
|
|
||||||
function canEdit($member) {
|
|
||||||
// WARNING! This affects permissions on ALL images. Setting this incorrectly can restrict
|
|
||||||
// access to authorised users or unintentionally give access to unauthorised users if set incorrectly.
|
|
||||||
return Permission::check('CMS_ACCESS_AssetAdmin');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
*mysite/_config/config.yml*
|
*mysite/_config/config.yml*
|
||||||
|
|
||||||
```yml
|
|
||||||
Image:
|
|
||||||
extensions:
|
|
||||||
- ImageExtension
|
|
||||||
```
|
```
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: Field Types
|
||||||
|
summary: More information about some of the core form fields
|
||||||
|
---
|
||||||
|
# Field Types
|
||||||
|
|
||||||
|
[CHILDREN]
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
title: How to Encapsulate Forms
|
title: How to Encapsulate Forms
|
||||||
|
summary: Learn how to move a form from a controller into its own class definition.
|
||||||
|
iconBrand: wpforms
|
||||||
|
---
|
||||||
# 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
|
Form definitions can often get long, complex and often end up cluttering up a `Controller` definition. We may also want
|
||||||
@ -9,7 +12,7 @@ code for a `Form` is to create it as a subclass to `Form`. Let's look at a examp
|
|||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page_Controller extends ContentController {
|
class Page_Controller extends ContentController {
|
||||||
@ -60,12 +63,12 @@ code for a `Form` is to create it as a subclass to `Form`. Let's look at a examp
|
|||||||
..
|
..
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
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**
|
**mysite/code/forms/SearchForm.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class SearchForm extends Form {
|
class SearchForm extends Form {
|
||||||
@ -121,11 +124,11 @@ should be. Good practice would be to move this to a subclass and create a new in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page_Controller extends ContentController {
|
class Page_Controller extends ContentController {
|
||||||
@ -139,7 +142,7 @@ Our controller will now just have to create a new instance of this form object.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Form actions can also be defined within your `Form` subclass to keep the entire form logic encapsulated.
|
```
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
title: How to Create Lightweight Form
|
title: How to Create Lightweight Form
|
||||||
|
summary: Create a simple search form with Silverstripe CMS
|
||||||
|
iconBrand: wpforms
|
||||||
|
---
|
||||||
# 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
|
Out of the box, SilverStripe provides a robust and reusable set of HTML markup for [api:FormFields], however this can
|
||||||
@ -11,7 +14,7 @@ totally custom template to meet our needs. To do this, we'll provide the class w
|
|||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
public function SearchForm() {
|
public function SearchForm() {
|
||||||
@ -29,9 +32,9 @@ totally custom template to meet our needs. To do this, we'll provide the class w
|
|||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
|
||||||
**mysite/templates/Includes/SearchForm.ss**
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<form $FormAttributes>
|
<form $FormAttributes>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
$Fields.dataFieldByName(q)
|
$Fields.dataFieldByName(q)
|
||||||
@ -42,11 +45,11 @@ totally custom template to meet our needs. To do this, we'll provide the class w
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</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`.
|
properties on [api:Form] such as `$Fields` and `$Actions`.
|
||||||
|
|
||||||
<div class="notice">
|
[notice]
|
||||||
To understand more about Scope or the syntax for custom templates, read the [Templates](../../templates) guide.
|
To understand more about Scope or the syntax for custom templates, read the [Templates](../../templates) guide.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Create a GridField Component
|
||||||
|
summary: Customise your GridField with a variety of add-ons.
|
||||||
|
icon: table
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
A single component often uses a number of interfaces.
|
A single component often uses a number of interfaces.
|
||||||
|
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Create a GridField action provider
|
||||||
|
summary: Handle custom actions on your GridField
|
||||||
|
---
|
||||||
|
|
||||||
# How to add a custom action to a GridField row
|
# How to add a custom action to a GridField row
|
||||||
|
|
||||||
In a [GridField](/developer_guides/forms/field_types/gridfield) instance each table row can have a
|
In a [GridField](/developer_guides/forms/field_types/gridfield) instance each table row can have a
|
||||||
@ -17,7 +22,7 @@ perform custom operations on a row.
|
|||||||
A basic outline of our new `GridFieldCustomAction.php` will look like something
|
A basic outline of our new `GridFieldCustomAction.php` will look like something
|
||||||
below:
|
below:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class GridFieldCustomAction implements GridField_ColumnProvider, GridField_ActionProvider {
|
class GridFieldCustomAction implements GridField_ColumnProvider, GridField_ActionProvider {
|
||||||
@ -32,7 +37,8 @@ below:
|
|||||||
return array('class' => 'col-buttons');
|
return array('class' => 'col-buttons');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
```
|
||||||
public function getColumnMetadata($gridField, $columnName) {
|
public function getColumnMetadata($gridField, $columnName) {
|
||||||
if($columnName == 'Actions') {
|
if($columnName == 'Actions') {
|
||||||
return array('title' => '');
|
return array('title' => '');
|
||||||
@ -54,7 +60,8 @@ below:
|
|||||||
array('RecordID' => $record->ID)
|
array('RecordID' => $record->ID)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
```
|
||||||
|
```
|
||||||
return $field->Field();
|
return $field->Field();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,14 +82,14 @@ below:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## Add the GridFieldCustomAction to the current `GridFieldConfig`
|
```
|
||||||
|
|
||||||
While we're working on the code, to add this new action to the `GridField`, add
|
While we're working on the code, to add this new action to the `GridField`, add
|
||||||
a new instance of the class to the [api:GridFieldConfig] object. The `GridField`
|
a new instance of the class to the [api:GridFieldConfig] object. The `GridField`
|
||||||
[Reference](/developer_guides/forms/field_types/gridfield) documentation has more information about
|
[Reference](/developer_guides/forms/field_types/gridfield) documentation has more information about
|
||||||
manipulating the `GridFieldConfig` instance if required.
|
manipulating the `GridFieldConfig` instance if required.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// option 1: creating a new GridField with the CustomAction
|
// option 1: creating a new GridField with the CustomAction
|
||||||
$config = GridFieldConfig::create();
|
$config = GridFieldConfig::create();
|
||||||
$config->addComponent(new GridFieldCustomAction());
|
$config->addComponent(new GridFieldCustomAction());
|
||||||
@ -92,6 +99,7 @@ manipulating the `GridFieldConfig` instance if required.
|
|||||||
// option 2: adding the CustomAction to an exisitng GridField
|
// option 2: adding the CustomAction to an exisitng GridField
|
||||||
$gridField->getConfig()->addComponent(new GridFieldCustomAction());
|
$gridField->getConfig()->addComponent(new GridFieldCustomAction());
|
||||||
|
|
||||||
|
```
|
||||||
For documentation on adding a Component to a `GridField` created by `ModelAdmin`
|
For documentation on adding a Component to a `GridField` created by `ModelAdmin`
|
||||||
please view the [GridField Customization](/developer_guides/forms/how_tos/create_a_gridfield_actionprovider) section.
|
please view the [GridField Customization](/developer_guides/forms/how_tos/create_a_gridfield_actionprovider) section.
|
||||||
|
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
|
---
|
||||||
|
title: Simple contact form
|
||||||
|
summary: Create a form that submits a message via email
|
||||||
|
iconBrand: wpforms
|
||||||
|
---
|
||||||
|
|
||||||
# How to make a simple contact form
|
# How to make a simple contact form
|
||||||
|
|
||||||
In this how-to, we'll explain how to set up a specific page type
|
In this how-to, we'll explain how to set up a specific page type
|
||||||
holding a contact form, which submits a message via email.
|
holding a contact form, which submits a message via email.
|
||||||
Let's start by defining a new `ContactPage` page type:
|
Let's start by defining a new `ContactPage` page type:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class ContactPage extends Page {
|
class ContactPage extends Page {
|
||||||
}
|
}
|
||||||
@ -23,30 +29,30 @@ Let's start by defining a new `ContactPage` page type:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
To create a form, we instanciate a `Form` object on a function on our page controller. We'll call this function `Form()`. You're free to choose this name, but it's standard practice to name the function `Form()` if there's only a single form on the page.
|
```
|
||||||
|
|
||||||
There's quite a bit in this function, so we'll step through one piece at a time.
|
There's quite a bit in this function, so we'll step through one piece at a time.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields = new FieldList(
|
$fields = new FieldList(
|
||||||
new TextField('Name'),
|
new TextField('Name'),
|
||||||
new EmailField('Email'),
|
new EmailField('Email'),
|
||||||
new TextareaField('Message')
|
new TextareaField('Message')
|
||||||
);
|
);
|
||||||
|
|
||||||
First we create all the fields we want in the contact form, and put them inside a FieldList. You can find a list of form fields available on the [api:FormField] page.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$actions = FieldList(
|
$actions = FieldList(
|
||||||
new FormAction('submit', 'Submit')
|
new FormAction('submit', 'Submit')
|
||||||
);
|
);
|
||||||
|
|
||||||
We then create a [api:FieldList] of the form actions, or the buttons that submit the form. Here we add a single form action, with the name 'submit', and the label 'Submit'. We'll use the name of the form action later.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
return new Form($this, 'Form', $fields, $actions);
|
return new Form($this, 'Form', $fields, $actions);
|
||||||
|
|
||||||
Finally we create the `Form` object and return it. The first argument is the controller that the form is on – this is almost always $this. The second argument is the name of the form – this has to be the same as the name of the function that creates the form, so we've used 'Form'. The third and fourth arguments are the fields and actions we created earlier.
|
```
|
||||||
|
|
||||||
|
|
||||||
To show the form on the page, we need to render it in our template. We do this by appending $ to the name of the form – so for the form we just created we need to add $Form. Add $Form to the themes/currenttheme/Layout/Page.ss template, below $Content.
|
To show the form on the page, we need to render it in our template. We do this by appending $ to the name of the form – so for the form we just created we need to add $Form. Add $Form to the themes/currenttheme/Layout/Page.ss template, below $Content.
|
||||||
@ -60,7 +66,7 @@ If you now create a ContactPage in the CMS (making sure you have rebuilt the dat
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class ContactPage_Controller extends Page_Controller {
|
class ContactPage_Controller extends Page_Controller {
|
||||||
private static $allowed_actions = array('Form');
|
private static $allowed_actions = array('Form');
|
||||||
public function Form() {
|
public function Form() {
|
||||||
@ -86,12 +92,12 @@ Now that we have a contact form, we need some way of collecting the data submitt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="hint" markdown="1">
|
```
|
||||||
Caution: This form is prone to abuse by spammers,
|
Caution: This form is prone to abuse by spammers,
|
||||||
since it doesn't enforce a rate limitation, or checks for bots.
|
since it doesn't enforce a rate limitation, or checks for bots.
|
||||||
We recommend to use a validation service like the ["recaptcha" module](http://www.silverstripe.org/recaptcha-module/)
|
We recommend to use a validation service like the ["recaptcha" module](http://www.silverstripe.org/recaptcha-module/)
|
||||||
for better security.
|
for better security.
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
Any function that receives a form submission takes two arguments: the data passed to the form as an indexed array, and the form itself. In order to extract the data, you can either use functions on the form object to get the fields and query their values, or just use the raw data in the array. In the example above, we used the array, as it's the easiest way to get data without requiring the form fields to perform any special transformations.
|
Any function that receives a form submission takes two arguments: the data passed to the form as an indexed array, and the form itself. In order to extract the data, you can either use functions on the form object to get the fields and query their values, or just use the raw data in the array. In the example above, we used the array, as it's the easiest way to get data without requiring the form fields to perform any special transformations.
|
||||||
|
|
||||||
@ -106,12 +112,12 @@ All forms have some basic validation built in – email fields will only let the
|
|||||||
|
|
||||||
The framework comes with a predefined validator called [api:RequiredFields], which performs the common task of making sure particular fields are filled out. Below is the code to add validation to a contact form:
|
The framework comes with a predefined validator called [api:RequiredFields], which performs the common task of making sure particular fields are filled out. Below is the code to add validation to a contact form:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function Form() {
|
public function Form() {
|
||||||
// ...
|
// ...
|
||||||
$validator = new RequiredFields('Name', 'Message');
|
$validator = new RequiredFields('Name', 'Message');
|
||||||
return new Form($this, 'Form', $fields, $actions, $validator);
|
return new Form($this, 'Form', $fields, $actions, $validator);
|
||||||
}
|
}
|
||||||
|
|
||||||
We've created a RequiredFields object, passing the name of the fields we want to be required. The validator we have created is then passed as the fifth argument of the form constructor. If we now try to submit the form without filling out the required fields, JavaScript validation will kick in, and the user will be presented with a message about the missing fields. If the user has JavaScript disabled, PHP validation will kick in when the form is submitted, and the user will be redirected back to the Form with messages about their missing fields.
|
```
|
||||||
|
|
||||||
|
6
docs/en/02_Developer_Guides/03_Forms/How_Tos/index.md
Normal file
6
docs/en/02_Developer_Guides/03_Forms/How_Tos/index.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: How To's
|
||||||
|
---
|
||||||
|
# How To's: Forms
|
||||||
|
|
||||||
|
[CHILDREN]
|
@ -1,7 +1,9 @@
|
|||||||
|
---
|
||||||
title: Forms
|
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.
|
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.
|
introduction: This guide will work through how to create SilverStripe forms, adding and modifying fields and how to handle form submissions.
|
||||||
|
iconBrand: wpforms
|
||||||
|
---
|
||||||
The [api:Form] class provides a way to create interactive forms in your web application with very little effort.
|
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
|
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.
|
framework for dealing with submissions and validation.
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Configuration API
|
title: Configuration API
|
||||||
summary: SilverStripe's YAML based Configuration API for setting runtime configuration.
|
summary: Silverstripe CMS's YAML based Configuration API for setting runtime configuration.
|
||||||
|
icon: laptop-code
|
||||||
|
---
|
||||||
# Configuration API
|
# Configuration API
|
||||||
|
|
||||||
SilverStripe comes with a comprehensive code based configuration system through the [api:Config] class. It primarily
|
SilverStripe comes with a comprehensive code based configuration system through the [api:Config] class. It primarily
|
||||||
@ -14,9 +16,9 @@ properties API:
|
|||||||
- Configuration is normally set once during initialization and then not changed.
|
- 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.
|
- Configuration is normally set by a knowledgeable technical user, such as a developer, not the end user.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
For providing content editors or CMS users a place to manage configuration see the [SiteConfig](siteconfig) module.
|
For providing content editors or CMS users a place to manage configuration see the [SiteConfig](siteconfig) module.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Configuration Properties
|
## Configuration Properties
|
||||||
|
|
||||||
@ -26,7 +28,7 @@ be marked `private static` and follow the `lower_case_with_underscores` structur
|
|||||||
|
|
||||||
**mysite/code/MyClass.php**
|
**mysite/code/MyClass.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyClass extends Page {
|
class MyClass extends Page {
|
||||||
@ -44,30 +46,31 @@ be marked `private static` and follow the `lower_case_with_underscores` structur
|
|||||||
// ..
|
// ..
|
||||||
}
|
}
|
||||||
|
|
||||||
## Accessing and Setting Configuration Properties
|
```
|
||||||
|
|
||||||
This can be done by calling the static method [api:Config::inst()], like so:
|
This can be done by calling the static method [api:Config::inst()], like so:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$config = Config::inst()->get('MyClass');
|
$config = Config::inst()->get('MyClass');
|
||||||
|
|
||||||
Or through the `config()` object on the class.
|
```
|
||||||
|
|
||||||
|
```
|
||||||
$config = $this->config();
|
$config = $this->config();
|
||||||
|
|
||||||
There are three public methods available on the instance. `get($class, $variable)`, `remove($class, $variable)` and
|
```
|
||||||
`update($class, $variable, $value)`.
|
`update($class, $variable, $value)`.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
There is no "set" method. It is not possible to completely set the value of a classes' property. `update` adds new
|
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.
|
values that are treated as the highest priority in the merge, and remove adds a merge mask that filters out values.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
To set those configuration options on our previously defined class we can define it in a `YAML` file.
|
To set those configuration options on our previously defined class we can define it in a `YAML` file.
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
MyClass:
|
MyClass:
|
||||||
option_one: false
|
option_one: false
|
||||||
option_two:
|
option_two:
|
||||||
@ -75,9 +78,9 @@ To set those configuration options on our previously defined class we can define
|
|||||||
- Bar
|
- Bar
|
||||||
- Baz
|
- Baz
|
||||||
|
|
||||||
To use those variables in your application code:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$me = new MyClass();
|
$me = new MyClass();
|
||||||
|
|
||||||
echo $me->config()->option_one;
|
echo $me->config()->option_one;
|
||||||
@ -105,10 +108,10 @@ To use those variables in your application code:
|
|||||||
echo implode(', ', MyClass::config()->option_one);
|
echo implode(', ', MyClass::config()->option_one);
|
||||||
// returns 'Qux'
|
// 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
|
There is no way currently to restrict read or write access to any configuration property, or influence/check the values
|
||||||
being read or written.
|
being read or written.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Configuration Values
|
## Configuration Values
|
||||||
|
|
||||||
@ -126,11 +129,11 @@ rules:
|
|||||||
- If the value is not an array, the highest priority value is used without any attempt to merge
|
- If the value is not an array, the highest priority value is used without any attempt to merge
|
||||||
|
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
The exception to this is "false-ish" values - empty arrays, empty strings, etc. When merging a non-false-ish value with
|
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
|
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 result will be the higher priority false-ish value.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
The locations that configuration values are taken from in highest -> lowest priority order are:
|
The locations that configuration values are taken from in highest -> lowest priority order are:
|
||||||
|
|
||||||
@ -141,21 +144,22 @@ order, where the item that is latest is highest priority)
|
|||||||
- Any static set on the class 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
|
- The composite configuration value of the parent class of this class
|
||||||
|
|
||||||
<div class="notice">
|
[notice]
|
||||||
It is an error to have mixed types of the same named property in different locations. An error will not necessarily
|
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.
|
be raised due to optimizations in the lookup code.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Configuration Masks
|
## Configuration Masks
|
||||||
|
|
||||||
At some of these levels you can also set masks. These remove values from the composite value at their priority point
|
At some of these levels you can also set masks. These remove values from the composite value at their priority point
|
||||||
rather than add.
|
rather than add.
|
||||||
|
|
||||||
|
```
|
||||||
$actionsWithoutExtra = $this->config()->get(
|
$actionsWithoutExtra = $this->config()->get(
|
||||||
'allowed_actions', Config::UNINHERITED
|
'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 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
|
- If the composite value is an associative array, any member of that array that matches both the key and value of any
|
||||||
@ -168,29 +172,31 @@ pair in the mask is removed
|
|||||||
Each module can have a directory immediately underneath the main module directory called `_config/`. Inside this
|
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.
|
directory you can add YAML files that contain values for the configuration system.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
The name of the files within the applications `_config` directly are arbitrary. Our examples use
|
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`,
|
`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`.
|
`email.yml` if you want. For add-on's and modules, it is recommended that you name them with `<module_name>.yml`.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
The structure of each YAML file is a series of headers and values separated by YAML document separators.
|
The structure of each YAML file is a series of headers and values separated by YAML document separators.
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
---
|
```
|
||||||
|
```
|
||||||
Name: adminroutes
|
Name: adminroutes
|
||||||
After:
|
After:
|
||||||
- '#rootroutes'
|
```
|
||||||
- '#coreroutes'
|
- '#coreroutes'
|
||||||
---
|
---
|
||||||
|
```
|
||||||
Director:
|
Director:
|
||||||
rules:
|
rules:
|
||||||
'admin': 'AdminRootController'
|
'admin': 'AdminRootController'
|
||||||
---
|
```
|
||||||
|
|
||||||
<div class="info">
|
[info]
|
||||||
If there is only one set of values the header can be omitted.
|
If there is only one set of values the header can be omitted.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
Each value section of a YAML file has:
|
Each value section of a YAML file has:
|
||||||
|
|
||||||
@ -223,17 +229,19 @@ before (lower priority than) or after (higher priority than) some other value se
|
|||||||
To specify these rules you add an "After" and/or "Before" key to the relevant header section. The value for these
|
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:
|
keys is a list of reference paths to other value sections. A basic example:
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
---
|
```
|
||||||
|
```
|
||||||
Name: adminroutes
|
Name: adminroutes
|
||||||
After:
|
After:
|
||||||
- '#rootroutes'
|
```
|
||||||
- '#coreroutes'
|
- '#coreroutes'
|
||||||
---
|
---
|
||||||
|
```
|
||||||
Director:
|
Director:
|
||||||
rules:
|
rules:
|
||||||
'admin': 'AdminRootController'
|
'admin': 'AdminRootController'
|
||||||
---
|
```
|
||||||
|
|
||||||
You do not have to specify all portions of a reference path. Any portion may be replaced with a wildcard "\*", or left
|
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
|
out all together. Either has the same affect - that portion will be ignored when checking a value section's reference
|
||||||
@ -255,10 +263,10 @@ after value sections with a name of `rootroutes`. However because `\*` has three
|
|||||||
|
|
||||||
In this case `\*` means "every value section _except_ ones that have a fragment name of rootroutes".
|
In this case `\*` means "every value section _except_ ones that have a fragment name of rootroutes".
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
It is possible to create chains that are unsolvable. For instance, A must be before B, B must be before C, C must be
|
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.
|
before A. In this case you will get an error when accessing your site.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## Exclusionary rules
|
## Exclusionary rules
|
||||||
|
|
||||||
@ -278,38 +286,43 @@ You then list any of the following rules as sub-keys, with informational values
|
|||||||
- 'classexists', in which case the value(s) should be classes that must exist
|
- '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
|
- '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
|
- '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
|
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
|
- '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:
|
For instance, to add a property to "foo" when a module exists, and "bar" otherwise, you could do this:
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
---
|
```
|
||||||
|
```
|
||||||
Only:
|
Only:
|
||||||
moduleexists: 'MyFineModule'
|
moduleexists: 'MyFineModule'
|
||||||
---
|
```
|
||||||
|
```
|
||||||
MyClass:
|
MyClass:
|
||||||
property: 'foo'
|
property: 'foo'
|
||||||
---
|
```
|
||||||
|
```
|
||||||
Except:
|
Except:
|
||||||
moduleexists: 'MyFineModule'
|
moduleexists: 'MyFineModule'
|
||||||
---
|
```
|
||||||
|
```
|
||||||
MyClass:
|
MyClass:
|
||||||
property: 'bar'
|
property: 'bar'
|
||||||
---
|
```
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
When you have more than one rule for a nested fragment, they're joined like
|
When you have more than one rule for a nested fragment, they're joined like
|
||||||
`FRAGMENT_INCLUDED = (ONLY && ONLY) && !(EXCEPT && EXCEPT)`.
|
`FRAGMENT_INCLUDED = (ONLY && ONLY) && !(EXCEPT && EXCEPT)`.
|
||||||
That is, the fragment will be included if all Only rules match, except if all Except rules match.
|
That is, the fragment will be included if all Only rules match, except if all Except rules match.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
Due to YAML limitations, having multiple conditions of the same kind (say, two `EnvVarSet` in one "Only" block)
|
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.
|
will result in only the latter coming through.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: SiteConfig
|
title: SiteConfig
|
||||||
summary: Content author configuration through the SiteConfig module.
|
summary: Content author configuration through the SiteConfig module.
|
||||||
|
icon: laptop-code
|
||||||
|
---
|
||||||
# SiteConfig
|
# SiteConfig
|
||||||
|
|
||||||
The `SiteConfig` module provides a generic interface for managing site wide settings or functionality which is used
|
The `SiteConfig` module provides a generic interface for managing site wide settings or functionality which is used
|
||||||
@ -10,7 +12,7 @@ throughout the site. Out of the box this includes selecting the current site the
|
|||||||
|
|
||||||
`SiteConfig` options can be accessed from any template by using the $SiteConfig variable.
|
`SiteConfig` options can be accessed from any template by using the $SiteConfig variable.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$SiteConfig.Title
|
$SiteConfig.Title
|
||||||
$SiteConfig.Tagline
|
$SiteConfig.Tagline
|
||||||
|
|
||||||
@ -18,23 +20,23 @@ throughout the site. Out of the box this includes selecting the current site the
|
|||||||
$Title $AnotherField
|
$Title $AnotherField
|
||||||
<% end_with %>
|
<% end_with %>
|
||||||
|
|
||||||
To access variables in the PHP:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$config = SiteConfig::current_site_config();
|
$config = SiteConfig::current_site_config();
|
||||||
|
|
||||||
echo $config->Title;
|
echo $config->Title;
|
||||||
|
|
||||||
// returns "Website Name"
|
// returns "Website Name"
|
||||||
|
|
||||||
|
```
|
||||||
## Extending SiteConfig
|
## Extending SiteConfig
|
||||||
|
|
||||||
To extend the options available in the panel, define your own fields via a [api:DataExtension].
|
To extend the options available in the panel, define your own fields via a [api:DataExtension].
|
||||||
|
|
||||||
**mysite/code/extensions/CustomSiteConfig.php**
|
**mysite/code/extensions/CustomSiteConfig.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class CustomSiteConfig extends DataExtension {
|
class CustomSiteConfig extends DataExtension {
|
||||||
@ -50,19 +52,19 @@ To extend the options available in the panel, define your own fields via a [api:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Then activate the extension.
|
```
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
SiteConfig:
|
SiteConfig:
|
||||||
extensions:
|
extensions:
|
||||||
- CustomSiteConfig
|
- 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.
|
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.
|
You may also need to reload the screen with a `?flush=1` i.e http://yoursite.com/admin/settings?flush=1.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
You can define as many extensions for `SiteConfig` as you need. For example, if you're developing a module and want to
|
You can define as many extensions for `SiteConfig` as you need. For example, if you're developing a module and want to
|
||||||
provide the users a place to configure settings then the `SiteConfig` panel is the place to go it.
|
provide the users a place to configure settings then the `SiteConfig` panel is the place to go it.
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Environment Variables
|
title: Environment Variables
|
||||||
summary: Site configuration variables such as database connection details, environment type and remote login information.
|
summary: Site configuration variables such as database connection details, environment type and remote login information.
|
||||||
|
icon: dollar-sign
|
||||||
|
---
|
||||||
# Environment Variables
|
# Environment Variables
|
||||||
|
|
||||||
Environment specific variables like database connection details, API keys and other server configuration should be kept
|
Environment specific variables like database connection details, API keys and other server configuration should be kept
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
---
|
||||||
title: Configuration
|
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.
|
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.
|
introduction: SilverStripe provides several ways to store and modify your application settings. Learn about site wide settings and the YAML based configuration system.
|
||||||
|
icon: laptop-code
|
||||||
|
---
|
||||||
[CHILDREN]
|
[CHILDREN]
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Modules
|
title: Modules
|
||||||
summary: Extend core functionality with modules.
|
summary: Extend core functionality with modules.
|
||||||
|
icon: code
|
||||||
|
---
|
||||||
# Modules
|
# Modules
|
||||||
|
|
||||||
SilverStripe is designed to be a modular application system - even the CMS is simply a module that plugs into the core
|
SilverStripe is designed to be a modular application system - even the CMS is simply a module that plugs into the core
|
||||||
@ -10,6 +12,7 @@ A module is a collection of classes, templates, and other resources that is load
|
|||||||
the `framework`, `cms` or `mysite` folders. The only thing that identifies a folder as a SilverStripe module is the
|
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.
|
existence of a `_config` directory or `_config.php` at the top level of the directory.
|
||||||
|
|
||||||
|
```
|
||||||
mysite/
|
mysite/
|
||||||
|
|
|
|
||||||
+-- _config/
|
+-- _config/
|
||||||
@ -21,13 +24,13 @@ existence of a `_config` directory or `_config.php` at the top level of the dire
|
|||||||
+-- _config/
|
+-- _config/
|
||||||
+-- ...
|
+-- ...
|
||||||
|
|
||||||
SilverStripe will automatically include any PHP classes and templates from within your module when you next flush your
|
```
|
||||||
cache.
|
cache.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
In a default SilverStripe installation, even resources in `framework` and `mysite` are treated in exactly the same as
|
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.
|
every other module. Order of priority is usually alphabetical unless stated.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
Creating a module is a good way to re-use abstract code and templates across multiple projects. SilverStripe already
|
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
|
has certain modules included, for example the `cms` module and core functionality such as commenting and spam protection
|
||||||
@ -44,14 +47,14 @@ are also abstracted into modules allowing developers the freedom to choose what
|
|||||||
|
|
||||||
Modules should exist in the root folder of your SilverStripe installation.
|
Modules should exist in the root folder of your SilverStripe installation.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
The root directory is the one containing the *framework* and *mysite* subdirectories. If your site is installed under
|
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.
|
`/Users/sam.minnee/Sites/website/` your modules will go in the `/Users/sam.minnee/Sites/website/` directory.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
After you add or remove modules make sure you rebuild the database by going to http://yoursite.com/dev/build?flush=1
|
After you add or remove modules make sure you rebuild the database by going to http://yoursite.com/dev/build?flush=1
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
### From Composer
|
### From Composer
|
||||||
|
|
||||||
@ -64,37 +67,37 @@ Each module has a unique identifier, consisting of a vendor prefix and name. For
|
|||||||
identifier `silverstripe/blog` as it is published by *silverstripe*. To install, use the following command executed in
|
identifier `silverstripe/blog` as it is published by *silverstripe*. To install, use the following command executed in
|
||||||
the root folder:
|
the root folder:
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
composer require "silverstripe/blog" "*@stable"
|
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.
|
tag name.
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
composer require "silverstripe/blog" "1.1.0"
|
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
|
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).
|
[Composer "lock" files](http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file).
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
## From an Archive Download
|
## From an Archive Download
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
Some modules might not work at all with this approach since they rely on the
|
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
|
Composer [autoloader](http://getcomposer.org/doc/01-basic-usage.md#autoloading), additional modules or post-install
|
||||||
hooks, so we recommend using Composer.
|
hooks, so we recommend using Composer.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
Alternatively, you can download the archive file from the [modules page](http://www.silverstripe.org/modules) and
|
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.
|
extract it to the root folder mentioned above.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
The main folder extracted from the archive might contain the version number or additional "container" folders above the
|
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
|
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
|
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.
|
operate in other structures, paths to static files such as CSS or JavaScript won't work.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Publishing your own SilverStripe module
|
## Publishing your own SilverStripe module
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Extensions
|
title: Extensions
|
||||||
summary: Extensions and DataExtensions let you modify and augment objects transparently.
|
summary: Extensions and DataExtensions let you modify and augment objects transparently.
|
||||||
|
icon: code
|
||||||
|
---
|
||||||
# Extensions and DataExtensions
|
# Extensions and DataExtensions
|
||||||
|
|
||||||
An [api:Extension] allows for adding additional functionality to a [api:Object] or modifying existing functionality
|
An [api:Extension] allows for adding additional functionality to a [api:Object] or modifying existing functionality
|
||||||
@ -10,14 +12,14 @@ 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
|
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])
|
the [api:Extension] class for non DataObject subclasses (such as [api:Controllers])
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
For performance reasons a few classes are excluded from receiving extensions, including `Object`, `ViewableData`
|
For performance reasons a few classes are excluded from receiving extensions, including `Object`, `ViewableData`
|
||||||
and `RequestHandler`. You can still apply extensions to descendants of these classes.
|
and `RequestHandler`. You can still apply extensions to descendants of these classes.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
**mysite/code/extensions/MyMemberExtension.php**
|
**mysite/code/extensions/MyMemberExtension.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyMemberExtension extends DataExtension {
|
class MyMemberExtension extends DataExtension {
|
||||||
@ -32,26 +34,26 @@ and `RequestHandler`. You can still apply extensions to descendants of these cla
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
```
|
||||||
Convention is for extension class names to end in `Extension`. This isn't a requirement but makes it clearer
|
Convention is for extension class names to end in `Extension`. This isn't a requirement but makes it clearer
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
After this class has been created, it does not yet apply it to any object. We need to tell SilverStripe what classes
|
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).
|
we want to add the `MyMemberExtension` too. To activate this extension, add the following via the [Configuration API](../configuration).
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Member:
|
Member:
|
||||||
extensions:
|
extensions:
|
||||||
- MyMemberExtension
|
- MyMemberExtension
|
||||||
|
|
||||||
Alternatively, we can add extensions through PHP code (in the `_config.php` file).
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Member::add_extension('MyMemberExtension');
|
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:
|
transformed the original `Member` class in two ways:
|
||||||
|
|
||||||
* Added a new [api:SS_Datetime] for the users date of birth, and;
|
* Added a new [api:SS_Datetime] for the users date of birth, and;
|
||||||
@ -70,7 +72,7 @@ $has_one etc.
|
|||||||
|
|
||||||
**mysite/code/extensions/MyMemberExtension.php**
|
**mysite/code/extensions/MyMemberExtension.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyMemberExtension extends DataExtension {
|
class MyMemberExtension extends DataExtension {
|
||||||
@ -89,32 +91,32 @@ $has_one etc.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
**mysite/templates/Page.ss**
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
$CurrentMember.Position
|
$CurrentMember.Position
|
||||||
$CurrentMember.Image
|
$CurrentMember.Image
|
||||||
|
|
||||||
|
```
|
||||||
## Adding Methods
|
## Adding Methods
|
||||||
|
|
||||||
Methods that have a unique name will be called as part of the `__call` method on [api:Object]. In the previous example
|
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.
|
we added a `SayHi` method which is unique to our extension.
|
||||||
|
|
||||||
**mysite/templates/Page.ss**
|
**mysite/templates/Page.ss**
|
||||||
:::ss
|
```ss
|
||||||
<p>$CurrentMember.SayHi</p>
|
<p>$CurrentMember.SayHi</p>
|
||||||
|
|
||||||
// "Hi Sam"
|
// "Hi Sam"
|
||||||
|
|
||||||
**mysite/code/Page.php**
|
```
|
||||||
:::php
|
```php
|
||||||
$member = Member::currentUser();
|
$member = Member::currentUser();
|
||||||
echo $member->SayHi;
|
echo $member->SayHi;
|
||||||
|
|
||||||
// "Hi Sam"
|
// "Hi Sam"
|
||||||
|
|
||||||
|
```
|
||||||
## Modifying Existing Methods
|
## Modifying Existing Methods
|
||||||
|
|
||||||
If the `Extension` needs to modify an existing method it's a little trickier. It requires that the method you want to
|
If the `Extension` needs to modify an existing method it's a little trickier. It requires that the method you want to
|
||||||
@ -123,7 +125,7 @@ through the [api:Object::extend()] method.
|
|||||||
|
|
||||||
**framework/security/Member.php**
|
**framework/security/Member.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function getValidator() {
|
public function getValidator() {
|
||||||
// ..
|
// ..
|
||||||
|
|
||||||
@ -132,14 +134,14 @@ through the [api:Object::extend()] method.
|
|||||||
// ..
|
// ..
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
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
|
`updateValidator` hook for developers to modify the core method. The `MyMemberExtension` would modify the core member's
|
||||||
validator by defining the `updateValidator` method.
|
validator by defining the `updateValidator` method.
|
||||||
|
|
||||||
**mysite/code/extensions/MyMemberExtension.php**
|
**mysite/code/extensions/MyMemberExtension.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyMemberExtension extends DataExtension {
|
class MyMemberExtension extends DataExtension {
|
||||||
@ -152,14 +154,14 @@ validator by defining the `updateValidator` method.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
```
|
||||||
The `$validator` parameter is passed by reference, as it is an object.
|
The `$validator` parameter is passed by reference, as it is an object.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
Another common example of when you will want to modify a method is to update the default CMS fields for an object in an
|
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.
|
extension. The `CMS` provides a `updateCMSFields` Extension Hook to tie into.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyMemberExtension extends DataExtension {
|
class MyMemberExtension extends DataExtension {
|
||||||
@ -178,13 +180,13 @@ extension. The `CMS` provides a `updateCMSFields` Extension Hook to tie into.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
If you're providing a module or working on code that may need to be extended by other code, it should provide a *hook*
|
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.
|
which allows an Extension to modify the results.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function Foo() {
|
public function Foo() {
|
||||||
$foo = // ..
|
$foo = // ..
|
||||||
|
|
||||||
@ -193,7 +195,7 @@ which allows an Extension to modify the results.
|
|||||||
return $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{..}`.
|
you need to provide extension hooks at the beginning of the method use `before{..}`.
|
||||||
|
|
||||||
## Owner
|
## Owner
|
||||||
@ -201,7 +203,7 @@ you need to provide extension hooks at the beginning of the method use `before{.
|
|||||||
In your [api:Extension] class you can only refer to the source object through the `owner` property on the class as
|
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.
|
`$this` will refer to your `Extension` instance.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyMemberExtension extends DataExtension {
|
class MyMemberExtension extends DataExtension {
|
||||||
@ -212,13 +214,13 @@ In your [api:Extension] class you can only refer to the source object through th
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## Checking to see if an Object has an Extension
|
```
|
||||||
|
|
||||||
To see what extensions are currently enabled on an object, use [api:Object::getExtensionInstances()] and
|
To see what extensions are currently enabled on an object, use [api:Object::getExtensionInstances()] and
|
||||||
[api:Object::hasExtension()]
|
[api:Object::hasExtension()]
|
||||||
|
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$member = Member::currentUser();
|
$member = Member::currentUser();
|
||||||
|
|
||||||
print_r($member->getExtensionInstances());
|
print_r($member->getExtensionInstances());
|
||||||
@ -227,7 +229,7 @@ To see what extensions are currently enabled on an object, use [api:Object::getE
|
|||||||
// ..
|
// ..
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
## Object extension injection points
|
## Object extension injection points
|
||||||
|
|
||||||
`Object` has two additional methods, `beforeExtending` and `afterExtending`, each of which takes a method name and a
|
`Object` has two additional methods, `beforeExtending` and `afterExtending`, each of which takes a method name and a
|
||||||
@ -236,15 +238,15 @@ callback to be executed immediately before and after `Object::extend()` is calle
|
|||||||
This is useful in many cases where working with modules such as `Translatable` which operate on `DataObject` fields
|
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.
|
that must exist in the `FieldList` at the time that `$this->extend('UpdateCMSFields')` is called.
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
Please note that each callback is only ever called once, and then cleared, so multiple extensions to the same function
|
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.
|
require that a callback is registered each time, if necessary.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
Example: A class that wants to control default values during object initialization. The code needs to assign a value
|
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:
|
if not specified in `self::$defaults`, but before extensions have been called:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
function __construct() {
|
function __construct() {
|
||||||
$self = $this;
|
$self = $this;
|
||||||
|
|
||||||
@ -257,13 +259,13 @@ if not specified in `self::$defaults`, but before extensions have been called:
|
|||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
Example 2: User code can intervene in the process of extending cms fields.
|
```
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
This method is preferred to disabling, enabling, and calling field extensions manually.
|
This method is preferred to disabling, enabling, and calling field extensions manually.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function getCMSFields() {
|
public function getCMSFields() {
|
||||||
|
|
||||||
$this->beforeUpdateCMSFields(function($fields) {
|
$this->beforeUpdateCMSFields(function($fields) {
|
||||||
@ -276,7 +278,7 @@ This method is preferred to disabling, enabling, and calling field extensions ma
|
|||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
## Related Documentaion
|
## Related Documentaion
|
||||||
|
|
||||||
* [Injector](injector/)
|
* [Injector](injector/)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Shortcodes
|
title: Shortcodes
|
||||||
summary: Flexible content embedding
|
summary: Flexible content embedding
|
||||||
|
icon: code
|
||||||
|
---
|
||||||
# Shortcodes
|
# Shortcodes
|
||||||
|
|
||||||
The [api:ShortcodeParser] API is simple parser that allows you to map specifically formatted content to a callback to
|
The [api:ShortcodeParser] API is simple parser that allows you to map specifically formatted content to a callback to
|
||||||
@ -12,17 +14,17 @@ in their WYSIWYG editor. Shortcodes are a semi-technical solution for this. A go
|
|||||||
viewer or a Google Map at a certain location.
|
viewer or a Google Map at a certain location.
|
||||||
|
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$text = "<h1>My Map</h1>[map]"
|
$text = "<h1>My Map</h1>[map]"
|
||||||
|
|
||||||
// Will output
|
// Will output
|
||||||
// <h1>My Map</h1><iframe ..></iframe>
|
// <h1>My Map</h1><iframe ..></iframe>
|
||||||
|
|
||||||
|
```
|
||||||
Here's some syntax variations:
|
Here's some syntax variations:
|
||||||
|
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
[my_shortcode]
|
[my_shortcode]
|
||||||
#
|
#
|
||||||
[my_shortcode /]
|
[my_shortcode /]
|
||||||
@ -31,23 +33,23 @@ Here's some syntax variations:
|
|||||||
#
|
#
|
||||||
[my_shortcode,myparameter="value"]Enclosed Content[/my_shortcode]
|
[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
|
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 [api:DataObject::$db] definitions of these types.
|
||||||
|
|
||||||
Other fields can be manually parsed with shortcodes through the `parse` method.
|
Other fields can be manually parsed with shortcodes through the `parse` method.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$text = "My awesome [my_shortcode] is here.";
|
$text = "My awesome [my_shortcode] is here.";
|
||||||
ShortcodeParser::get_active()->parse($text);
|
ShortcodeParser::get_active()->parse($text);
|
||||||
|
|
||||||
## Defining Custom Shortcodes
|
```
|
||||||
|
|
||||||
First we need to define a callback for the shortcode.
|
First we need to define a callback for the shortcode.
|
||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page extends SiteTree {
|
class Page extends SiteTree {
|
||||||
@ -61,7 +63,7 @@ First we need to define a callback for the shortcode.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
These parameters are passed to the `MyShortCodeMethod` callback:
|
```
|
||||||
|
|
||||||
- Any parameters attached to the shortcode as an associative array (keys are lower-case).
|
- 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
|
- Any content enclosed within the shortcode (if it is an enclosing shortcode). Note that any content within this
|
||||||
@ -77,12 +79,12 @@ To register a shortcode you call the following.
|
|||||||
|
|
||||||
**mysite/_config.php**
|
**mysite/_config.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// ShortcodeParser::get('default')->register($shortcode, $callback);
|
// ShortcodeParser::get('default')->register($shortcode, $callback);
|
||||||
|
|
||||||
ShortcodeParser::get('default')->register('my_shortcode', array('Page', 'MyShortCodeMethod'));
|
ShortcodeParser::get('default')->register('my_shortcode', array('Page', 'MyShortCodeMethod'));
|
||||||
|
|
||||||
|
```
|
||||||
## Built-in Shortcodes
|
## Built-in Shortcodes
|
||||||
|
|
||||||
SilverStripe comes with several shortcode parsers already.
|
SilverStripe comes with several shortcode parsers already.
|
||||||
@ -93,15 +95,15 @@ Internal page links keep references to their database IDs rather than the URL, i
|
|||||||
against moving the target page to a different location in the page tree. This is done through the `[sitetree_link]`
|
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.
|
shortcode, which takes an `id` parameter.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<a href="[sitetree_link,id=99]">
|
<a href="[sitetree_link,id=99]">
|
||||||
|
|
||||||
Links to internal `File` database records work exactly the same, but with the `[file_link]` shortcode.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<a href="[file_link,id=99]">
|
<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
|
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
|
special markup and attributes. OEmbed is a standard to discover these formats based on a simple URL, for example a
|
||||||
@ -112,8 +114,9 @@ custom `[embed]` shortcode.
|
|||||||
|
|
||||||
|
|
||||||
[embed width=480 height=270 class=left thumbnail=http://i1.ytimg.com/vi/lmWeD-vZAMY/hqdefault.jpg?r=8767]
|
[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
|
http://www.youtube.com/watch?v=lmWeD-vZAMY
|
||||||
[/embed]
|
```
|
||||||
|
|
||||||
|
|
||||||
### Attribute and element scope
|
### Attribute and element scope
|
||||||
@ -128,14 +131,15 @@ 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
|
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:
|
change the name of a tag. These usages are forbidden:
|
||||||
|
|
||||||
|
```
|
||||||
<[paragraph]>Some test</[paragraph]>
|
<[paragraph]>Some test</[paragraph]>
|
||||||
|
|
||||||
<a [titleattribute]>link</a>
|
<a [titleattribute]>link</a>
|
||||||
|
|
||||||
You may need to escape text inside attributes `>` becomes `>`, 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.
|
you need to be careful of nesting to ensure you don't break the output.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<!-- Good -->
|
<!-- Good -->
|
||||||
<div>
|
<div>
|
||||||
[shortcode]
|
[shortcode]
|
||||||
@ -152,33 +156,37 @@ you need to be careful of nesting to ensure you don't break the output.
|
|||||||
[/shortcode]
|
[/shortcode]
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
### Location
|
```
|
||||||
|
|
||||||
Element scoped shortcodes have a special ability to move the location they are inserted at to comply with HTML lexical
|
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:
|
rules. Take for example this basic paragraph tag:
|
||||||
|
|
||||||
|
```
|
||||||
<p><a href="#">Head [figure,src="assets/a.jpg",caption="caption"] Tail</a></p>
|
<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>
|
<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"
|
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:
|
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>
|
<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>
|
<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.
|
Here is a summary of the callback parameter values based on some example shortcodes.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function MyCustomShortCode($arguments, $content = null, $parser = null, $tagName) {
|
public function MyCustomShortCode($arguments, $content = null, $parser = null, $tagName) {
|
||||||
// ..
|
// ..
|
||||||
}
|
}
|
||||||
@ -203,16 +211,17 @@ Here is a summary of the callback parameter values based on some example shortco
|
|||||||
$parser => ShortcodeParser instance
|
$parser => ShortcodeParser instance
|
||||||
$tagName => 'my_shortcode'
|
$tagName => 'my_shortcode'
|
||||||
|
|
||||||
## Limitations
|
```
|
||||||
|
|
||||||
Since the shortcode parser is based on a simple regular expression it cannot properly handle nested shortcodes. For
|
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:
|
example the below code will not work as expected:
|
||||||
|
|
||||||
|
```
|
||||||
[shortcode]
|
[shortcode]
|
||||||
[shortcode][/shortcode]
|
[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
|
## Related Documentation
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Injector
|
title: Injector
|
||||||
summary: Introduction to using Dependency Injection within SilverStripe.
|
summary: Introduction to using Dependency Injection within SilverStripe.
|
||||||
|
icon: code
|
||||||
|
---
|
||||||
# Injector
|
# Injector
|
||||||
|
|
||||||
The [api:Injector] class is the central manager of inter-class dependencies in SilverStripe. It offers developers the
|
The [api:Injector] class is the central manager of inter-class dependencies in SilverStripe. It offers developers the
|
||||||
@ -18,22 +20,22 @@ Some of the goals of dependency injection are:
|
|||||||
|
|
||||||
The following sums up the simplest usage of the `Injector` it creates a new object of type `MyClassName` through `create`
|
The following sums up the simplest usage of the `Injector` it creates a new object of type `MyClassName` through `create`
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$object = Injector::inst()->create('MyClassName');
|
$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.
|
[Configuration API](../configuration) by developers.
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Injector:
|
Injector:
|
||||||
MyClassName:
|
MyClassName:
|
||||||
class: MyBetterClassName
|
class: MyBetterClassName
|
||||||
|
|
||||||
Repeated calls to `create()` create a new object each time.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$object = Injector::inst()->create('MyClassName');
|
$object = Injector::inst()->create('MyClassName');
|
||||||
$object2 = Injector::inst()->create('MyClassName');
|
$object2 = Injector::inst()->create('MyClassName');
|
||||||
|
|
||||||
@ -41,12 +43,12 @@ Repeated calls to `create()` create a new object each time.
|
|||||||
|
|
||||||
// returns true;
|
// returns true;
|
||||||
|
|
||||||
## Singleton Pattern
|
```
|
||||||
|
|
||||||
The `Injector` API can be used for the singleton pattern through `get()`. Subsequent calls to `get` return the same
|
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.
|
object instance as the first call.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// sets up MyClassName as a singleton
|
// sets up MyClassName as a singleton
|
||||||
$object = Injector::inst()->get('MyClassName');
|
$object = Injector::inst()->get('MyClassName');
|
||||||
$object2 = Injector::inst()->get('MyClassName');
|
$object2 = Injector::inst()->get('MyClassName');
|
||||||
@ -55,11 +57,11 @@ object instance as the first call.
|
|||||||
|
|
||||||
// returns true;
|
// returns true;
|
||||||
|
|
||||||
## Dependencies
|
```
|
||||||
|
|
||||||
The `Injector` API can be used to define the types of `$dependencies` that an object requires.
|
The `Injector` API can be used to define the types of `$dependencies` that an object requires.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
@ -77,9 +79,9 @@ The `Injector` API can be used to define the types of `$dependencies` that an ob
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
When creating a new instance of `MyController` the dependencies on that class will be met.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$object = Injector::inst()->get('MyController');
|
$object = Injector::inst()->get('MyController');
|
||||||
|
|
||||||
echo ($object->permissions instanceof PermissionService);
|
echo ($object->permissions instanceof PermissionService);
|
||||||
@ -88,11 +90,11 @@ When creating a new instance of `MyController` the dependencies on that class wi
|
|||||||
echo (is_string($object->textProperty));
|
echo (is_string($object->textProperty));
|
||||||
// returns true;
|
// returns true;
|
||||||
|
|
||||||
The [Configuration YAML](../configuration) does the hard work of configuring those `$dependencies` for us.
|
```
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Injector:
|
Injector:
|
||||||
PermissionService:
|
PermissionService:
|
||||||
class: MyCustomPermissionService
|
class: MyCustomPermissionService
|
||||||
@ -100,9 +102,9 @@ The [Configuration YAML](../configuration) does the hard work of configuring tho
|
|||||||
properties:
|
properties:
|
||||||
textProperty: 'My Text Value'
|
textProperty: 'My Text Value'
|
||||||
|
|
||||||
Now the dependencies will be replaced with our configuration.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$object = Injector::inst()->get('MyController');
|
$object = Injector::inst()->get('MyController');
|
||||||
|
|
||||||
echo ($object->permissions instanceof MyCustomPermissionService);
|
echo ($object->permissions instanceof MyCustomPermissionService);
|
||||||
@ -111,7 +113,7 @@ Now the dependencies will be replaced with our configuration.
|
|||||||
echo ($object->textProperty == 'My Text Value');
|
echo ($object->textProperty == 'My Text Value');
|
||||||
// returns true;
|
// returns true;
|
||||||
|
|
||||||
## Factories
|
```
|
||||||
|
|
||||||
Some services require non-trivial construction which means they must be created by a factory class. To do this, create
|
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
|
a factory class which implements the [api:SilverStripe\Framework\Injector\Factory] interface. You can then specify
|
||||||
@ -121,14 +123,14 @@ An example using the `MyFactory` service to create instances of the `MyService`
|
|||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Injector:
|
Injector:
|
||||||
MyService:
|
MyService:
|
||||||
factory: MyFactory
|
factory: MyFactory
|
||||||
|
|
||||||
**mysite/code/MyFactory.php**
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyFactory implements SilverStripe\Framework\Injector\Factory {
|
class MyFactory implements SilverStripe\Framework\Injector\Factory {
|
||||||
@ -141,18 +143,19 @@ An example using the `MyFactory` service to create instances of the `MyService`
|
|||||||
// Will use MyFactoryImplementation::create() to create the service instance.
|
// Will use MyFactoryImplementation::create() to create the service instance.
|
||||||
$instance = Injector::inst()->get('MyService');
|
$instance = Injector::inst()->get('MyService');
|
||||||
|
|
||||||
## Dependency overrides
|
```
|
||||||
|
|
||||||
To override the `$dependency` declaration for a class, define the following configuration file.
|
To override the `$dependency` declaration for a class, define the following configuration file.
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
|
```
|
||||||
MyController:
|
MyController:
|
||||||
dependencies:
|
dependencies:
|
||||||
textProperty: a string value
|
textProperty: a string value
|
||||||
permissions: %$PermissionService
|
permissions: %$PermissionService
|
||||||
|
|
||||||
## Managed objects
|
```
|
||||||
|
|
||||||
Simple dependencies can be specified by the `$dependencies`, but more complex configurations are possible by specifying
|
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.
|
constructor arguments, or by specifying more complex properties such as lists.
|
||||||
@ -162,7 +165,7 @@ runtime.
|
|||||||
|
|
||||||
Assuming a class structure such as
|
Assuming a class structure such as
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class RestrictivePermissionService {
|
class RestrictivePermissionService {
|
||||||
@ -183,11 +186,12 @@ Assuming a class structure such as
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
And the following configuration..
|
```
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
name: MyController
|
name: MyController
|
||||||
---
|
```
|
||||||
|
```
|
||||||
MyController:
|
MyController:
|
||||||
dependencies:
|
dependencies:
|
||||||
permissions: %$PermissionService
|
permissions: %$PermissionService
|
||||||
@ -201,13 +205,13 @@ And the following configuration..
|
|||||||
0: 'dbusername'
|
0: 'dbusername'
|
||||||
1: 'dbpassword'
|
1: 'dbpassword'
|
||||||
|
|
||||||
Calling..
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// sets up ClassName as a singleton
|
// sets up ClassName as a singleton
|
||||||
$controller = Injector::inst()->get('MyController');
|
$controller = Injector::inst()->get('MyController');
|
||||||
|
|
||||||
Would setup the following
|
```
|
||||||
|
|
||||||
* Create an object of type `MyController`
|
* Create an object of type `MyController`
|
||||||
* Look through the **dependencies** and call get('PermissionService')
|
* Look through the **dependencies** and call get('PermissionService')
|
||||||
@ -223,7 +227,7 @@ which may be later discarded, reverting the application to the original state. T
|
|||||||
|
|
||||||
This is useful when writing test cases, as certain services may be necessary to override for a single method call.
|
This is useful when writing test cases, as certain services may be necessary to override for a single method call.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// Setup default service
|
// Setup default service
|
||||||
Injector::inst()->registerService(new LiveService(), 'ServiceName');
|
Injector::inst()->registerService(new LiveService(), 'ServiceName');
|
||||||
|
|
||||||
@ -237,7 +241,7 @@ This is useful when writing test cases, as certain services may be necessary to
|
|||||||
// revert changes
|
// revert changes
|
||||||
Injector::unnest();
|
Injector::unnest();
|
||||||
|
|
||||||
|
```
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
|
||||||
* [api:Injector]
|
* [api:Injector]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Aspects
|
title: Aspects
|
||||||
summary: Introduction to using aspect-oriented programming with SilverStripe.
|
summary: Introduction to using aspect-oriented programming with SilverStripe.
|
||||||
|
icon: code
|
||||||
|
---
|
||||||
# Aspects
|
# Aspects
|
||||||
|
|
||||||
Aspect oriented programming is the idea that some logic abstractions can be applied across various type hierarchies
|
Aspect oriented programming is the idea that some logic abstractions can be applied across various type hierarchies
|
||||||
@ -10,9 +12,9 @@ Aspect oriented programming is the idea that some logic abstractions can be appl
|
|||||||
> functions from the main program's business logic. It aims to increase modularity by allowing the separation of
|
> 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.
|
> cross-cutting concerns, forming a basis for aspect-oriented software development.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
[Wikipedia](http://en.wikipedia.org/wiki/Aspect-oriented_programming) provides a much more in-depth explanation.
|
[Wikipedia](http://en.wikipedia.org/wiki/Aspect-oriented_programming) provides a much more in-depth explanation.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
In the context of the SilverStripe [Dependency Injector](injector), Aspects are achieved thanks to PHP's `__call` magic
|
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.
|
method combined with the `Proxy` Design Pattern.
|
||||||
@ -38,14 +40,14 @@ specific database server, whereas all read queries can be handled by slave serve
|
|||||||
|
|
||||||
A simplified implementation might look like the following.
|
A simplified implementation might look like the following.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
This doesn't cover all cases used by SilverStripe so is not a complete solution, more just a guide to how it would be
|
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.
|
used.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
**mysite/code/MySQLWriteDbAspect.php**
|
**mysite/code/MySQLWriteDbAspect.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MySQLWriteDbAspect implements BeforeCallAspect {
|
class MySQLWriteDbAspect implements BeforeCallAspect {
|
||||||
@ -59,7 +61,8 @@ used.
|
|||||||
'insert','update','delete','replace'
|
'insert','update','delete','replace'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
```
|
||||||
|
```
|
||||||
public function beforeCall($proxied, $method, $args, &$alternateReturn) {
|
public function beforeCall($proxied, $method, $args, &$alternateReturn) {
|
||||||
if (isset($args[0])) {
|
if (isset($args[0])) {
|
||||||
$sql = $args[0];
|
$sql = $args[0];
|
||||||
@ -73,12 +76,12 @@ used.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
object that's made use of above.
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
WriteMySQLDatabase:
|
WriteMySQLDatabase:
|
||||||
class: MySQLDatabase
|
class: MySQLDatabase
|
||||||
constructor:
|
constructor:
|
||||||
@ -88,24 +91,24 @@ object that's made use of above.
|
|||||||
password: pass
|
password: pass
|
||||||
database: write_database
|
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'.
|
of type `MySQLDatabase`, configured to point at the 'write_database'.
|
||||||
|
|
||||||
Next, this should be bound into an instance of the `Aspect` class
|
Next, this should be bound into an instance of the `Aspect` class
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
MySQLWriteDbAspect:
|
MySQLWriteDbAspect:
|
||||||
properties:
|
properties:
|
||||||
writeDb: %$WriteMySQLDatabase
|
writeDb: %$WriteMySQLDatabase
|
||||||
|
|
||||||
|
```
|
||||||
Next, we need to define the database connection that will be used for all non-write queries
|
Next, we need to define the database connection that will be used for all non-write queries
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
ReadMySQLDatabase:
|
ReadMySQLDatabase:
|
||||||
class: MySQLDatabase
|
class: MySQLDatabase
|
||||||
constructor:
|
constructor:
|
||||||
@ -115,12 +118,12 @@ Next, we need to define the database connection that will be used for all non-wr
|
|||||||
password: pass
|
password: pass
|
||||||
database: read_database
|
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.
|
object when the framework creates the database connection.
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
MySQLDatabase:
|
MySQLDatabase:
|
||||||
class: AopProxyService
|
class: AopProxyService
|
||||||
properties:
|
properties:
|
||||||
@ -129,7 +132,7 @@ object when the framework creates the database connection.
|
|||||||
query:
|
query:
|
||||||
- %$MySQLWriteDbAspect
|
- %$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.
|
- **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
|
- **beforeCall** : A hash of method\_name => array containing objects that are to be evaluated _before_ a call to the
|
||||||
@ -139,7 +142,7 @@ Overall configuration for this would look as follows
|
|||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Injector:
|
Injector:
|
||||||
ReadMySQLDatabase:
|
ReadMySQLDatabase:
|
||||||
class: MySQLDatabase
|
class: MySQLDatabase
|
||||||
@ -168,19 +171,19 @@ Overall configuration for this would look as follows
|
|||||||
query:
|
query:
|
||||||
- %$MySQLWriteDbAspect
|
- %$MySQLWriteDbAspect
|
||||||
|
|
||||||
|
```
|
||||||
## Changing what a method returns
|
## 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.
|
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`
|
As seen in the above example, the `beforeCall` method modifies the `&$alternateReturn` variable, and returns `false`
|
||||||
after doing so.
|
after doing so.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$alternateReturn = $this->writeDb->query($sql, $code);
|
$alternateReturn = $this->writeDb->query($sql, $code);
|
||||||
|
|
||||||
return false;
|
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
|
handlers defined for the called method. Assigning the `$alternateReturn` variable also indicates to return that value
|
||||||
to the caller of the method.
|
to the caller of the method.
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Custom Templates
|
title: Custom Templates
|
||||||
summary: Override templates from core and modules in your application
|
summary: Override templates from core and modules in your application
|
||||||
|
icon: code
|
||||||
|
---
|
||||||
# Custom Templates
|
# Custom Templates
|
||||||
|
|
||||||
See [Template Inheritance](../templates).
|
See [Template Inheritance](../templates).
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
title: How to Publish a SilverStripe module
|
title: How to Publish a SilverStripe module
|
||||||
|
summary: Have you created some work you think others can use? Turn it into a module and share it.
|
||||||
|
icon: rocket
|
||||||
|
---
|
||||||
# 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,
|
If you wish to submit your module to our public directory, you take responsibility for a certain level of code quality,
|
||||||
@ -16,7 +19,7 @@ A basic usage of a module for 3.1 that requires the CMS would look similar to
|
|||||||
this:
|
this:
|
||||||
|
|
||||||
**mycustommodule/composer.json**
|
**mycustommodule/composer.json**
|
||||||
:::js
|
```js
|
||||||
{
|
{
|
||||||
"name": "your-vendor-name/module-name",
|
"name": "your-vendor-name/module-name",
|
||||||
"description": "One-liner describing your module",
|
"description": "One-liner describing your module",
|
||||||
@ -43,7 +46,7 @@ this:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Once your module is published online with a service like Github.com or Bitbucket.com, submit the repository to
|
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
|
[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.
|
up by [addons.silverstripe.org](http://addons.silverstripe.org/) website.
|
||||||
@ -59,9 +62,9 @@ Say you have a module which supports SilverStripe 3.0. A new release of this mod
|
|||||||
in SilverStripe 3.1. In this case, you would create a new branch for the 3.0 compatible code base of your module. This
|
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.
|
allows you to continue fixing bugs on this older release branch.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
As a convention, the `master` branch of your module should always work with the `master` branch of SilverStripe.
|
As a convention, the `master` branch of your module should always work with the `master` branch of SilverStripe.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
Other branches should be created on your module as needed if they're required to support specific SilverStripe releases.
|
Other branches should be created on your module as needed if they're required to support specific SilverStripe releases.
|
||||||
|
|
||||||
|
@ -1,33 +1,17 @@
|
|||||||
|
---
|
||||||
title: How to Create a Google Maps Shortcode
|
title: How to Create a Google Maps Shortcode
|
||||||
|
summary: Learn how to embed a Google map in the WYSIWYG editor with a simple shortcode
|
||||||
|
icon: map
|
||||||
|
---
|
||||||
# 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
|
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:
|
address. We want our CMS authors to be able to embed the map using the following code:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
[googlemap,width=500,height=300]97-99 Courtenay Place, Wellington, New Zealand[/googlemap]
|
[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.
|
We'll add defaults to those in our shortcode parser so they're optional.
|
||||||
|
|
||||||
**mysite/_config.php**
|
**mysite/_config.php**
|
||||||
|
|
||||||
:::php
|
|
||||||
ShortcodeParser::get('default')->register('googlemap', function($arguments, $address, $parser, $shortcode) {
|
|
||||||
$iframeUrl = sprintf(
|
|
||||||
'http://maps.google.com/maps?q=%s&hnear=%s&ie=UTF8&hq=&t=m&z=14&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
|
|
||||||
);
|
|
||||||
});
|
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Track member logins
|
||||||
|
summary: Keep a log in the database of who logs in and when
|
||||||
|
icon: user-friends
|
||||||
|
---
|
||||||
|
|
||||||
# Howto: Track Member Logins
|
# Howto: Track Member Logins
|
||||||
|
|
||||||
Sometimes its good to know how active your users are,
|
Sometimes its good to know how active your users are,
|
||||||
@ -9,7 +15,7 @@ often the member has visited. Or more specifically,
|
|||||||
how often he has started a browser session, either through
|
how often he has started a browser session, either through
|
||||||
explicitly logging in or by invoking the "remember me" functionality.
|
explicitly logging in or by invoking the "remember me" functionality.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class MyMemberExtension extends DataExtension {
|
class MyMemberExtension extends DataExtension {
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
@ -43,10 +49,11 @@ explicitly logging in or by invoking the "remember me" functionality.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Now you just need to apply this extension through your config:
|
```
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Member:
|
Member:
|
||||||
extensions:
|
extensions:
|
||||||
- MyMemberExtension
|
- MyMemberExtension
|
||||||
|
|
||||||
|
```
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: How To's
|
||||||
|
---
|
||||||
|
# How To's: Extending Silverstripe
|
||||||
|
|
||||||
|
[CHILDREN]
|
@ -1,7 +1,9 @@
|
|||||||
|
---
|
||||||
title: Extending SilverStripe
|
title: Extending SilverStripe
|
||||||
summary: Understand the ways to modify the built-in functionality through Extensions, Subclassing and Dependency Injection.
|
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.
|
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.
|
||||||
|
icon: code
|
||||||
|
---
|
||||||
No two applications are ever going to be the same and SilverStripe is built with this in mind. The core framework
|
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
|
includes common functionality and default behaviors easily complemented with add-ons such as modules, widgets and
|
||||||
themes.
|
themes.
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: Unit and Integration Testing
|
title: Unit and Integration Testing
|
||||||
summary: Test models, database logic and your object methods.
|
summary: Test models, database logic and your object methods.
|
||||||
|
---
|
||||||
# Unit and Integration Testing
|
# 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
|
A Unit Test is an automated piece of code that invokes a unit of work in the application and then checks the behavior
|
||||||
@ -8,7 +9,7 @@ to ensure that it works as it should. A simple example would be to test the resu
|
|||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page extends SiteTree {
|
class Page extends SiteTree {
|
||||||
@ -18,9 +19,9 @@ to ensure that it works as it should. A simple example would be to test the resu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
**mysite/tests/PageTest.php**
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class PageTest extends SapphireTest {
|
class PageTest extends SapphireTest {
|
||||||
@ -30,26 +31,26 @@ to ensure that it works as it should. A simple example would be to test the resu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<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
|
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.
|
the `(modulename)/tests` directory.
|
||||||
|
|
||||||
Test case classes should end with `Test` (e.g PageTest) and test methods must start with `test` (e.g testMyMethod).
|
Test case classes should end with `Test` (e.g PageTest) and test methods must start with `test` (e.g testMyMethod).
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
A SilverStripe unit test is created by extending one of two classes, [api:SapphireTest] or [api:FunctionalTest].
|
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
|
[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.
|
you want to test a `Controller`, `Form` or anything that requires a web page.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
`FunctionalTest` is a subclass of `SapphireTest` so will inherit all of the behaviors. By subclassing `FunctionalTest`
|
`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.
|
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
|
`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
|
the [PHPUnit](http://www.phpunit.de) documentation. It provides a lot of fundamental concepts that we build on in this
|
||||||
documentation.
|
documentation.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
## Running Tests
|
## Running Tests
|
||||||
|
|
||||||
@ -57,7 +58,7 @@ documentation.
|
|||||||
|
|
||||||
The `phpunit` binary should be used from the root directory of your website.
|
The `phpunit` binary should be used from the root directory of your website.
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
phpunit
|
phpunit
|
||||||
# Runs all tests
|
# Runs all tests
|
||||||
|
|
||||||
@ -73,34 +74,35 @@ The `phpunit` binary should be used from the root directory of your website.
|
|||||||
phpunit framework/tests '' flush=all
|
phpunit framework/tests '' flush=all
|
||||||
# Run tests with optional `$_GET` parameters (you need an empty second argument)
|
# Run tests with optional `$_GET` parameters (you need an empty second argument)
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
```
|
||||||
The manifest is not flushed when running tests. Add `flush=all` to the test command to do this (see above example.)
|
The manifest is not flushed when running tests. Add `flush=all` to the test command to do this (see above example.)
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
If phpunit is not installed globally on your machine, you may need to replace the above usage of `phpunit` with the full
|
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`)
|
path (e.g `vendor/bin/phpunit framework/tests`)
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
All command-line arguments are documented on [phpunit.de](http://www.phpunit.de/manual/current/en/textui.html).
|
All command-line arguments are documented on [phpunit.de](http://www.phpunit.de/manual/current/en/textui.html).
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
### Via a Web Browser
|
### Via a Web Browser
|
||||||
|
|
||||||
Executing tests from the command line is recommended, since it most closely reflects test runs in any automated testing
|
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.
|
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
|
http://yoursite.com/dev/tests
|
||||||
|
|
||||||
|
```
|
||||||
### Via the CLI
|
### Via the CLI
|
||||||
|
|
||||||
The [sake](../cli) executable that comes with SilverStripe can trigger a customised [api:TestRunner] class that
|
The [sake](../cli) executable that comes with SilverStripe can trigger a customised [api:TestRunner] class that
|
||||||
handles the PHPUnit configuration and output formatting. While the custom test runner a handy tool, it's also more
|
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.
|
limited than using `phpunit` directly, particularly around formatting test output.
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
sake dev/tests/all
|
sake dev/tests/all
|
||||||
# Run all tests
|
# Run all tests
|
||||||
|
|
||||||
@ -116,13 +118,13 @@ limited than using `phpunit` directly, particularly around formatting test outpu
|
|||||||
sake dev/tests/all SkipTests=MySkippedTest
|
sake dev/tests/all SkipTests=MySkippedTest
|
||||||
# Skip some tests
|
# Skip some tests
|
||||||
|
|
||||||
## Making Tests Run Fast
|
```
|
||||||
A major impedement to testing is that by default tests are extremely slow to run. There are two things that can be done to speed them up:
|
A major impedement to testing is that by default tests are extremely slow to run. There are two things that can be done to speed them up:
|
||||||
|
|
||||||
### Disable xDebug
|
### Disable xDebug
|
||||||
Unless executing a coverage report there is no need to have xDebug enabled.
|
Unless executing a coverage report there is no need to have xDebug enabled.
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
# Disable xdebug
|
# Disable xdebug
|
||||||
sudo php5dismod xdebug
|
sudo php5dismod xdebug
|
||||||
|
|
||||||
@ -132,10 +134,11 @@ Unless executing a coverage report there is no need to have xDebug enabled.
|
|||||||
# Enable xdebug
|
# Enable xdebug
|
||||||
sudo php5enmod xdebug
|
sudo php5enmod xdebug
|
||||||
|
|
||||||
|
```
|
||||||
### Use SQLite In Memory
|
### Use SQLite In Memory
|
||||||
SQLIte can be configured to run in memory as opposed to disk and this makes testing an order of magnitude faster. To effect this change add the following to mysite/_config.php - this enables an optional flag to switch between MySQL and SQLite. Note also that the package silverstripe/sqlite3 will need installed, version will vary depending on which version of SilverStripe is being tested.
|
SQLIte can be configured to run in memory as opposed to disk and this makes testing an order of magnitude faster. To effect this change add the following to mysite/_config.php - this enables an optional flag to switch between MySQL and SQLite. Note also that the package silverstripe/sqlite3 will need installed, version will vary depending on which version of SilverStripe is being tested.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
if(Director::isDev()) {
|
if(Director::isDev()) {
|
||||||
if(isset($_GET['db']) && ($db = $_GET['db'])) {
|
if(isset($_GET['db']) && ($db = $_GET['db'])) {
|
||||||
global $databaseConfig;
|
global $databaseConfig;
|
||||||
@ -146,11 +149,12 @@ SQLIte can be configured to run in memory as opposed to disk and this makes test
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
To use SQLite append '' db=sqlite3 after the phpunit command.
|
```
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
phpunit framework/tests '' db=sqlite3
|
phpunit framework/tests '' db=sqlite3
|
||||||
|
|
||||||
|
```
|
||||||
### Speed Comparison
|
### Speed Comparison
|
||||||
Testing against a medium sized module with 93 tests:
|
Testing against a medium sized module with 93 tests:
|
||||||
* SQLite - 16.15s
|
* SQLite - 16.15s
|
||||||
@ -163,15 +167,15 @@ SilverStripe tests create their own database when the test starts. New `ss_tmp`
|
|||||||
connection details you provide for the main website. The new `ss_tmp` database does not copy what is currently in your
|
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.
|
application database. To provide seed data use a [Fixture](fixtures) file.
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
As the test runner will create new databases for the tests to run, the database user should have the appropriate
|
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.
|
permissions to create new databases on your server.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
The test database is rebuilt every time one of the test methods is run. Over time, you may have several hundred test
|
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`
|
databases on your machine. To get rid of them is a call to `http://yoursite.com/dev/tests/cleanupdb`
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Custom PHPUnit Configuration
|
## Custom PHPUnit Configuration
|
||||||
|
|
||||||
@ -181,7 +185,7 @@ needs.
|
|||||||
|
|
||||||
**phpunit.xml**
|
**phpunit.xml**
|
||||||
|
|
||||||
:::xml
|
```xml
|
||||||
<phpunit bootstrap="framework/tests/bootstrap.php" colors="true">
|
<phpunit bootstrap="framework/tests/bootstrap.php" colors="true">
|
||||||
<testsuite name="Default">
|
<testsuite name="Default">
|
||||||
<directory>mysite/tests</directory>
|
<directory>mysite/tests</directory>
|
||||||
@ -200,9 +204,9 @@ needs.
|
|||||||
</groups>
|
</groups>
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
```
|
||||||
This configuration file doesn't apply for running tests through the "sake" wrapper
|
This configuration file doesn't apply for running tests through the "sake" wrapper
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
|
|
||||||
### setUp() and tearDown()
|
### setUp() and tearDown()
|
||||||
@ -211,7 +215,7 @@ In addition to loading data through a [Fixture File](fixtures), a test case may
|
|||||||
run before each test method. For this, use the PHPUnit `setUp` and `tearDown` methods. These are run at the start and
|
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.
|
end of each test.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class PageTest extends SapphireTest {
|
class PageTest extends SapphireTest {
|
||||||
@ -239,10 +243,10 @@ end of each test.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
`tearDownOnce` and `setUpOnce` can be used to run code just once for the file rather than before and after each
|
```
|
||||||
individual test case.
|
individual test case.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class PageTest extends SapphireTest {
|
class PageTest extends SapphireTest {
|
||||||
@ -260,6 +264,7 @@ individual test case.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
### Config and Injector Nesting
|
### Config and Injector Nesting
|
||||||
|
|
||||||
A powerful feature of both [`Config`](/developer_guides/configuration/configuration/) and [`Injector`](/developer_guides/extending/injector/) is the ability to "nest" them so that you can make changes that can easily be discarded without having to manage previous values.
|
A powerful feature of both [`Config`](/developer_guides/configuration/configuration/) and [`Injector`](/developer_guides/extending/injector/) is the ability to "nest" them so that you can make changes that can easily be discarded without having to manage previous values.
|
||||||
@ -270,7 +275,7 @@ If you need to make changes to `Config` (or `Injector) for each test (or the who
|
|||||||
|
|
||||||
It's important to remember that the `parent::setUp();` functions will need to be called first to ensure the nesting feature works as expected.
|
It's important to remember that the `parent::setUp();` functions will need to be called first to ensure the nesting feature works as expected.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
function setUpOnce() {
|
function setUpOnce() {
|
||||||
parent::setUpOnce();
|
parent::setUpOnce();
|
||||||
//this will remain for the whole suite and be removed for any other tests
|
//this will remain for the whole suite and be removed for any other tests
|
||||||
@ -286,27 +291,27 @@ It's important to remember that the `parent::setUp();` functions will need to be
|
|||||||
Config::inst()->get('ClassName', 'var_name'); // this will be 'var_value'
|
Config::inst()->get('ClassName', 'var_name'); // this will be 'var_value'
|
||||||
}
|
}
|
||||||
|
|
||||||
## Generating a Coverage Report
|
```
|
||||||
|
|
||||||
PHPUnit can generate a code coverage report ([docs](http://www.phpunit.de/manual/current/en/code-coverage-analysis.html))
|
PHPUnit can generate a code coverage report ([docs](http://www.phpunit.de/manual/current/en/code-coverage-analysis.html))
|
||||||
by executing the following commands.
|
by executing the following commands.
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
phpunit --coverage-html assets/coverage-report
|
phpunit --coverage-html assets/coverage-report
|
||||||
# Generate coverage report for the whole project
|
# Generate coverage report for the whole project
|
||||||
|
|
||||||
phpunit --coverage-html assets/coverage-report mysite/tests/
|
```
|
||||||
# Generate coverage report for the "mysite" module
|
# Generate coverage report for the "mysite" module
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
These commands will output a report to the `assets/coverage-report/` folder. To view the report, open the `index.html`
|
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.
|
file within a web browser.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
Typically, only your own custom PHP code in your project should be regarded when producing these reports. To exclude
|
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.
|
some `thirdparty/` directories add the following to the `phpunit.xml` configuration file.
|
||||||
|
|
||||||
:::xml
|
```xml
|
||||||
<filter>
|
<filter>
|
||||||
<blacklist>
|
<blacklist>
|
||||||
<directory suffix=".php">framework/dev/</directory>
|
<directory suffix=".php">framework/dev/</directory>
|
||||||
@ -318,7 +323,7 @@ some `thirdparty/` directories add the following to the `phpunit.xml` configurat
|
|||||||
</blacklist>
|
</blacklist>
|
||||||
</filter>
|
</filter>
|
||||||
|
|
||||||
## Related Documentation
|
```
|
||||||
|
|
||||||
* [How to Write a SapphireTest](how_tos/write_a_sapphiretest)
|
* [How to Write a SapphireTest](how_tos/write_a_sapphiretest)
|
||||||
* [How to Write a FunctionalTest](how_tos/write_a_functionaltest)
|
* [How to Write a FunctionalTest](how_tos/write_a_functionaltest)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: Functional Testing
|
title: Functional Testing
|
||||||
summary: Test controllers, forms and HTTP responses.
|
summary: Test controllers, forms and HTTP responses.
|
||||||
|
---
|
||||||
# Functional Testing
|
# Functional Testing
|
||||||
|
|
||||||
[api:FunctionalTest] test your applications `Controller` logic and anything else which requires a web request. The
|
[api:FunctionalTest] test your applications `Controller` logic and anything else which requires a web request. The
|
||||||
@ -9,93 +10,93 @@ creating [api:SS_HTTPRequest], receiving [api:SS_HTTPResponse] objects and modif
|
|||||||
|
|
||||||
## Get
|
## Get
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$page = $this->get($url);
|
$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.
|
of the response.
|
||||||
|
|
||||||
## Post
|
## Post
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$page = $this->post($url);
|
$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.
|
of the response.
|
||||||
|
|
||||||
## Submit
|
## Submit
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$submit = $this->submitForm($formID, $button = null, $data = array());
|
$submit = $this->submitForm($formID, $button = null, $data = array());
|
||||||
|
|
||||||
Submits the given form (`#ContactForm`) on the current page and returns the [api:SS_HTTPResponse].
|
```
|
||||||
|
|
||||||
## LogInAs
|
## LogInAs
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$this->logInAs($member);
|
$this->logInAs($member);
|
||||||
|
|
||||||
Logs a given user in, sets the current session. To log all users out pass `null` to the method.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$this->logInAs(null);
|
$this->logInAs(null);
|
||||||
|
|
||||||
## Assertions
|
```
|
||||||
|
|
||||||
The `FunctionalTest` class also provides additional asserts to validate your tests.
|
The `FunctionalTest` class also provides additional asserts to validate your tests.
|
||||||
|
|
||||||
### assertPartialMatchBySelector
|
### assertPartialMatchBySelector
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$this->assertPartialMatchBySelector('p.good',array(
|
$this->assertPartialMatchBySelector('p.good',array(
|
||||||
'Test save was successful'
|
'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
|
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.
|
assertion fails if one of the expectedMatches fails to appear.
|
||||||
|
|
||||||
|
|
||||||
### assertExactMatchBySelector
|
### assertExactMatchBySelector
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$this->assertExactMatchBySelector("#MyForm_ID p.error", array(
|
$this->assertExactMatchBySelector("#MyForm_ID p.error", array(
|
||||||
"That email address is invalid."
|
"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
|
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.
|
assertion fails if one of the expectedMatches fails to appear.
|
||||||
|
|
||||||
### assertPartialHTMLMatchBySelector
|
### assertPartialHTMLMatchBySelector
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$this->assertPartialHTMLMatchBySelector("#MyForm_ID p.error", array(
|
$this->assertPartialHTMLMatchBySelector("#MyForm_ID p.error", array(
|
||||||
"That email address is invalid."
|
"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
|
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.
|
assertion fails if one of the expectedMatches fails to appear.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
`&nbsp;` characters are stripped from the content; make sure that your assertions take this into account.
|
`&nbsp;` characters are stripped from the content; make sure that your assertions take this into account.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
### assertExactHTMLMatchBySelector
|
### assertExactHTMLMatchBySelector
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$this->assertExactHTMLMatchBySelector("#MyForm_ID p.error", array(
|
$this->assertExactHTMLMatchBySelector("#MyForm_ID p.error", array(
|
||||||
"That email address is invalid."
|
"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
|
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.
|
assertion fails if one of the expectedMatches fails to appear.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
`&nbsp;` characters are stripped from the content; make sure that your assertions take this into account.
|
`&nbsp;` characters are stripped from the content; make sure that your assertions take this into account.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: Behavior Testing
|
title: Behavior Testing
|
||||||
summary: Describe how your application should behave in plain text and run tests in a browser.
|
summary: Describe how your application should behave in plain text and run tests in a browser.
|
||||||
|
---
|
||||||
# Behavior Testing
|
# Behavior Testing
|
||||||
|
|
||||||
For behavior testing in SilverStripe, check out
|
For behavior testing in SilverStripe, check out
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: Fixtures
|
title: Fixtures
|
||||||
summary: Populate test databases with fake seed data.
|
summary: Populate test databases with fake seed data.
|
||||||
|
---
|
||||||
# Fixtures
|
# Fixtures
|
||||||
|
|
||||||
To test functionality correctly, we must use consistent data. If we are testing our code with the same data each
|
To test functionality correctly, we must use consistent data. If we are testing our code with the same data each
|
||||||
@ -14,7 +15,7 @@ To include your fixture file in your tests, you should define it as your `$fixtu
|
|||||||
|
|
||||||
**mysite/tests/MyNewTest.php**
|
**mysite/tests/MyNewTest.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyNewTest extends SapphireTest {
|
class MyNewTest extends SapphireTest {
|
||||||
@ -23,12 +24,12 @@ To include your fixture file in your tests, you should define it as your `$fixtu
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
You can also use an array of fixture files, if you want to use parts of multiple other tests:
|
```
|
||||||
|
|
||||||
|
|
||||||
**mysite/tests/MyNewTest.php**
|
**mysite/tests/MyNewTest.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyNewTest extends SapphireTest {
|
class MyNewTest extends SapphireTest {
|
||||||
@ -40,12 +41,13 @@ You can also use an array of fixture files, if you want to use parts of multiple
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Typically, you'd have a separate fixture file for each class you are testing - although overlap between tests is common.
|
Typically, you'd have a separate fixture file for each class you are testing - although overlap between tests is common.
|
||||||
|
|
||||||
Fixtures are defined in `YAML`. `YAML` is a markup language which is deliberately simple and easy to read, so it is
|
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:
|
ideal for fixture generation. Say we have the following two DataObjects:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -71,11 +73,11 @@ ideal for fixture generation. Say we have the following two DataObjects:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
We can represent multiple instances of them in `YAML` as follows:
|
```
|
||||||
|
|
||||||
**mysite/tests/fixtures.yml**
|
**mysite/tests/fixtures.yml**
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Team:
|
Team:
|
||||||
hurricanes:
|
hurricanes:
|
||||||
Name: The Hurricanes
|
Name: The Hurricanes
|
||||||
@ -94,16 +96,16 @@ We can represent multiple instances of them in `YAML` as follows:
|
|||||||
Name: Jack
|
Name: Jack
|
||||||
Team: =>Team.crusaders
|
Team: =>Team.crusaders
|
||||||
|
|
||||||
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.
|
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
|
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`
|
represents a new object and can be referenced in the PHP using `objFromFixture`
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$player = $this->objFromFixture('Player', 'jack');
|
$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
|
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 `=>`.
|
seen by the fields prefixed with `=>`.
|
||||||
@ -111,25 +113,25 @@ 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
|
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.
|
to `=>Team.` followed by a team name.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
Take the player John in our example YAML, his team is the Hurricanes which is represented by `=>Team.hurricanes`. This
|
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`.
|
sets the `has_one` relationship for John with with the `Team` object `hurricanes`.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
[hint]
|
||||||
Note that we use the name of the relationship (Team), and not the name of the
|
Note that we use the name of the relationship (Team), and not the name of the
|
||||||
database field (TeamID).
|
database field (TeamID).
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
[hint]
|
||||||
Also be aware the target of a relationship must be defined before it is referenced, for example the `hurricanes` team must appear in the fixture file before the line `Team: =>Team.hurricanes`.
|
Also be aware the target of a relationship must be defined before it is referenced, for example the `hurricanes` team must appear in the fixture file before the line `Team: =>Team.hurricanes`.
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
This style of relationship declaration can be used for any type of relationship (i.e `has_one`, `has_many`, `many_many`).
|
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:
|
We can also declare the relationships conversely. Another way we could write the previous example is:
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Player:
|
Player:
|
||||||
john:
|
john:
|
||||||
Name: John
|
Name: John
|
||||||
@ -147,11 +149,11 @@ We can also declare the relationships conversely. Another way we could write the
|
|||||||
Origin: Canterbury
|
Origin: Canterbury
|
||||||
Players: =>Player.joe,=>Player.jack
|
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
|
calling `write()` on those objects. Take for instance the `hurricances` record in the `YAML`. It is equivalent to
|
||||||
writing:
|
writing:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$team = new Team(array(
|
$team = new Team(array(
|
||||||
'Name' => 'Hurricanes',
|
'Name' => 'Hurricanes',
|
||||||
'Origin' => 'Wellington'
|
'Origin' => 'Wellington'
|
||||||
@ -161,17 +163,17 @@ writing:
|
|||||||
|
|
||||||
$team->Players()->add($john);
|
$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
|
As the YAML fixtures will call `write`, any `onBeforeWrite()` or default value logic will be executed as part of the
|
||||||
test.
|
test.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
### Defining many_many_extraFields
|
### Defining many_many_extraFields
|
||||||
|
|
||||||
`many_many` relations can have additional database fields attached to the relationship. For example we may want to
|
`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.
|
declare the role each player has in the team.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
|
|
||||||
private static $db = array (
|
private static $db = array (
|
||||||
@ -200,9 +202,9 @@ declare the role each player has in the team.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
To provide the value for the `many_many_extraField` use the YAML list syntax.
|
```
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Player:
|
Player:
|
||||||
john:
|
john:
|
||||||
Name: John
|
Name: John
|
||||||
@ -215,8 +217,9 @@ To provide the value for the `many_many_extraField` use the YAML list syntax.
|
|||||||
Name: The Hurricanes
|
Name: The Hurricanes
|
||||||
Players:
|
Players:
|
||||||
- =>Player.john:
|
- =>Player.john:
|
||||||
Role: Captain
|
```
|
||||||
|
|
||||||
|
```
|
||||||
crusaders:
|
crusaders:
|
||||||
Name: The Crusaders
|
Name: The Crusaders
|
||||||
Players:
|
Players:
|
||||||
@ -225,16 +228,16 @@ To provide the value for the `many_many_extraField` use the YAML list syntax.
|
|||||||
- =>Player.jack:
|
- =>Player.jack:
|
||||||
Role: Winger
|
Role: Winger
|
||||||
|
|
||||||
## Fixture Factories
|
```
|
||||||
|
|
||||||
While manually defined fixtures provide full flexibility, they offer very little in terms of structure and convention.
|
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
|
Alternatively, you can use the [api:FixtureFactory] class, which allows you to set default values, callbacks on object
|
||||||
creation, and dynamic/lazy value setting.
|
creation, and dynamic/lazy value setting.
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
[hint]
|
||||||
SapphireTest uses FixtureFactory under the hood when it is provided with YAML based fixtures.
|
SapphireTest uses FixtureFactory under the hood when it is provided with YAML based fixtures.
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
The idea is that rather than instantiating objects directly, we'll have a factory class for them. This factory can have
|
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
|
*blueprints* defined on it, which tells the factory how to instantiate an object of a specific type. Blueprints need a
|
||||||
@ -243,45 +246,45 @@ 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
|
Blueprints are auto-created for all available DataObject subclasses, you only need to instantiate a factory to start
|
||||||
using them.
|
using them.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$factory = Injector::inst()->create('FixtureFactory');
|
$factory = Injector::inst()->create('FixtureFactory');
|
||||||
|
|
||||||
$obj = $factory->createObject('Team', 'hurricanes');
|
$obj = $factory->createObject('Team', 'hurricanes');
|
||||||
|
|
||||||
In order to create an object with certain properties, just add a third argument:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$obj = $factory->createObject('Team', 'hurricanes', array(
|
$obj = $factory->createObject('Team', 'hurricanes', array(
|
||||||
'Name' => 'My Value'
|
'Name' => 'My Value'
|
||||||
));
|
));
|
||||||
|
|
||||||
<div class="warning" markdown="1">
|
```
|
||||||
It is important to remember that fixtures are referenced by arbitrary identifiers ('hurricanes'). These are internally
|
It is important to remember that fixtures are referenced by arbitrary identifiers ('hurricanes'). These are internally
|
||||||
mapped to their database identifiers.
|
mapped to their database identifiers.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
After we've created this object in the factory, `getId` is used to retrieve it by the identifier.
|
After we've created this object in the factory, `getId` is used to retrieve it by the identifier.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$databaseId = $factory->getId('Team', 'hurricanes');
|
$databaseId = $factory->getId('Team', 'hurricanes');
|
||||||
|
|
||||||
|
```
|
||||||
### Default Properties
|
### Default Properties
|
||||||
|
|
||||||
Blueprints can be overwritten in order to customise their behavior. For example, if a Fixture does not provide a Team
|
Blueprints can be overwritten in order to customise their behavior. For example, if a Fixture does not provide a Team
|
||||||
name, we can set the default to be `Unknown Team`.
|
name, we can set the default to be `Unknown Team`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$factory->define('Team', array(
|
$factory->define('Team', array(
|
||||||
'Name' => 'Unknown Team'
|
'Name' => 'Unknown Team'
|
||||||
));
|
));
|
||||||
|
|
||||||
### Dependent Properties
|
```
|
||||||
|
|
||||||
Values can be set on demand through anonymous functions, which can either generate random defaults, or create composite
|
Values can be set on demand through anonymous functions, which can either generate random defaults, or create composite
|
||||||
values based on other fixture data.
|
values based on other fixture data.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$factory->define('Member', array(
|
$factory->define('Member', array(
|
||||||
'Email' => function($obj, $data, $fixtures) {
|
'Email' => function($obj, $data, $fixtures) {
|
||||||
if(isset($data['FirstName']) {
|
if(isset($data['FirstName']) {
|
||||||
@ -293,22 +296,22 @@ values based on other fixture data.
|
|||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
### Relations
|
```
|
||||||
|
|
||||||
Model relations can be expressed through the same notation as in the YAML fixture format described earlier, through the
|
Model relations can be expressed through the same notation as in the YAML fixture format described earlier, through the
|
||||||
`=>` prefix on data values.
|
`=>` prefix on data values.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$obj = $factory->createObject('Team', 'hurricanes', array(
|
$obj = $factory->createObject('Team', 'hurricanes', array(
|
||||||
'MyHasManyRelation' => '=>Player.john,=>Player.joe'
|
'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
|
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.
|
publish a page, which requires a method call.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$blueprint = Injector::inst()->create('FixtureBlueprint', 'Member');
|
$blueprint = Injector::inst()->create('FixtureBlueprint', 'Member');
|
||||||
|
|
||||||
$blueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
|
$blueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
|
||||||
@ -317,7 +320,7 @@ publish a page, which requires a method call.
|
|||||||
|
|
||||||
$page = $factory->define('Page', $blueprint);
|
$page = $factory->define('Page', $blueprint);
|
||||||
|
|
||||||
Available callbacks:
|
```
|
||||||
|
|
||||||
* `beforeCreate($identifier, $data, $fixtures)`
|
* `beforeCreate($identifier, $data, $fixtures)`
|
||||||
* `afterCreate($obj, $identifier, $data, $fixtures)`
|
* `afterCreate($obj, $identifier, $data, $fixtures)`
|
||||||
@ -328,7 +331,7 @@ Data of the same type can have variations, for example forum members vs. CMS adm
|
|||||||
class, but have completely different properties. This is where named blueprints come in. By default, blueprint names
|
class, but have completely different properties. This is where named blueprints come in. By default, blueprint names
|
||||||
equal the class names they manage.
|
equal the class names they manage.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$memberBlueprint = Injector::inst()->create('FixtureBlueprint', 'Member', 'Member');
|
$memberBlueprint = Injector::inst()->create('FixtureBlueprint', 'Member', 'Member');
|
||||||
|
|
||||||
$adminBlueprint = Injector::inst()->create('FixtureBlueprint', 'AdminMember', 'Member');
|
$adminBlueprint = Injector::inst()->create('FixtureBlueprint', 'AdminMember', 'Member');
|
||||||
@ -344,7 +347,7 @@ equal the class names they manage.
|
|||||||
|
|
||||||
$admin = $factory->createObject('AdminMember'); // in admin group
|
$admin = $factory->createObject('AdminMember'); // in admin group
|
||||||
|
|
||||||
## Related Documentation
|
```
|
||||||
|
|
||||||
* [How to use a FixtureFactory](how_tos/fixturefactories/)
|
* [How to use a FixtureFactory](how_tos/fixturefactories/)
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
---
|
||||||
title: Testing Glossary
|
title: Testing Glossary
|
||||||
|
summary: All the jargon you need to be a bonafide testing guru
|
||||||
|
---
|
||||||
<dl>
|
<dl>
|
||||||
<dt>Assertion<dd>A predicate statement that must be true when a test runs.
|
<dt>Assertion<dd>A predicate statement that must be true when a test runs.
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
---
|
||||||
title: How to write a SapphireTest
|
title: How to write a SapphireTest
|
||||||
|
summary: Learn the basics of unit testing in Silverstripe
|
||||||
|
---
|
||||||
# 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
|
Here is an example of a test which extends [api:SapphireTest] to test the URL generation of the page. It also showcases
|
||||||
@ -7,7 +9,7 @@ how you can load default records into the test database.
|
|||||||
|
|
||||||
**mysite/tests/PageTest.php**
|
**mysite/tests/PageTest.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class PageTest extends SapphireTest {
|
class PageTest extends SapphireTest {
|
||||||
@ -42,21 +44,21 @@ how you can load default records into the test database.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
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.
|
database and discarded at the end of the test.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
The `fixture_file` property can be path to a file, or an array of strings pointing to many files. The path must be
|
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.
|
absolute from your website's root folder.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
The second part of our class is the `testURLGeneration` method. This method is our test. When the test is executed,
|
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.
|
methods prefixed with the word `test` will be run.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
The test database is rebuilt every time one of these methods is run.
|
The test database is rebuilt every time one of these methods is run.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
Inside our test method is the `objFromFixture` method that will generate an object for us based on data from our fixture
|
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
|
file. To identify to the object, we provide a class name and an identifier. The identifier is specified in the YAML file
|
||||||
@ -67,9 +69,9 @@ The final part of our test is an assertion command, `assertEquals`. An assertion
|
|||||||
in our test methods (in this case we are testing if two values are equal). A test method can have more than one
|
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.
|
assertion command, and if any one of these assertions fail, so will the test method.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
For more information on PHPUnit's assertions see the [PHPUnit manual](http://www.phpunit.de/manual/current/en/api.html#api.assert).
|
For more information on PHPUnit's assertions see the [PHPUnit manual](http://www.phpunit.de/manual/current/en/api.html#api.assert).
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
---
|
||||||
title: How to write a FunctionalTest
|
title: How to write a FunctionalTest
|
||||||
|
summary: Expand your testing capabilities with integrations tests
|
||||||
|
---
|
||||||
# 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
|
[api:FunctionalTest] test your applications `Controller` instances and anything else which requires a web request. The
|
||||||
@ -9,7 +11,7 @@ response and modify the session within a test.
|
|||||||
|
|
||||||
**mysite/tests/HomePageTest.php**
|
**mysite/tests/HomePageTest.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class HomePageTest extends FunctionalTest {
|
class HomePageTest extends FunctionalTest {
|
||||||
@ -46,7 +48,7 @@ response and modify the session within a test.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## Related Documentation
|
```
|
||||||
|
|
||||||
* [Functional Testing](../functional_testing)
|
* [Functional Testing](../functional_testing)
|
||||||
* [Unit Testing](../unit_testing)
|
* [Unit Testing](../unit_testing)
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
title: How to use a FixtureFactory
|
title: How to use a FixtureFactory
|
||||||
|
summary: Provide context to your tests with database fixtures
|
||||||
|
icon: industry
|
||||||
|
---
|
||||||
# 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
|
The [api:FixtureFactory] is used to manually create data structures for use with tests. For more information on fixtures
|
||||||
@ -8,7 +11,7 @@ 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
|
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.
|
with information that we need.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyObjectTest extends SapphireTest {
|
class MyObjectTest extends SapphireTest {
|
||||||
|
|
||||||
protected $factory;
|
protected $factory;
|
||||||
@ -40,7 +43,7 @@ with information that we need.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## Related Documentation
|
```
|
||||||
|
|
||||||
* [Fixtures](../fixtures)
|
* [Fixtures](../fixtures)
|
||||||
|
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
|
---
|
||||||
title: How to test emails within unit tests
|
title: How to test emails within unit tests
|
||||||
|
summary: Test email functionality without ever hitting an inbox
|
||||||
|
icon: envelope
|
||||||
|
---
|
||||||
# Testing Email 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
|
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
|
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.
|
email was sent using this method.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function MyMethod() {
|
public function MyMethod() {
|
||||||
$e = new Email();
|
$e = new Email();
|
||||||
$e->To = "someone@example.com";
|
$e->To = "someone@example.com";
|
||||||
@ -15,15 +18,15 @@ email was sent using this method.
|
|||||||
$e->send();
|
$e->send();
|
||||||
}
|
}
|
||||||
|
|
||||||
To test that `MyMethod` sends the correct email, use the [api:SapphireTest::assertEmailSent()] method.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$this->assertEmailSent($to, $from, $subject, $body);
|
$this->assertEmailSent($to, $from, $subject, $body);
|
||||||
|
|
||||||
// to assert that the email is sent to the correct person
|
// to assert that the email is sent to the correct person
|
||||||
$this->assertEmailSent("someone@example.com", null, "/th.*e$/");
|
$this->assertEmailSent("someone@example.com", null, "/th.*e$/");
|
||||||
|
|
||||||
|
```
|
||||||
Each of the arguments (`$to`, `$from`, `$subject` and `$body`) can be either one of the following.
|
Each of the arguments (`$to`, `$from`, `$subject` and `$body`) can be either one of the following.
|
||||||
|
|
||||||
* A string: match exactly that string
|
* A string: match exactly that string
|
||||||
|
6
docs/en/02_Developer_Guides/06_Testing/How_Tos/index.md
Normal file
6
docs/en/02_Developer_Guides/06_Testing/How_Tos/index.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: How To's
|
||||||
|
---
|
||||||
|
# How To's: Testing
|
||||||
|
|
||||||
|
[CHILDREN]
|
@ -1,5 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: Testing
|
||||||
summary: Deploy robust applications by bundling Unit and Behavior tests with your application code and modules.
|
summary: Deploy robust applications by bundling Unit and Behavior tests with your application code and modules.
|
||||||
|
---
|
||||||
# Unit and Integration Testing
|
# Unit and Integration Testing
|
||||||
|
|
||||||
For behaviour testing in SilverStripe, check out [SilverStripe Behat Documentation](https://github.com/silverstripe-labs/silverstripe-behat-extension/).
|
For behaviour testing in SilverStripe, check out [SilverStripe Behat Documentation](https://github.com/silverstripe-labs/silverstripe-behat-extension/).
|
||||||
@ -79,6 +81,7 @@ Tutorials and recipes for creating tests using the SilverStripe framework:
|
|||||||
|
|
||||||
The `phpunit` binary should be used from the root directory of your website.
|
The `phpunit` binary should be used from the root directory of your website.
|
||||||
|
|
||||||
|
```
|
||||||
# Runs all tests defined in phpunit.xml
|
# Runs all tests defined in phpunit.xml
|
||||||
phpunit
|
phpunit
|
||||||
|
|
||||||
@ -94,7 +97,7 @@ The `phpunit` binary should be used from the root directory of your website.
|
|||||||
# Run tests with optional `$_GET` parameters (you need an empty second argument)
|
# Run tests with optional `$_GET` parameters (you need an empty second argument)
|
||||||
phpunit framework/tests '' flush=all
|
phpunit framework/tests '' flush=all
|
||||||
|
|
||||||
All command-line arguments are documented on
|
```
|
||||||
[phpunit.de](http://www.phpunit.de/manual/current/en/textui.html).
|
[phpunit.de](http://www.phpunit.de/manual/current/en/textui.html).
|
||||||
|
|
||||||
### Via the "sake" Wrapper on Command Line
|
### Via the "sake" Wrapper on Command Line
|
||||||
@ -104,6 +107,7 @@ The [sake](/developer_guides/cli/) executable that comes with SilverStripe can t
|
|||||||
While the custom test runner a handy tool, its also more limited than using `phpunit` directly,
|
While the custom test runner a handy tool, its also more limited than using `phpunit` directly,
|
||||||
particularly around formatting test output.
|
particularly around formatting test output.
|
||||||
|
|
||||||
|
```
|
||||||
# Run all tests
|
# Run all tests
|
||||||
sake dev/tests/all
|
sake dev/tests/all
|
||||||
|
|
||||||
@ -119,9 +123,7 @@ particularly around formatting test output.
|
|||||||
# Skip some tests
|
# Skip some tests
|
||||||
sake dev/tests/all SkipTests=MySkippedTest
|
sake dev/tests/all SkipTests=MySkippedTest
|
||||||
|
|
||||||
### Via Web Browser
|
```
|
||||||
|
|
||||||
Executing tests from the command line is recommended, since it most closely reflects
|
Executing tests from the command line is recommended, since it most closely reflects
|
||||||
test runs in any automated testing environments. However, you can also run tests through the browser:
|
test runs in any automated testing environments. However, you can also run tests through the browser:
|
||||||
|
|
||||||
http://localhost/dev/tests
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Environment Types
|
title: Environment Types
|
||||||
summary: Configure your SilverStripe environment to define how your web application behaves.
|
summary: Configure your SilverStripe environment to define how your web application behaves.
|
||||||
|
icon: exclamation-circle
|
||||||
|
---
|
||||||
# Environment Types
|
# Environment Types
|
||||||
|
|
||||||
SilverStripe knows three different environment types (or "modes"). Each of the modes gives you different tools
|
SilverStripe knows three different environment types (or "modes"). Each of the modes gives you different tools
|
||||||
@ -9,16 +11,16 @@ and behaviors. The environment is managed either through a [YML configuration fi
|
|||||||
|
|
||||||
The definition of setting an environment type in a `mysite/_config/app.yml` looks like
|
The definition of setting an environment type in a `mysite/_config/app.yml` looks like
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Director:
|
Director:
|
||||||
environment_type: 'dev'
|
environment_type: 'dev'
|
||||||
|
|
||||||
The definition of setting an environment type in a `_ss_environment.php` file looks like
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
define('SS_ENVIRONMENT_TYPE', 'dev');
|
define('SS_ENVIRONMENT_TYPE', 'dev');
|
||||||
|
|
||||||
The three environment types you can set are `dev`, `test` and `live`.
|
```
|
||||||
|
|
||||||
### Dev
|
### Dev
|
||||||
|
|
||||||
@ -26,11 +28,11 @@ When developing your websites, adding page types or installing modules you shoul
|
|||||||
you will see full error back traces and view the development tools without having to be logged in as an administrator
|
you will see full error back traces and view the development tools without having to be logged in as an administrator
|
||||||
user.
|
user.
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
**dev mode should not be enabled long term on live sites for security reasons**. In dev mode by outputting back traces
|
**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
|
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.
|
on a public server very carefully.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
### Test Mode
|
### Test Mode
|
||||||
|
|
||||||
@ -39,21 +41,23 @@ Test mode is designed for staging environments or other private collaboration si
|
|||||||
In this mode error messages are hidden from the user and SilverStripe includes [api:BasicAuth] integration if you
|
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:
|
want to password protect the site. You can enable that but adding this to your `mysite/_config/app.yml` file:
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
---
|
```
|
||||||
|
```
|
||||||
Only:
|
Only:
|
||||||
environment: 'test'
|
environment: 'test'
|
||||||
---
|
```
|
||||||
|
```
|
||||||
BasicAuth:
|
BasicAuth:
|
||||||
entire_site_protected: true
|
entire_site_protected: true
|
||||||
|
|
||||||
### Live Mode
|
```
|
||||||
|
|
||||||
All error messages are suppressed from the user and the application is in it's most *secure* state.
|
All error messages are suppressed from the user and the application is in it's most *secure* state.
|
||||||
|
|
||||||
<div class="alert">
|
[alert]
|
||||||
Live sites should always run in live mode. You should not run production websites in dev mode.
|
Live sites should always run in live mode. You should not run production websites in dev mode.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
|
|
||||||
## Checking Environment Type
|
## Checking Environment Type
|
||||||
@ -62,22 +66,26 @@ You can check for the current environment type in [config files](../configuratio
|
|||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
---
|
---
|
||||||
|
```
|
||||||
Only:
|
Only:
|
||||||
environment: 'live'
|
environment: 'live'
|
||||||
---
|
```
|
||||||
|
```
|
||||||
MyClass:
|
MyClass:
|
||||||
myvar: live_value
|
myvar: live_value
|
||||||
---
|
```
|
||||||
|
```
|
||||||
Only:
|
Only:
|
||||||
environment: 'test'
|
environment: 'test'
|
||||||
---
|
```
|
||||||
|
```
|
||||||
MyClass:
|
MyClass:
|
||||||
myvar: test_value
|
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.
|
certain functionality depending on the environment type.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
if(Director::isLive()) {
|
if(Director::isLive()) {
|
||||||
// is in live
|
// is in live
|
||||||
} else if(Director::isTest()) {
|
} else if(Director::isTest()) {
|
||||||
@ -86,3 +94,4 @@ certain functionality depending on the environment type.
|
|||||||
// is in dev mode
|
// is in dev mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
@ -1,6 +1,8 @@
|
|||||||
title: Error Handling
|
---
|
||||||
summary: Trap, fire and report user exceptions, warnings and errors.
|
title: Logging and Error Handling
|
||||||
|
summary: Trap, fire and report diagnostic logs, user exceptions, warnings and errors.
|
||||||
|
icon: exclamation-circle
|
||||||
|
---
|
||||||
# Error Handling
|
# Error Handling
|
||||||
|
|
||||||
SilverStripe has its own error trapping and handling support. On development sites, SilverStripe will deal harshly with
|
SilverStripe has its own error trapping and handling support. On development sites, SilverStripe will deal harshly with
|
||||||
@ -11,7 +13,7 @@ potential issue to handle.
|
|||||||
|
|
||||||
You should use [user_error](http://www.php.net/user_error) to throw errors where appropriate.
|
You should use [user_error](http://www.php.net/user_error) to throw errors where appropriate.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
if(true == false) {
|
if(true == false) {
|
||||||
user_error("I have an error problem", E_USER_ERROR);
|
user_error("I have an error problem", E_USER_ERROR);
|
||||||
}
|
}
|
||||||
@ -20,7 +22,7 @@ You should use [user_error](http://www.php.net/user_error) to throw errors where
|
|||||||
user_error("This time I am warning you", E_USER_WARNING);
|
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
|
* **E_USER_WARNING:** Err on the side of over-reporting warnings. Throwing warnings provides a means of ensuring that
|
||||||
developers know:
|
developers know:
|
||||||
@ -38,7 +40,7 @@ You can indicate a log file relative to the site root.
|
|||||||
|
|
||||||
**mysite/_config.php**
|
**mysite/_config.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
if(!Director::isDev()) {
|
if(!Director::isDev()) {
|
||||||
// log errors and warnings
|
// log errors and warnings
|
||||||
SS_Log::add_writer(new SS_LogFileWriter('../silverstripe-errors-warnings.log'), SS_Log::WARN, '<=');
|
SS_Log::add_writer(new SS_LogFileWriter('../silverstripe-errors-warnings.log'), SS_Log::WARN, '<=');
|
||||||
@ -50,11 +52,11 @@ You can indicate a log file relative to the site root.
|
|||||||
SS_Log::add_writer(new SS_LogFileWriter('../silverstripe-errors-notices.log'), SS_Log::NOTICE);
|
SS_Log::add_writer(new SS_LogFileWriter('../silverstripe-errors-notices.log'), SS_Log::NOTICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
```
|
||||||
In addition to SilverStripe-integrated logging, it is advisable to fall back to PHPs native logging functionality. A
|
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
|
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.
|
sure `log_errors` and `error_log` in your PHP ini file are configured.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
## Email Logs
|
## Email Logs
|
||||||
|
|
||||||
@ -62,7 +64,7 @@ You can send both fatal errors and warnings in your code to a specified email-ad
|
|||||||
|
|
||||||
**mysite/_config.php**
|
**mysite/_config.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
if(!Director::isDev()) {
|
if(!Director::isDev()) {
|
||||||
// log errors and warnings
|
// log errors and warnings
|
||||||
SS_Log::add_writer(new SS_LogFileWriter('../silverstripe-errors-warnings.log'), SS_Log::WARN, '<=');
|
SS_Log::add_writer(new SS_LogFileWriter('../silverstripe-errors-warnings.log'), SS_Log::WARN, '<=');
|
||||||
@ -71,6 +73,6 @@ You can send both fatal errors and warnings in your code to a specified email-ad
|
|||||||
SS_Log::add_writer(new SS_LogEmailWriter('admin@domain.com'), SS_Log::ERR);
|
SS_Log::add_writer(new SS_LogEmailWriter('admin@domain.com'), SS_Log::ERR);
|
||||||
}
|
}
|
||||||
|
|
||||||
## API Documentation
|
```
|
||||||
|
|
||||||
* [api:SS_Log]
|
* [api:SS_Log]
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: URL Variable tools
|
||||||
|
summary: Useful debugging tools you can use right in the browser
|
||||||
|
---
|
||||||
|
|
||||||
# URL Variable Tools
|
# URL Variable Tools
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
@ -10,10 +15,11 @@ core.
|
|||||||
|
|
||||||
Append the option and corresponding value to your URL in your browser's address bar. You may find the [Firefox UrlParams extension](https://addons.mozilla.org/en-US/firefox/addon/1290) useful in order to debug a POST requests (Like Forms).
|
Append the option and corresponding value to your URL in your browser's address bar. You may find the [Firefox UrlParams extension](https://addons.mozilla.org/en-US/firefox/addon/1290) useful in order to debug a POST requests (Like Forms).
|
||||||
|
|
||||||
|
```
|
||||||
http://yoursite.com/page?option_name=value
|
http://yoursite.com/page?option_name=value
|
||||||
http://yoursite.com/page?option_1=value&option_2=value
|
http://yoursite.com/page?option_1=value&option_2=value
|
||||||
|
|
||||||
## Templates
|
```
|
||||||
|
|
||||||
| URL Variable | | Values | | Description |
|
| URL Variable | | Values | | Description |
|
||||||
| ------------ | | ------ | | ----------- |
|
| ------------ | | ------ | | ----------- |
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Template debugging
|
title: Template debugging
|
||||||
summary: Track down which template rendered a piece of html
|
summary: Track down which template rendered a piece of html
|
||||||
|
icon: bug
|
||||||
|
---
|
||||||
# Debugging templates
|
# Debugging templates
|
||||||
|
|
||||||
## Source code comments
|
## Source code comments
|
||||||
@ -10,11 +12,4 @@ to track down a template or two. The template engine can help you along by displ
|
|||||||
source code comments indicating which template is responsible for rendering each
|
source code comments indicating which template is responsible for rendering each
|
||||||
block of html on your page.
|
block of html on your page.
|
||||||
|
|
||||||
```yaml
|
|
||||||
---
|
|
||||||
Only:
|
|
||||||
environment: 'dev'
|
|
||||||
---
|
|
||||||
SSViewer:
|
|
||||||
source_file_comments: true
|
|
||||||
```
|
```
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Debugging
|
||||||
summary: Learn how to identify errors in your application and best practice for logging application errors.
|
summary: Learn how to identify errors in your application and best practice for logging application errors.
|
||||||
|
icon: bug
|
||||||
|
---
|
||||||
# Debugging
|
# Debugging
|
||||||
|
|
||||||
SilverStripe can be a large and complex framework to debug, but there are ways to make debugging less painful. In this
|
SilverStripe can be a large and complex framework to debug, but there are ways to make debugging less painful. In this
|
||||||
@ -17,7 +20,7 @@ bottle-necks and identify slow moving parts of your application chain.
|
|||||||
|
|
||||||
The [api:Debug] class contains a number of static utility methods for more advanced debugging.
|
The [api:Debug] class contains a number of static utility methods for more advanced debugging.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Debug::show($myVariable);
|
Debug::show($myVariable);
|
||||||
// similar to print_r($myVariable) but shows it in a more useful format.
|
// similar to print_r($myVariable) but shows it in a more useful format.
|
||||||
|
|
||||||
@ -27,7 +30,7 @@ The [api:Debug] class contains a number of static utility methods for more advan
|
|||||||
SS_Backtrace::backtrace();
|
SS_Backtrace::backtrace();
|
||||||
// prints a calls-stack
|
// prints a calls-stack
|
||||||
|
|
||||||
## API Documentation
|
```
|
||||||
|
|
||||||
* [api:SS_Log]
|
* [api:SS_Log]
|
||||||
* [api:SS_Backtrace]
|
* [api:SS_Backtrace]
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
|
---
|
||||||
title: Partial Caching
|
title: Partial Caching
|
||||||
summary: Cache SilverStripe templates to reduce database queries.
|
summary: Cache SilverStripe templates to reduce database queries.
|
||||||
|
icon: tachometer-alt
|
||||||
|
---
|
||||||
# Partial Caching
|
# Partial Caching
|
||||||
|
|
||||||
Partial caching is a feature that allows the caching of just a portion of a page.
|
Partial caching is a feature that allows the caching of just a portion of a page.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached 'CacheKey' %>
|
<% cached 'CacheKey' %>
|
||||||
$DataTable
|
$DataTable
|
||||||
...
|
...
|
||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
|
|
||||||
|
```
|
||||||
Each cache block has a cache key. A cache key is an unlimited number of comma separated variables and quoted strings.
|
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
|
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.
|
the same as a previous render, the cached value stored last time is used.
|
||||||
@ -21,7 +23,7 @@ will invalidate the cache after a given amount of time has expired (default 10 m
|
|||||||
|
|
||||||
Here are some more complex examples:
|
Here are some more complex examples:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached 'database', $LastEdited %>
|
<% cached 'database', $LastEdited %>
|
||||||
<!-- that updates every time the record changes. -->
|
<!-- that updates every time the record changes. -->
|
||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
@ -34,7 +36,7 @@ Here are some more complex examples:
|
|||||||
<!-- recached when block object changes, and if the user is admin -->
|
<!-- recached when block object changes, and if the user is admin -->
|
||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
|
|
||||||
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.
|
`$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
|
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.
|
template caching when modifying this config, as this key is cached within the template itself.
|
||||||
@ -44,11 +46,11 @@ user does not influence your template content, you can update this key as below;
|
|||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yaml
|
```yaml
|
||||||
SSViewer:
|
SSViewer:
|
||||||
global_key: '$CurrentReadingMode, $Locale'
|
global_key: '$CurrentReadingMode, $Locale'
|
||||||
|
|
||||||
|
```
|
||||||
## Aggregates
|
## Aggregates
|
||||||
|
|
||||||
Often you want to invalidate a cache when any object in a set of objects change, or when the objects in a relationship
|
Often you want to invalidate a cache when any object in a set of objects change, or when the objects in a relationship
|
||||||
@ -58,21 +60,21 @@ 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
|
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 do that like this:
|
otherwise. By using aggregates, we do that like this:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached 'navigation', $List('SiteTree').max('LastEdited'), $List('SiteTree').count() %>
|
<% cached 'navigation', $List('SiteTree').max('LastEdited'), $List('SiteTree').count() %>
|
||||||
|
|
||||||
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
|
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
|
or edited
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached 'categorylist', $List('Category').max('LastEdited'), $List('Category').count() %>
|
<% cached 'categorylist', $List('Category').max('LastEdited'), $List('Category').count() %>
|
||||||
|
|
||||||
<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
|
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.
|
edited since the cache was last built, and also when an object has been deleted since the cache was last built.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
We can also calculate aggregates on relationships. The logic for that can get a bit complex, so we can extract that on
|
We can also calculate aggregates on relationships. The logic for that can get a bit complex, so we can extract that on
|
||||||
to the controller so it's not cluttering up our template.
|
to the controller so it's not cluttering up our template.
|
||||||
@ -85,7 +87,7 @@ fragment.
|
|||||||
For example, a block that shows a collection of rotating slides needs to update whenever the relationship
|
For example, a block that shows a collection of rotating slides needs to update whenever the relationship
|
||||||
`Page::$many_many = array('Slides' => 'Slide')` changes. In Page_Controller:
|
`Page::$many_many = array('Slides' => 'Slide')` changes. In Page_Controller:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
|
|
||||||
public function SliderCacheKey() {
|
public function SliderCacheKey() {
|
||||||
$fragments = array(
|
$fragments = array(
|
||||||
@ -98,12 +100,12 @@ For example, a block that shows a collection of rotating slides needs to update
|
|||||||
return implode('-_-', $fragments);
|
return implode('-_-', $fragments);
|
||||||
}
|
}
|
||||||
|
|
||||||
Then reference that function in the cache key:
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached $SliderCacheKey %>
|
<% cached $SliderCacheKey %>
|
||||||
|
|
||||||
The example above would work for both a has_many and many_many relationship.
|
```
|
||||||
|
|
||||||
## Cache blocks and template changes
|
## Cache blocks and template changes
|
||||||
|
|
||||||
@ -119,25 +121,25 @@ data updates.
|
|||||||
|
|
||||||
For instance, if we show some blog statistics, but are happy having them be slightly stale, we could do
|
For instance, if we show some blog statistics, but are happy having them be slightly stale, we could do
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached 'blogstatistics', $Blog.ID %>
|
<% cached 'blogstatistics', $Blog.ID %>
|
||||||
|
|
||||||
|
```
|
||||||
which will invalidate after the cache lifetime expires. If you need more control than that (cache lifetime is
|
which will invalidate after the cache lifetime expires. If you need more control than that (cache lifetime is
|
||||||
configurable only on a site-wide basis), you could add a special function to your controller:
|
configurable only on a site-wide basis), you could add a special function to your controller:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function BlogStatisticsCounter() {
|
public function BlogStatisticsCounter() {
|
||||||
return (int)(time() / 60 / 5); // Returns a new number every five minutes
|
return (int)(time() / 60 / 5); // Returns a new number every five minutes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
and then use it in the cache key
|
and then use it in the cache key
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached 'blogstatistics', $Blog.ID, $BlogStatisticsCounter %>
|
<% cached 'blogstatistics', $Blog.ID, $BlogStatisticsCounter %>
|
||||||
|
|
||||||
|
```
|
||||||
## Cache block conditionals
|
## Cache block conditionals
|
||||||
|
|
||||||
You may wish to conditionally enable or disable caching. To support this, in cached tags you may (after any key
|
You may wish to conditionally enable or disable caching. To support this, in cached tags you may (after any key
|
||||||
@ -147,28 +149,28 @@ value must be true for that block to be cached. Conversely if 'unless' is used,
|
|||||||
Following on from the previous example, you might wish to only cache slightly-stale data if the server is experiencing
|
Following on from the previous example, you might wish to only cache slightly-stale data if the server is experiencing
|
||||||
heavy load:
|
heavy load:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached 'blogstatistics', $Blog.ID if $HighLoad %>
|
<% 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,
|
To cache the contents of a page for all anonymous users, but dynamically calculate the contents for logged in members,
|
||||||
use something like:
|
use something like:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached unless $CurrentUser %>
|
<% cached unless $CurrentUser %>
|
||||||
|
|
||||||
## Uncached
|
```
|
||||||
|
|
||||||
The template tag 'uncached' can be used - it is the exact equivalent of a cached block with an if condition that always
|
The 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
|
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.
|
particular cache block by changing just the tag, leaving the key and conditional intact.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% uncached %>
|
<% uncached %>
|
||||||
|
|
||||||
|
```
|
||||||
## Nested cache blocks
|
## Nested cache blocks
|
||||||
|
|
||||||
You can also nest independent cache blocks Any nested cache blocks are calculated independently from their containing
|
You can also nest independent cache blocks Any nested cache blocks are calculated independently from their containing
|
||||||
@ -179,7 +181,7 @@ portion dynamic, without having to include any member info in the page's cache k
|
|||||||
|
|
||||||
An example:
|
An example:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached $LastEdited %>
|
<% cached $LastEdited %>
|
||||||
Our wonderful site
|
Our wonderful site
|
||||||
|
|
||||||
@ -190,14 +192,14 @@ An example:
|
|||||||
$ASlowCalculation
|
$ASlowCalculation
|
||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
|
|
||||||
|
```
|
||||||
This will cache the entire outer section until the next time the page is edited, but will display a different welcome
|
This will cache the entire outer section until the next time the page is edited, but will display a different welcome
|
||||||
message depending on the logged in member.
|
message depending on the logged in member.
|
||||||
|
|
||||||
Cache conditionals and the uncached tag also work in the same nested manner. Since Member.Name is fast to calculate, you
|
Cache conditionals and the uncached tag also work in the same nested manner. Since Member.Name is fast to calculate, you
|
||||||
could also write the last example as:
|
could also write the last example as:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached $LastEdited %>
|
<% cached $LastEdited %>
|
||||||
Our wonderful site
|
Our wonderful site
|
||||||
|
|
||||||
@ -208,14 +210,14 @@ could also write the last example as:
|
|||||||
$ASlowCalculation
|
$ASlowCalculation
|
||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
|
|
||||||
<div class="warning" markdown="1">
|
```
|
||||||
Currently a nested cache block can not be contained within an if or loop block. The template engine will throw an error
|
Currently a nested cache block can not be contained within an if or loop block. The template engine will throw an error
|
||||||
letting you know if you've done this. You can often get around this using aggregates or by un-nesting the block.
|
letting you know if you've done this. You can often get around this using aggregates or by un-nesting the block.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
Failing example:
|
Failing example:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached $LastEdited %>
|
<% cached $LastEdited %>
|
||||||
|
|
||||||
<% loop $Children %>
|
<% loop $Children %>
|
||||||
@ -226,9 +228,9 @@ Failing example:
|
|||||||
|
|
||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
|
|
||||||
Can be re-written as:
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached $LastEdited %>
|
<% cached $LastEdited %>
|
||||||
|
|
||||||
<% cached $AllChildren.max('LastEdited') %>
|
<% cached $AllChildren.max('LastEdited') %>
|
||||||
@ -239,9 +241,9 @@ Can be re-written as:
|
|||||||
|
|
||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
|
|
||||||
Or:
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached $LastEdited %>
|
<% cached $LastEdited %>
|
||||||
(other code)
|
(other code)
|
||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
@ -252,7 +254,7 @@ Or:
|
|||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
|
|
||||||
## Cache expiry
|
```
|
||||||
|
|
||||||
The default expiry for partial caches is 10 minutes. The advantage of a short cache expiry is that if you have a problem
|
The default expiry for partial caches is 10 minutes. The advantage of a short cache expiry is that if you have a problem
|
||||||
with your caching logic, the window in which stale content may be shown is short. The disadvantage, particularly for
|
with your caching logic, the window in which stale content may be shown is short. The disadvantage, particularly for
|
||||||
@ -260,7 +262,3 @@ low-traffic sites, is that cache blocks may expire before they can be utilised.
|
|||||||
logic is sound, you could increase the expiry dramatically.
|
logic is sound, you could increase the expiry dramatically.
|
||||||
|
|
||||||
**mysite/_config.php**
|
**mysite/_config.php**
|
||||||
|
|
||||||
:::php
|
|
||||||
// Set partial cache expiry to 7 days
|
|
||||||
SS_Cache::set_cache_lifetime('cacheblock', 60 * 60 * 24 * 7);
|
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Caching
|
||||||
|
summary: Optimise performance by caching expensive processes
|
||||||
|
icon: tachometer-alt
|
||||||
|
---
|
||||||
|
|
||||||
# Caching
|
# Caching
|
||||||
|
|
||||||
## Built-In Caches
|
## Built-In Caches
|
||||||
@ -45,7 +51,7 @@ backend for each named cache. There is a default File cache set up as the
|
|||||||
Caches can be created and retrieved through the `SS_Cache::factory()` method.
|
Caches can be created and retrieved through the `SS_Cache::factory()` method.
|
||||||
The returned object is of type `Zend_Cache`.
|
The returned object is of type `Zend_Cache`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// foo is any name (try to be specific), and is used to get configuration
|
// foo is any name (try to be specific), and is used to get configuration
|
||||||
// & storage info
|
// & storage info
|
||||||
$cache = SS_Cache::factory('foo');
|
$cache = SS_Cache::factory('foo');
|
||||||
@ -55,42 +61,42 @@ The returned object is of type `Zend_Cache`.
|
|||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
|
|
||||||
Normally there's no need to remove things from the cache - the cache
|
```
|
||||||
backends clear out entries based on age and maximum allocated storage. If you
|
backends clear out entries based on age and maximum allocated storage. If you
|
||||||
include the version of the object in the cache key, even object changes
|
include the version of the object in the cache key, even object changes
|
||||||
don't need any invalidation. You can force disable the cache though,
|
don't need any invalidation. You can force disable the cache though,
|
||||||
e.g. in development mode.
|
e.g. in development mode.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// Disables all caches
|
// Disables all caches
|
||||||
SS_Cache::set_cache_lifetime('any', -1, 100);
|
SS_Cache::set_cache_lifetime('any', -1, 100);
|
||||||
|
|
||||||
You can also specifically clean a cache.
|
```
|
||||||
Keep in mind that `Zend_Cache::CLEANING_MODE_ALL` deletes all cache
|
Keep in mind that `Zend_Cache::CLEANING_MODE_ALL` deletes all cache
|
||||||
entries across all caches, not just for the 'foo' cache in the example below.
|
entries across all caches, not just for the 'foo' cache in the example below.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$cache = SS_Cache::factory('foo');
|
$cache = SS_Cache::factory('foo');
|
||||||
$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
|
$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
|
||||||
|
|
||||||
A single element can be invalidated through its cache key.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$cache = SS_Cache::factory('foo');
|
$cache = SS_Cache::factory('foo');
|
||||||
$cache->remove($cachekey);
|
$cache->remove($cachekey);
|
||||||
|
|
||||||
In order to increase the chance of your cache actually being hit,
|
```
|
||||||
it often pays to increase the lifetime of caches ("TTL").
|
it often pays to increase the lifetime of caches ("TTL").
|
||||||
It defaults to 10 minutes (600s) in SilverStripe, which can be
|
It defaults to 10 minutes (600s) in SilverStripe, which can be
|
||||||
quite short depending on how often your data changes.
|
quite short depending on how often your data changes.
|
||||||
Keep in mind that data expiry should primarily be handled by your cache key,
|
Keep in mind that data expiry should primarily be handled by your cache key,
|
||||||
e.g. by including the `LastEdited` value when caching `DataObject` results.
|
e.g. by including the `LastEdited` value when caching `DataObject` results.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// set all caches to 3 hours
|
// set all caches to 3 hours
|
||||||
SS_Cache::set_cache_lifetime('any', 60*60*3);
|
SS_Cache::set_cache_lifetime('any', 60*60*3);
|
||||||
|
|
||||||
### Versioned cache segmentation
|
```
|
||||||
|
|
||||||
`SS_Cache` segments caches based on the versioned reading mode. This prevents developers
|
`SS_Cache` segments caches based on the versioned reading mode. This prevents developers
|
||||||
from caching draft data and then accidentally exposing it on the live stage without potentially
|
from caching draft data and then accidentally exposing it on the live stage without potentially
|
||||||
@ -100,8 +106,6 @@ required authorisation checks. This segmentation is automatic for all caches gen
|
|||||||
Data that is not content sensitive can be cached across stages by simply opting out of the
|
Data that is not content sensitive can be cached across stages by simply opting out of the
|
||||||
segmented cache with the `disable-segmentation` argument.
|
segmented cache with the `disable-segmentation` argument.
|
||||||
|
|
||||||
```php
|
|
||||||
$cache = SS_Cache::factory('myapp', 'Output', array('disable-segmentation' => true));
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Alternative Cache Backends
|
## Alternative Cache Backends
|
||||||
@ -126,6 +130,7 @@ server. memcached is a high-performance, distributed memory object caching syste
|
|||||||
To use this backend, you need a memcached daemon and the memcache PECL extension.
|
To use this backend, you need a memcached daemon and the memcache PECL extension.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
|
```
|
||||||
// _config.php
|
// _config.php
|
||||||
SS_Cache::add_backend(
|
SS_Cache::add_backend(
|
||||||
'primary_memcached',
|
'primary_memcached',
|
||||||
@ -145,9 +150,11 @@ To use this backend, you need a memcached daemon and the memcache PECL extension
|
|||||||
);
|
);
|
||||||
SS_Cache::pick_backend('primary_memcached', 'any', 10);
|
SS_Cache::pick_backend('primary_memcached', 'any', 10);
|
||||||
|
|
||||||
|
```
|
||||||
If your Memcached instance is using a local Unix socket instead of a network port:
|
If your Memcached instance is using a local Unix socket instead of a network port:
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
|
```
|
||||||
// _config.php
|
// _config.php
|
||||||
SS_Cache::add_backend(
|
SS_Cache::add_backend(
|
||||||
'primary_memcached',
|
'primary_memcached',
|
||||||
@ -167,26 +174,16 @@ If your Memcached instance is using a local Unix socket instead of a network por
|
|||||||
);
|
);
|
||||||
SS_Cache::pick_backend('primary_memcached', 'any', 10);
|
SS_Cache::pick_backend('primary_memcached', 'any', 10);
|
||||||
|
|
||||||
### APC
|
```
|
||||||
|
|
||||||
This backends stores cache records in shared memory through the [APC](http://pecl.php.net/package/APC)
|
This backends stores cache records in shared memory through the [APC](http://pecl.php.net/package/APC)
|
||||||
(Alternative PHP Cache) extension (which is of course need for using this backend).
|
(Alternative PHP Cache) extension (which is of course need for using this backend).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
SS_Cache::add_backend('primary_apc', 'APC');
|
SS_Cache::add_backend('primary_apc', 'APC');
|
||||||
SS_Cache::pick_backend('primary_apc', 'any', 10);
|
SS_Cache::pick_backend('primary_apc', 'any', 10);
|
||||||
|
|
||||||
### Two-Levels
|
```
|
||||||
|
|
||||||
This backend is an hybrid one. It stores cache records in two other backends:
|
This backend is an hybrid one. It stores cache records in two other backends:
|
||||||
a fast one (but limited) like Apc, Memcache... and a "slow" one like File or Sqlite.
|
a fast one (but limited) like Apc, Memcache... and a "slow" one like File or Sqlite.
|
||||||
|
|
||||||
:::php
|
|
||||||
SS_Cache::add_backend('two_level', 'Two-Levels', array(
|
|
||||||
'slow_backend' => 'File',
|
|
||||||
'fast_backend' => 'APC',
|
|
||||||
'slow_backend_options' => array(
|
|
||||||
'cache_dir' => TEMP_FOLDER . DIRECTORY_SEPARATOR . 'cache'
|
|
||||||
)
|
|
||||||
));
|
|
||||||
SS_Cache::pick_backend('two_level', 'any', 10);
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: HTTP Cache Headers
|
title: HTTP Cache Headers
|
||||||
summary: Set the correct HTTP cache headers for your responses.
|
summary: Set the correct HTTP cache headers for your responses.
|
||||||
|
icon: tachometer-alt
|
||||||
|
---
|
||||||
# HTTP Cache Headers
|
# HTTP Cache Headers
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
@ -98,19 +100,6 @@ The priority order is as followed, sorted in descending order
|
|||||||
|
|
||||||
Enable caching for all page content (through `Page_Controller`).
|
Enable caching for all page content (through `Page_Controller`).
|
||||||
|
|
||||||
```php
|
|
||||||
class Page_Controller extends ContentController
|
|
||||||
{
|
|
||||||
public function init()
|
|
||||||
{
|
|
||||||
HTTPCacheControl::singleton()
|
|
||||||
->enableCache()
|
|
||||||
->setMaxAge(60); // 1 minute
|
|
||||||
|
|
||||||
|
|
||||||
parent::init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: SilverStripe will still override this preference when a session is active,
|
Note: SilverStripe will still override this preference when a session is active,
|
||||||
@ -124,18 +113,6 @@ permission checks or other triggers for conditional output,
|
|||||||
you can disable caching either on a controller level
|
you can disable caching either on a controller level
|
||||||
(through `init()`) or for a particular action.
|
(through `init()`) or for a particular action.
|
||||||
|
|
||||||
```php
|
|
||||||
class MyPage_Controller extends Page_Controller
|
|
||||||
{
|
|
||||||
public function myprivateaction($request)
|
|
||||||
{
|
|
||||||
$response = $this->myPrivateResponse();
|
|
||||||
HTTPCacheControl::singleton()
|
|
||||||
->disableCache();
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: SilverStripe will still override this preference when a session is active,
|
Note: SilverStripe will still override this preference when a session is active,
|
||||||
@ -155,18 +132,6 @@ But any subsequent requests by this visitor will also carry a session, leading t
|
|||||||
for this visitor. This is the case even if the output does not contain any forms,
|
for this visitor. This is the case even if the output does not contain any forms,
|
||||||
and does not vary for this particular visitor.
|
and does not vary for this particular visitor.
|
||||||
|
|
||||||
```php
|
|
||||||
class Page_Controller extends ContentController
|
|
||||||
{
|
|
||||||
public function init()
|
|
||||||
{
|
|
||||||
HTTPCacheControl::singleton()
|
|
||||||
->enableCache($force=true) // DANGER ZONE
|
|
||||||
->setMaxAge(60); // 1 minute
|
|
||||||
|
|
||||||
parent::init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Defaults
|
## Defaults
|
||||||
@ -188,35 +153,30 @@ The cache age determines the lifetime of your cache, in seconds.
|
|||||||
It only takes effect if you instruct the cache control
|
It only takes effect if you instruct the cache control
|
||||||
that your response is public in the first place (via `enableCache()` or via modifying the `HTTP.cache_control` defaults).
|
that your response is public in the first place (via `enableCache()` or via modifying the `HTTP.cache_control` defaults).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
HTTPCacheControl::singleton()
|
HTTPCacheControl::singleton()
|
||||||
->setMaxAge(60)
|
->setMaxAge(60)
|
||||||
|
|
||||||
Note that `setMaxAge(0)` is NOT sufficient to disable caching in all cases.
|
```
|
||||||
|
|
||||||
### Last Modified
|
### Last Modified
|
||||||
|
|
||||||
Used to set the modification date to something more recent than the default. [api:DataObject::__construct] calls
|
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.
|
[api:HTTP::register_modification_date(] whenever a record comes from the database ensuring the newest date is present.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
HTTP::register_modification_date('2014-10-10');
|
HTTP::register_modification_date('2014-10-10');
|
||||||
|
|
||||||
### Vary
|
```
|
||||||
|
|
||||||
A `Vary` header tells caches which aspects of the response should be considered
|
A `Vary` header tells caches which aspects of the response should be considered
|
||||||
when calculating a cache key, usually in addition to the full URL path.
|
when calculating a cache key, usually in addition to the full URL path.
|
||||||
By default, SilverStripe will output a `Vary` header with the following content:
|
By default, SilverStripe will output a `Vary` header with the following content:
|
||||||
|
|
||||||
```
|
|
||||||
Vary: X-Forwarded-Protocol
|
|
||||||
```
|
```
|
||||||
|
|
||||||
To change the value of the `Vary` header, you can change this value by specifying the header in configuration.
|
To change the value of the `Vary` header, you can change this value by specifying the header in configuration.
|
||||||
|
|
||||||
```yml
|
|
||||||
HTTP:
|
|
||||||
vary: ""
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that if you use `Director::is_ajax()` on cached pages then you should add `X-Requested-With` to the vary
|
Note that if you use `Director::is_ajax()` on cached pages then you should add `X-Requested-With` to the vary
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Profiling
|
title: Profiling
|
||||||
summary: Identify bottlenecks within your application.
|
summary: Identify bottlenecks within your application.
|
||||||
|
icon: tachometer-alt
|
||||||
|
---
|
||||||
# Profiling
|
# Profiling
|
||||||
|
|
||||||
Profiling is the best way to identify bottle necks and other slow moving parts of your application prime for
|
Profiling is the best way to identify bottle necks and other slow moving parts of your application prime for
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
|
---
|
||||||
title: Static Publishing
|
title: Static Publishing
|
||||||
summary: Export your web pages as static HTML and serve the web like it's 1999.
|
summary: Export your web pages as static HTML and serve the web like it's 1999.
|
||||||
|
---
|
||||||
# Static Publishing
|
# 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
|
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
|
time, connecting to the database and formatting your templates. This is only appropriate approach on web pages that
|
||||||
have completely static content.
|
have completely static content.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
If you want to cache part of a page, or your site has interactive elements such as forms, then
|
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.
|
[Partial Caching](partial_caching) is more suitable.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
By publishing the page as HTML it's possible to run SilverStripe from behind a corporate firewall, on a low performance
|
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.
|
server or serve millions of hits an hour without expensive hardware.
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Resource Usage
|
title: Resource Usage
|
||||||
summary: Manage SilverStripe's memory footprint and CPU usage.
|
summary: Manage SilverStripe's memory footprint and CPU usage.
|
||||||
|
icon: tachometer-alt
|
||||||
|
---
|
||||||
# Resource Usage
|
# Resource Usage
|
||||||
|
|
||||||
SilverStripe tries to keep its resource usage within the documented limits
|
SilverStripe tries to keep its resource usage within the documented limits
|
||||||
@ -10,25 +12,14 @@ These limits are defined through `memory_limit` and `max_execution_time` in the
|
|||||||
overwritten through `ini_set()`, unless PHP is running with the [Suhoshin Patches](http://www.hardened-php.net/)
|
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)".
|
or in "[safe mode](http://php.net/manual/en/features.safe-mode.php)".
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
Most shared hosting providers will have maximum values that can't be altered.
|
Most shared hosting providers will have maximum values that can't be altered.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
For certain tasks like synchronizing a large `assets/` folder with all file and folder entries in the database, more
|
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
|
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.
|
[command line](../cli), where configuration defaults for these settings are higher or even unlimited.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
SilverStripe can request more resources through `increase_memory_limit_to()` and `increase_time_limit_to()` functions.
|
SilverStripe can request more resources through `increase_memory_limit_to()` and `increase_time_limit_to()` functions.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
:::php
|
|
||||||
function myBigFunction() {
|
|
||||||
increase_time_limit_to(400);
|
|
||||||
|
|
||||||
// or..
|
|
||||||
|
|
||||||
set_increase_time_limit_max();
|
|
||||||
|
|
||||||
// ..
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
---
|
||||||
title: Performance
|
title: Performance
|
||||||
summary: Make your applications faster by learning how to write more scalable code and ways to cache your important information.
|
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.
|
introduction: Make your applications faster by learning how to write more scalable code and ways to cache your important information.
|
||||||
|
icon: tachometer-alt
|
||||||
|
---
|
||||||
The following guide describes the common ways to speed your SilverStripe website up. The general rules for getting
|
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
|
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
|
[opcode](http://en.wikipedia.org/wiki/Opcode) cache such as [XCache](http://xcache.lighttpd.net/) or
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
title: Members
|
title: Members
|
||||||
|
summary: Learn how logged in users are managed in Silverstripe CMS
|
||||||
|
icon: user
|
||||||
|
---
|
||||||
# Member
|
# Member
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
@ -15,7 +18,7 @@ The [api:Member] class comes with 2 static methods for getting information about
|
|||||||
Retrieves the ID (int) of the current logged in member. Returns *0* if user is not logged in. Much lighter than the
|
Retrieves the ID (int) of the current logged in member. Returns *0* if user is not logged in. Much lighter than the
|
||||||
next method for testing if you just need to test.
|
next method for testing if you just need to test.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// Is a member logged in?
|
// Is a member logged in?
|
||||||
if( Member::currentUserID() ) {
|
if( Member::currentUserID() ) {
|
||||||
// Yes!
|
// Yes!
|
||||||
@ -23,29 +26,29 @@ next method for testing if you just need to test.
|
|||||||
// No!
|
// No!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
**Member::currentUser()**
|
**Member::currentUser()**
|
||||||
|
|
||||||
Returns the full *Member* Object for the current user, returns *null* if user is not logged in.
|
Returns the full *Member* Object for the current user, returns *null* if user is not logged in.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
if( $member = Member::currentUser() ) {
|
if( $member = Member::currentUser() ) {
|
||||||
// Work with $member
|
// Work with $member
|
||||||
} else {
|
} else {
|
||||||
// Do non-member stuff
|
// Do non-member stuff
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
## Subclassing
|
## Subclassing
|
||||||
|
|
||||||
<div class="warning" markdown="1">
|
[warning]
|
||||||
This is the least desirable way of extending the [api:Member] class. It's better to use [api:DataExtension]
|
This is the least desirable way of extending the [api:Member] class. It's better to use [api:DataExtension]
|
||||||
(see below).
|
(see below).
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
You can define subclasses of [api:Member] to add extra fields or functionality to the built-in membership system.
|
You can define subclasses of [api:Member] to add extra fields or functionality to the built-in membership system.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyMember extends Member {
|
class MyMember extends Member {
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
"Age" => "Int",
|
"Age" => "Int",
|
||||||
@ -53,14 +56,14 @@ You can define subclasses of [api:Member] to add extra fields or functionality t
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
To ensure that all new members are created using this class, put a call to [api:Object::useCustomClass()] in
|
To ensure that all new members are created using this class, put a call to [api:Object::useCustomClass()] in
|
||||||
(project)/_config.php:
|
(project)/_config.php:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Object::useCustomClass("Member", "MyMember");
|
Object::useCustomClass("Member", "MyMember");
|
||||||
|
|
||||||
Note that if you want to look this class-name up, you can call Object::getCustomClass("Member")
|
```
|
||||||
|
|
||||||
## Overloading getCMSFields()
|
## Overloading getCMSFields()
|
||||||
|
|
||||||
@ -68,7 +71,7 @@ If you overload the built-in public function getCMSFields(), then you can change
|
|||||||
details in the newsletter system. This function returns a [api:FieldList] object. You should generally start by calling
|
details in the newsletter system. This function returns a [api:FieldList] object. You should generally start by calling
|
||||||
parent::getCMSFields() and manipulate the [api:FieldList] from there.
|
parent::getCMSFields() and manipulate the [api:FieldList] from there.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function getCMSFields() {
|
public function getCMSFields() {
|
||||||
$fields = parent::getCMSFields();
|
$fields = parent::getCMSFields();
|
||||||
$fields->insertBefore("HTMLEmail", new TextField("Age"));
|
$fields->insertBefore("HTMLEmail", new TextField("Age"));
|
||||||
@ -77,7 +80,7 @@ parent::getCMSFields() and manipulate the [api:FieldList] from there.
|
|||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
## Extending Member or DataObject?
|
## Extending Member or DataObject?
|
||||||
|
|
||||||
Basic rule: Class [api:Member] should just be extended for entities who have some kind of login.
|
Basic rule: Class [api:Member] should just be extended for entities who have some kind of login.
|
||||||
@ -93,16 +96,16 @@ Using inheritance to add extra behaviour or data fields to a member is limiting,
|
|||||||
class. A better way is to use role extensions to add this behaviour. Add the following to your
|
class. A better way is to use role extensions to add this behaviour. Add the following to your
|
||||||
`[config.yml](/developer_guides/configuration/configuration/#configuration-yaml-syntax-and-rules)`.
|
`[config.yml](/developer_guides/configuration/configuration/#configuration-yaml-syntax-and-rules)`.
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Member:
|
Member:
|
||||||
extensions:
|
extensions:
|
||||||
- MyMemberExtension
|
- MyMemberExtension
|
||||||
|
|
||||||
A role extension is simply a subclass of [api:DataExtension] that is designed to be used to add behaviour to [api:Member].
|
```
|
||||||
The roles affect the entire class - all members will get the additional behaviour. However, if you want to restrict
|
The roles affect the entire class - all members will get the additional behaviour. However, if you want to restrict
|
||||||
things, you should add appropriate [api:Permission::checkMember()] calls to the role's methods.
|
things, you should add appropriate [api:Permission::checkMember()] calls to the role's methods.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyMemberExtension extends DataExtension {
|
class MyMemberExtension extends DataExtension {
|
||||||
/**
|
/**
|
||||||
|
|
||||||
@ -127,7 +130,7 @@ things, you should add appropriate [api:Permission::checkMember()] calls to the
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
|
||||||
[api:Member]
|
[api:Member]
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Access Control
|
||||||
|
summary: Restrict CMS access to specific groups of users
|
||||||
|
icon: user-lock
|
||||||
|
---
|
||||||
|
|
||||||
# Access Control and Page Security
|
# Access Control and Page Security
|
||||||
|
|
||||||
There is a fairly comprehensive security mechanism in place for SilverStripe. If you want to add premium content to your
|
There is a fairly comprehensive security mechanism in place for SilverStripe. If you want to add premium content to your
|
||||||
@ -21,16 +27,18 @@ In the security tab you can make groups for security. The way this was intended
|
|||||||
intuitive):
|
intuitive):
|
||||||
|
|
||||||
* employees
|
* employees
|
||||||
|
```
|
||||||
* marketing
|
* marketing
|
||||||
* marketing executive
|
* marketing executive
|
||||||
|
|
||||||
Thus, the further up the hierarchy you go the MORE privileges you can get. Similarly, you could have:
|
```
|
||||||
|
|
||||||
* members
|
* members
|
||||||
|
```
|
||||||
* coordinators
|
* coordinators
|
||||||
* admins
|
* admins
|
||||||
|
|
||||||
Where members have some privileges, coordinators slightly more and administrators the most; having each group inheriting
|
```
|
||||||
privileges from its parent group.
|
privileges from its parent group.
|
||||||
|
|
||||||
## Permission checking is at class level
|
## Permission checking is at class level
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Permissions
|
||||||
|
summary: Customise the permission system in Silverstripe
|
||||||
|
icon: lock
|
||||||
|
---
|
||||||
|
|
||||||
# User Permissions
|
# User Permissions
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
@ -22,7 +28,7 @@ The simple usage, Permission::check("PERM_CODE") will detect if the currently lo
|
|||||||
[api:PermissionProvider] is an interface which lets you define a method *providePermissions()*.
|
[api:PermissionProvider] is an interface which lets you define a method *providePermissions()*.
|
||||||
This method should return a map of permission code names with a human readable explanation of its purpose.
|
This method should return a map of permission code names with a human readable explanation of its purpose.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class Page_Controller implements PermissionProvider {
|
class Page_Controller implements PermissionProvider {
|
||||||
public function init() {
|
public function init() {
|
||||||
parent::init();
|
parent::init();
|
||||||
@ -36,7 +42,7 @@ This method should return a map of permission code names with a human readable e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
This can then be used to add a dropdown for permission codes to the security panel. Permission::get_all_codes() will be
|
This can then be used to add a dropdown for permission codes to the security panel. Permission::get_all_codes() will be
|
||||||
a helper method that will call providePermissions() on every applicable class, and collate the resuls into a single
|
a helper method that will call providePermissions() on every applicable class, and collate the resuls into a single
|
||||||
dropdown.
|
dropdown.
|
||||||
@ -89,12 +95,12 @@ This works much like ADMIN permissions (see above)
|
|||||||
|
|
||||||
You can check if a user has access to the CMS by simply performing a check against `CMS_ACCESS`.
|
You can check if a user has access to the CMS by simply performing a check against `CMS_ACCESS`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
if (Permission::checkMember($member, 'CMS_ACCESS')) {
|
if (Permission::checkMember($member, 'CMS_ACCESS')) {
|
||||||
//user can access the CMS
|
//user can access the CMS
|
||||||
}
|
}
|
||||||
|
|
||||||
Internally, this checks that the user has any of the defined `CMS_ACCESS_*` permissions.
|
```
|
||||||
|
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Authentication
|
title: Authentication
|
||||||
summary: Explains SilverStripe's Authentication options and custom authenticators.
|
summary: Explains SilverStripe's Authentication options and custom authenticators.
|
||||||
|
icon: users-cog
|
||||||
|
---
|
||||||
# Authentication
|
# 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
|
||||||
@ -39,11 +41,11 @@ and password to be configured for a single special user outside of the normal me
|
|||||||
|
|
||||||
It is advisable to configure this user in your `_ss_environment.php` file outside of the web root, as below:
|
It is advisable to configure this user in your `_ss_environment.php` file outside of the web root, as below:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// Configure a default username and password to access the CMS on all sites in this environment.
|
// Configure a default username and password to access the CMS on all sites in this environment.
|
||||||
define('SS_DEFAULT_ADMIN_USERNAME', 'admin');
|
define('SS_DEFAULT_ADMIN_USERNAME', 'admin');
|
||||||
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
|
define('SS_DEFAULT_ADMIN_PASSWORD', 'password');
|
||||||
|
|
||||||
When a user logs in with these credentials, then a [api:Member] with the Email 'admin' will be generated in
|
```
|
||||||
the database, but without any password information. This means that the password can be reset or changed by simply
|
the database, but without any password information. This means that the password can be reset or changed by simply
|
||||||
updating the `_ss_environment.php` file.
|
updating the `_ss_environment.php` file.
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Security
|
||||||
|
summary: Learn how to minimise vulnerabilities in your code
|
||||||
|
icon: user-secret
|
||||||
|
---
|
||||||
|
|
||||||
# Security
|
# Security
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
@ -26,7 +32,7 @@ come from user input.
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$records = DB::prepared_query('SELECT * FROM "MyClass" WHERE "ID" = ?', array(3));
|
$records = DB::prepared_query('SELECT * FROM "MyClass" WHERE "ID" = ?', array(3));
|
||||||
$records = MyClass::get()->where(array('"ID" = ?' => 3));
|
$records = MyClass::get()->where(array('"ID" = ?' => 3));
|
||||||
$records = MyClass::get()->where(array('"ID"' => 3));
|
$records = MyClass::get()->where(array('"ID"' => 3));
|
||||||
@ -35,9 +41,9 @@ Example:
|
|||||||
$records = MyClass::get()->byID(3);
|
$records = MyClass::get()->byID(3);
|
||||||
$records = SQLQuery::create()->addWhere(array('"ID"' => 3))->execute();
|
$records = SQLQuery::create()->addWhere(array('"ID"' => 3))->execute();
|
||||||
|
|
||||||
Parameterised updates and inserts are also supported, but the syntax is a little different
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
SQLInsert::create('"MyClass"')
|
SQLInsert::create('"MyClass"')
|
||||||
->assign('"Name"', 'Daniel')
|
->assign('"Name"', 'Daniel')
|
||||||
->addAssignments(array(
|
->addAssignments(array(
|
||||||
@ -53,7 +59,7 @@ Parameterised updates and inserts are also supported, but the syntax is a little
|
|||||||
array('Daniel', 'Accountant', 24, 28)
|
array('Daniel', 'Accountant', 24, 28)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
```
|
||||||
### Automatic escaping
|
### Automatic escaping
|
||||||
|
|
||||||
SilverStripe internally will use parameterised queries in SQL statements wherever possible.
|
SilverStripe internally will use parameterised queries in SQL statements wherever possible.
|
||||||
@ -77,7 +83,7 @@ handled via prepared statements.
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// automatically escaped/quoted
|
// automatically escaped/quoted
|
||||||
$members = Member::get()->filter('Name', $_GET['name']);
|
$members = Member::get()->filter('Name', $_GET['name']);
|
||||||
// automatically escaped/quoted
|
// automatically escaped/quoted
|
||||||
@ -87,10 +93,10 @@ Example:
|
|||||||
// needs to be escaped and quoted manually (note raw2sql called with the $quote parameter set to true)
|
// needs to be escaped and quoted manually (note raw2sql called with the $quote parameter set to true)
|
||||||
$members = Member::get()->where(sprintf('"Name" = %s', Convert::raw2sql($_GET['name'], true)));
|
$members = Member::get()->where(sprintf('"Name" = %s', Convert::raw2sql($_GET['name'], true)));
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
```
|
||||||
It is NOT good practice to "be sure" and convert the data passed to the functions above manually. This might
|
It is NOT good practice to "be sure" and convert the data passed to the functions above manually. This might
|
||||||
result in *double escaping* and alters the actually saved data (e.g. by adding slashes to your content).
|
result in *double escaping* and alters the actually saved data (e.g. by adding slashes to your content).
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
### Manual escaping
|
### Manual escaping
|
||||||
|
|
||||||
@ -108,7 +114,7 @@ and [datamodel](/developer_guides/model) for ways to parameterise, cast, and con
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyForm extends Form {
|
class MyForm extends Form {
|
||||||
public function save($RAW_data, $form) {
|
public function save($RAW_data, $form) {
|
||||||
// Pass true as the second parameter of raw2sql to quote the value safely
|
// Pass true as the second parameter of raw2sql to quote the value safely
|
||||||
@ -118,13 +124,13 @@ Example:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
* `FormField->Value()`
|
* `FormField->Value()`
|
||||||
* URLParams passed to a Controller-method
|
* URLParams passed to a Controller-method
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
private static $allowed_actions = array('myurlaction');
|
private static $allowed_actions = array('myurlaction');
|
||||||
public function myurlaction($RAW_urlParams) {
|
public function myurlaction($RAW_urlParams) {
|
||||||
@ -135,12 +141,12 @@ Example:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
As a rule of thumb, you should escape your data **as close to querying as possible**
|
As a rule of thumb, you should escape your data **as close to querying as possible**
|
||||||
(or preferably, use parameterised queries). This means if you've got a chain of functions
|
(or preferably, use parameterised queries). This means if you've got a chain of functions
|
||||||
passing data through, escaping should happen at the end of the chain.
|
passing data through, escaping should happen at the end of the chain.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
/**
|
/**
|
||||||
* @param array $RAW_data All names in an indexed array (not SQL-safe)
|
* @param array $RAW_data All names in an indexed array (not SQL-safe)
|
||||||
@ -156,7 +162,7 @@ passing data through, escaping should happen at the end of the chain.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
This might not be applicable in all cases - especially if you are building an API thats likely to be customised. If
|
```
|
||||||
you're passing unescaped data, make sure to be explicit about it by writing *phpdoc*-documentation and *prefixing* your
|
you're passing unescaped data, make sure to be explicit about it by writing *phpdoc*-documentation and *prefixing* your
|
||||||
variables ($RAW_data instead of $data).
|
variables ($RAW_data instead of $data).
|
||||||
|
|
||||||
@ -168,10 +174,10 @@ XSS (Cross-Site-Scripting). With some basic guidelines, you can ensure your outp
|
|||||||
displaying a blog post in HTML from a trusted author, or escaping a search parameter from an untrusted visitor before
|
displaying a blog post in HTML from a trusted author, or escaping a search parameter from an untrusted visitor before
|
||||||
redisplaying it).
|
redisplaying it).
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
Note: SilverStripe templates do not remove tags, please use [strip_tags()](http://php.net/strip_tags) for this purpose
|
Note: SilverStripe templates do not remove tags, please use [strip_tags()](http://php.net/strip_tags) for this purpose
|
||||||
or [sanitize](http://htmlpurifier.org/) it correctly.
|
or [sanitize](http://htmlpurifier.org/) it correctly.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
See [http://shiflett.org/articles/foiling-cross-site-attacks](http://shiflett.org/articles/foiling-cross-site-attacks)
|
See [http://shiflett.org/articles/foiling-cross-site-attacks](http://shiflett.org/articles/foiling-cross-site-attacks)
|
||||||
for in-depth information about "Cross-Site-Scripting".
|
for in-depth information about "Cross-Site-Scripting".
|
||||||
@ -189,9 +195,10 @@ stripped out
|
|||||||
To enable filtering, set the HtmlEditorField::$sanitise_server_side [configuration](/developer_guides/configuration/configuration) property to
|
To enable filtering, set the HtmlEditorField::$sanitise_server_side [configuration](/developer_guides/configuration/configuration) property to
|
||||||
true, e.g.
|
true, e.g.
|
||||||
|
|
||||||
|
```
|
||||||
HtmlEditorField::config()->sanitise_server_side = true
|
HtmlEditorField::config()->sanitise_server_side = true
|
||||||
|
|
||||||
The built in sanitiser enforces the TinyMCE whitelist rules on the server side, and is sufficient to eliminate the
|
```
|
||||||
most common XSS vectors.
|
most common XSS vectors.
|
||||||
|
|
||||||
However some subtle XSS attacks that exploit HTML parsing bugs need heavier filtering. For greater protection
|
However some subtle XSS attacks that exploit HTML parsing bugs need heavier filtering. For greater protection
|
||||||
@ -218,7 +225,7 @@ object-properties by [casting](/developer_guides/model/data_types_and_casting) i
|
|||||||
|
|
||||||
PHP:
|
PHP:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyObject extends DataObject {
|
class MyObject extends DataObject {
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
'MyEscapedValue' => 'Text', // Example value: <b>not bold</b>
|
'MyEscapedValue' => 'Text', // Example value: <b>not bold</b>
|
||||||
@ -226,16 +233,16 @@ PHP:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Template:
|
Template:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<ul>
|
<ul>
|
||||||
<li>$MyEscapedValue</li> // output: <b>not bold<b>
|
<li>$MyEscapedValue</li> // output: <b>not bold<b>
|
||||||
<li>$MyUnescapedValue</li> // output: <b>bold</b>
|
<li>$MyUnescapedValue</li> // output: <b>bold</b>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
```
|
||||||
The example below assumes that data wasn't properly filtered when saving to the database, but are escaped before
|
The example below assumes that data wasn't properly filtered when saving to the database, but are escaped before
|
||||||
outputting through SSViewer.
|
outputting through SSViewer.
|
||||||
|
|
||||||
@ -246,7 +253,7 @@ You can force escaping on a casted value/object by using an [escape type](/devel
|
|||||||
|
|
||||||
Template (see above):
|
Template (see above):
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<ul>
|
<ul>
|
||||||
// output: <a href="#" title="foo & &#quot;bar"">foo & "bar"</a>
|
// output: <a href="#" title="foo & &#quot;bar"">foo & "bar"</a>
|
||||||
<li><a href="#" title="$Title.ATT">$Title</a></li>
|
<li><a href="#" title="$Title.ATT">$Title</a></li>
|
||||||
@ -255,7 +262,7 @@ Template (see above):
|
|||||||
<li>$MyUnescapedValue.XML</li> // output: <b>bold<b>
|
<li>$MyUnescapedValue.XML</li> // output: <b>bold<b>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
```
|
||||||
### Escaping custom attributes and getters
|
### Escaping custom attributes and getters
|
||||||
|
|
||||||
Every object attribute or getter method used for template purposes should have its escape type defined through the
|
Every object attribute or getter method used for template purposes should have its escape type defined through the
|
||||||
@ -263,7 +270,7 @@ static *$casting* array. Caution: Casting only applies when using values in a te
|
|||||||
|
|
||||||
PHP:
|
PHP:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyObject extends DataObject {
|
class MyObject extends DataObject {
|
||||||
public $Title = '<b>not bold</b>'; // will be escaped due to Text casting
|
public $Title = '<b>not bold</b>'; // will be escaped due to Text casting
|
||||||
|
|
||||||
@ -278,17 +285,17 @@ PHP:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Template:
|
Template:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<ul>
|
<ul>
|
||||||
<li>$Title</li> // output: <b>not bold<b>
|
<li>$Title</li> // output: <b>not bold<b>
|
||||||
<li>$Title.RAW</li> // output: <b>not bold</b>
|
<li>$Title.RAW</li> // output: <b>not bold</b>
|
||||||
<li>$TitleWithHTMLSuffix</li> // output: <b>not bold</b>: <small>(...)</small>
|
<li>$TitleWithHTMLSuffix</li> // output: <b>not bold</b>: <small>(...)</small>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
```
|
||||||
Note: Avoid generating HTML by string concatenation in PHP wherever possible to minimize risk and separate your
|
Note: Avoid generating HTML by string concatenation in PHP wherever possible to minimize risk and separate your
|
||||||
presentation from business logic.
|
presentation from business logic.
|
||||||
|
|
||||||
@ -302,7 +309,7 @@ also used by *XML* and *ATT* in template code).
|
|||||||
|
|
||||||
PHP:
|
PHP:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
private static $allowed_actions = array('search');
|
private static $allowed_actions = array('search');
|
||||||
public function search($request) {
|
public function search($request) {
|
||||||
@ -314,13 +321,13 @@ PHP:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Template:
|
Template:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<h2 title="Searching for $Query.ATT">$HTMLTitle</h2>
|
<h2 title="Searching for $Query.ATT">$HTMLTitle</h2>
|
||||||
|
|
||||||
|
```
|
||||||
Whenever you insert a variable into an HTML attribute within a template, use $VarName.ATT, no not $VarName.
|
Whenever you insert a variable into an HTML attribute within a template, use $VarName.ATT, no not $VarName.
|
||||||
|
|
||||||
You can also use the built-in casting in PHP by using the *obj()* wrapper, see [datamodel](/developer_guides/model/data_types_and_casting).
|
You can also use the built-in casting in PHP by using the *obj()* wrapper, see [datamodel](/developer_guides/model/data_types_and_casting).
|
||||||
@ -332,7 +339,7 @@ user data, not *Convert::raw2att()*. Use raw ampersands in your URL, and cast t
|
|||||||
|
|
||||||
PHP:
|
PHP:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
private static $allowed_actions = array('search');
|
private static $allowed_actions = array('search');
|
||||||
public function search($request) {
|
public function search($request) {
|
||||||
@ -344,13 +351,13 @@ PHP:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Template:
|
Template:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<a href="$RSSLink.ATT">RSS feed</a>
|
<a href="$RSSLink.ATT">RSS feed</a>
|
||||||
|
|
||||||
|
```
|
||||||
Some rules of thumb:
|
Some rules of thumb:
|
||||||
|
|
||||||
* Don't concatenate URLs in a template. It only works in extremely simple cases that usually contain bugs.
|
* Don't concatenate URLs in a template. It only works in extremely simple cases that usually contain bugs.
|
||||||
@ -395,7 +402,7 @@ passed, such as *mysite.com/home/add/dfsdfdsfd*, then it returns 0.
|
|||||||
|
|
||||||
Below is an example with different ways you would use this casting technique:
|
Below is an example with different ways you would use this casting technique:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function CaseStudies() {
|
public function CaseStudies() {
|
||||||
|
|
||||||
// cast an ID from URL parameters e.g. (mysite.com/home/action/ID)
|
// cast an ID from URL parameters e.g. (mysite.com/home/action/ID)
|
||||||
@ -411,7 +418,7 @@ Below is an example with different ways you would use this casting technique:
|
|||||||
return CaseStudy::get()->byID($categoryID);
|
return CaseStudy::get()->byID($categoryID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
The same technique can be employed anywhere in your PHP code you know something must be of a certain type. A list of PHP
|
The same technique can be employed anywhere in your PHP code you know something must be of a certain type. A list of PHP
|
||||||
cast types can be found here:
|
cast types can be found here:
|
||||||
|
|
||||||
@ -438,6 +445,7 @@ disallow certain filetypes.
|
|||||||
|
|
||||||
Example configuration for Apache2:
|
Example configuration for Apache2:
|
||||||
|
|
||||||
|
```
|
||||||
<VirtualHost *:80>
|
<VirtualHost *:80>
|
||||||
<LocationMatch assets/>
|
<LocationMatch assets/>
|
||||||
php_flag engine off
|
php_flag engine off
|
||||||
@ -445,16 +453,17 @@ Example configuration for Apache2:
|
|||||||
</LocationMatch>
|
</LocationMatch>
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
|
|
||||||
|
```
|
||||||
If you are using shared hosting or in a situation where you cannot alter your Vhost definitions, you can use a .htaccess
|
If you are using shared hosting or in a situation where you cannot alter your Vhost definitions, you can use a .htaccess
|
||||||
file in the assets directory. This requires PHP to be loaded as an Apache module (not CGI or FastCGI).
|
file in the assets directory. This requires PHP to be loaded as an Apache module (not CGI or FastCGI).
|
||||||
|
|
||||||
**/assets/.htaccess**
|
**/assets/.htaccess**
|
||||||
|
|
||||||
|
```
|
||||||
php_flag engine off
|
php_flag engine off
|
||||||
Options -ExecCGI -Includes -Indexes
|
Options -ExecCGI -Includes -Indexes
|
||||||
|
|
||||||
### Don't allow access to YAML files
|
```
|
||||||
|
|
||||||
YAML files are often used to store sensitive or semi-sensitive data for use by
|
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
|
SilverStripe, such as configuration files. We block access to any files
|
||||||
@ -463,12 +472,13 @@ If you need users to access files with this extension,
|
|||||||
you can bypass the rules for a specific directory.
|
you can bypass the rules for a specific directory.
|
||||||
Here's an example for a `.htaccess` file used by the Apache web server:
|
Here's an example for a `.htaccess` file used by the Apache web server:
|
||||||
|
|
||||||
|
```
|
||||||
<Files *.yml>
|
<Files *.yml>
|
||||||
Order allow,deny
|
Order allow,deny
|
||||||
Allow from all
|
Allow from all
|
||||||
</Files>
|
</Files>
|
||||||
|
|
||||||
|
```
|
||||||
### User uploaded files
|
### User uploaded files
|
||||||
|
|
||||||
Certain file types are by default excluded from user upload. html, xhtml, htm, and xml files may have embedded,
|
Certain file types are by default excluded from user upload. html, xhtml, htm, and xml files may have embedded,
|
||||||
@ -512,19 +522,20 @@ So in addition to storing the password in a secure fashion,
|
|||||||
you can also enforce specific password policies by configuring
|
you can also enforce specific password policies by configuring
|
||||||
a [api:PasswordValidator]:
|
a [api:PasswordValidator]:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$validator = new PasswordValidator();
|
$validator = new PasswordValidator();
|
||||||
$validator->minLength(7);
|
$validator->minLength(7);
|
||||||
$validator->checkHistoricalPasswords(6);
|
$validator->checkHistoricalPasswords(6);
|
||||||
$validator->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"));
|
$validator->characterStrength(3, array("lowercase", "uppercase", "digits", "punctuation"));
|
||||||
Member::set_password_validator($validator);
|
Member::set_password_validator($validator);
|
||||||
|
|
||||||
In addition, you can tighten password security with the following configuration settings:
|
```
|
||||||
|
|
||||||
* `Member.password_expiry_days`: Set the number of days that a password should be valid for.
|
* `Member.password_expiry_days`: Set the number of days that a password should be valid for.
|
||||||
* `Member.lock_out_after_incorrect_logins`: Number of incorrect logins after which
|
* `Member.lock_out_after_incorrect_logins`: Number of incorrect logins after which
|
||||||
|
```
|
||||||
the user is blocked from further attempts for the timespan defined in `$lock_out_delay_mins`
|
the user is blocked from further attempts for the timespan defined in `$lock_out_delay_mins`
|
||||||
* `Member.lock_out_delay_mins`: Minutes of enforced lockout after incorrect password attempts.
|
```
|
||||||
Only applies if `lock_out_after_incorrect_logins` is greater than 0.
|
Only applies if `lock_out_after_incorrect_logins` is greater than 0.
|
||||||
* `Security.remember_username`: Set to false to disable autocomplete on login form
|
* `Security.remember_username`: Set to false to disable autocomplete on login form
|
||||||
|
|
||||||
@ -539,7 +550,7 @@ included in HTML "frame" or "iframe" elements, and thereby prevent the most comm
|
|||||||
attack vector. This is done through a HTTP header, which is usually added in your
|
attack vector. This is done through a HTTP header, which is usually added in your
|
||||||
controller's `init()` method:
|
controller's `init()` method:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
public function init() {
|
public function init() {
|
||||||
parent::init();
|
parent::init();
|
||||||
@ -547,7 +558,7 @@ controller's `init()` method:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
This is a recommended option to secure any controller which displays
|
This is a recommended option to secure any controller which displays
|
||||||
or submits sensitive user input, and is enabled by default in all CMS controllers,
|
or submits sensitive user input, and is enabled by default in all CMS controllers,
|
||||||
as well as the login form.
|
as well as the login form.
|
||||||
@ -559,10 +570,10 @@ allows the configure of a whitelist of hosts that are allowed to access the syst
|
|||||||
this whitelist in your _ss_environment.php file, any request presenting a `Host` header that is
|
this whitelist in your _ss_environment.php file, any request presenting a `Host` header that is
|
||||||
_not_ in this list will be blocked with a HTTP 400 error:
|
_not_ in this list will be blocked with a HTTP 400 error:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
define('SS_ALLOWED_HOSTS', 'www.mysite.com,mysite.com,subdomain.mysite.com');
|
define('SS_ALLOWED_HOSTS', 'www.mysite.com,mysite.com,subdomain.mysite.com');
|
||||||
|
|
||||||
Please note that if this configuration is defined, you _must_ include _all_ subdomains (eg www.)
|
```
|
||||||
that will be accessing the site.
|
that will be accessing the site.
|
||||||
|
|
||||||
When SilverStripe is run behind a reverse proxy, it's normally necessary for this proxy to
|
When SilverStripe is run behind a reverse proxy, it's normally necessary for this proxy to
|
||||||
@ -578,13 +589,13 @@ In order to prevent this kind of attack, it's necessary to whitelist trusted pro
|
|||||||
server IPs using the SS_TRUSTED_PROXY_IPS define in your _ss_environment.php.
|
server IPs using the SS_TRUSTED_PROXY_IPS define in your _ss_environment.php.
|
||||||
|
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
define('SS_TRUSTED_PROXY_IPS', '127.0.0.1,192.168.0.1');
|
define('SS_TRUSTED_PROXY_IPS', '127.0.0.1,192.168.0.1');
|
||||||
define('SS_TRUSTED_PROXY_HOST_HEADER', 'HTTP_X_FORWARDED_HOST');
|
define('SS_TRUSTED_PROXY_HOST_HEADER', 'HTTP_X_FORWARDED_HOST');
|
||||||
define('SS_TRUSTED_PROXY_IP_HEADER', 'HTTP_X_FORWARDED_FOR');
|
define('SS_TRUSTED_PROXY_IP_HEADER', 'HTTP_X_FORWARDED_FOR');
|
||||||
define('SS_TRUSTED_PROXY_PROTOCOL_HEADER', 'HTTP_X_FORWARDED_PROTOCOL');
|
define('SS_TRUSTED_PROXY_PROTOCOL_HEADER', 'HTTP_X_FORWARDED_PROTOCOL');
|
||||||
|
|
||||||
At the same time, you'll also need to define which headers you trust from these proxy IPs. Since there are multiple ways through which proxies can pass through HTTP information on the original hostname, IP and protocol, these values need to be adjusted for your specific proxy. The header names match their equivalent `$_SERVER` values.
|
```
|
||||||
|
|
||||||
If there is no proxy server, 'none' can be used to distrust all clients.
|
If there is no proxy server, 'none' can be used to distrust all clients.
|
||||||
If only trusted servers will make requests then you can use '*' to trust all clients.
|
If only trusted servers will make requests then you can use '*' to trust all clients.
|
||||||
@ -595,6 +606,7 @@ This behaviour is enabled whenever SS_TRUSTED_PROXY_IPS is defined, or if the
|
|||||||
following in your .htaccess to ensure this behaviour is activated.
|
following in your .htaccess to ensure this behaviour is activated.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
<IfModule mod_env.c>
|
<IfModule mod_env.c>
|
||||||
# Ensure that X-Forwarded-Host is only allowed to determine the request
|
# Ensure that X-Forwarded-Host is only allowed to determine the request
|
||||||
# hostname for servers ips defined by SS_TRUSTED_PROXY_IPS in your _ss_environment.php
|
# hostname for servers ips defined by SS_TRUSTED_PROXY_IPS in your _ss_environment.php
|
||||||
@ -602,7 +614,7 @@ following in your .htaccess to ensure this behaviour is activated.
|
|||||||
SetEnv BlockUntrustedIPs true
|
SetEnv BlockUntrustedIPs true
|
||||||
</IfModule>
|
</IfModule>
|
||||||
|
|
||||||
|
```
|
||||||
In a future release this behaviour will be changed to be on by default, and this environment
|
In a future release this behaviour will be changed to be on by default, and this environment
|
||||||
variable will be no longer necessary, thus it will be necessary to always set
|
variable will be no longer necessary, thus it will be necessary to always set
|
||||||
SS_TRUSTED_PROXY_IPS if using a proxy.
|
SS_TRUSTED_PROXY_IPS if using a proxy.
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Security
|
||||||
summary: This guide covers user authentication, the permission system and how to secure your code against malicious behaviors
|
summary: This guide covers user authentication, the permission system and how to secure your code against malicious behaviors
|
||||||
|
icon: user-shield
|
||||||
|
---
|
||||||
# Security and User Authentication
|
# Security and User Authentication
|
||||||
|
|
||||||
This guide covers using and extending the user authentication in SilverStripe, permissions, user groups and roles, and
|
This guide covers using and extending the user authentication in SilverStripe, permissions, user groups and roles, and
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Email
|
||||||
summary: Send HTML and plain text email from your SilverStripe application.
|
summary: Send HTML and plain text email from your SilverStripe application.
|
||||||
|
icon: envelope-open
|
||||||
|
---
|
||||||
# Email
|
# Email
|
||||||
|
|
||||||
Creating and sending email in SilverStripe is done through the [api:Email] and [api:Mailer] classes. This document
|
Creating and sending email in SilverStripe is done through the [api:Email] and [api:Mailer] classes. This document
|
||||||
@ -16,25 +19,25 @@ and [Postmark](https://github.com/fullscreeninteractive/silverstripe-postmarkmai
|
|||||||
|
|
||||||
### Sending plain text only
|
### Sending plain text only
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$email = new Email($from, $to, $subject, $body);
|
$email = new Email($from, $to, $subject, $body);
|
||||||
$email->sendPlain();
|
$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
|
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
|
from the system by stripping HTML markup, or transforming it where possible (e.g. `<strong>text</strong>` is converted
|
||||||
to `*text*`).
|
to `*text*`).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$email = new Email($from, $to, $subject, $body);
|
$email = new Email($from, $to, $subject, $body);
|
||||||
$email->send();
|
$email->send();
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
```
|
||||||
The default HTML template for emails is named `GenericEmail` and is located in `framework/templates/email/`. To
|
The default HTML template for emails is named `GenericEmail` and is located in `framework/templates/email/`. To
|
||||||
customise this template, copy it to the `mysite/templates/Email/` folder or use `setTemplate` when you create the
|
customise this template, copy it to the `mysite/templates/Email/` folder or use `setTemplate` when you create the
|
||||||
`Email` instance.
|
`Email` instance.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
|
|
||||||
### Templates
|
### Templates
|
||||||
@ -44,13 +47,13 @@ email object additional information using the `populateTemplate` method.
|
|||||||
|
|
||||||
**mysite/templates/Email/MyCustomEmail.ss**
|
**mysite/templates/Email/MyCustomEmail.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<h1>Hi $Member.FirstName</h1>
|
<h1>Hi $Member.FirstName</h1>
|
||||||
<p>You can go to $Link.</p>
|
<p>You can go to $Link.</p>
|
||||||
|
|
||||||
The PHP Logic..
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$email = new Email();
|
$email = new Email();
|
||||||
$email
|
$email
|
||||||
->setFrom($from)
|
->setFrom($from)
|
||||||
@ -64,10 +67,10 @@ The PHP Logic..
|
|||||||
|
|
||||||
$email->send();
|
$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
|
As we've added a new template file (`MyCustomEmail`) make sure you clear the SilverStripe cache for your changes to
|
||||||
take affect.
|
take affect.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## Sub classing
|
## Sub classing
|
||||||
|
|
||||||
@ -76,7 +79,7 @@ a new subclass of `Email` which takes the required dependencies and handles sett
|
|||||||
|
|
||||||
**mysite/code/MyCustomEmail.php**
|
**mysite/code/MyCustomEmail.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyEmail extends Email {
|
class MyEmail extends Email {
|
||||||
@ -98,31 +101,31 @@ a new subclass of `Email` which takes the required dependencies and handles sett
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Then within your application, usage of the email is much clearer to follow.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
$member = Member::currentUser();
|
$member = Member::currentUser();
|
||||||
|
|
||||||
$email = new MyEmail($member);
|
$email = new MyEmail($member);
|
||||||
$email->send();
|
$email->send();
|
||||||
|
|
||||||
|
```
|
||||||
## Administrator Emails
|
## Administrator Emails
|
||||||
|
|
||||||
You can set the default sender address of emails through the `Email.admin_email` [configuration setting](/developer_guides/configuration).
|
You can set the default sender address of emails through the `Email.admin_email` [configuration setting](/developer_guides/configuration).
|
||||||
|
|
||||||
**mysite/_config/app.yml**
|
**mysite/_config/app.yml**
|
||||||
|
|
||||||
:::yaml
|
```yaml
|
||||||
Email:
|
Email:
|
||||||
admin_email: support@silverstripe.org
|
admin_email: support@silverstripe.org
|
||||||
|
|
||||||
|
```
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
Remember, setting a `from` address that doesn't come from your domain (such as the users email) will likely see your
|
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 `setReplyTo` method.
|
email marked as spam. If you want to send from another address think about using the `setReplyTo` method.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## Redirecting Emails
|
## Redirecting Emails
|
||||||
|
|
||||||
@ -137,34 +140,34 @@ Configuration of those properties looks like the following:
|
|||||||
|
|
||||||
**mysite/_config.php**
|
**mysite/_config.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
if(Director::isLive()) {
|
if(Director::isLive()) {
|
||||||
Config::inst()->update('Email', 'bcc_all_emails_to', "client@example.com");
|
Config::inst()->update('Email', 'bcc_all_emails_to', "client@example.com");
|
||||||
} else {
|
} else {
|
||||||
Config::inst()->update('Email', 'send_all_emails_to', "developer@example.com");
|
Config::inst()->update('Email', 'send_all_emails_to', "developer@example.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
### Setting custom "Reply To" email address.
|
```
|
||||||
|
|
||||||
For email messages that should have an email address which is replied to that actually differs from the original "from" email, do the following. This is encouraged especially when the domain responsible for sending the message isn't necessarily the same which should be used for return correspondence and should help prevent your message from being marked as spam.
|
For email messages that should have an email address which is replied to that actually differs from the original "from" email, do the following. This is encouraged especially when the domain responsible for sending the message isn't necessarily the same which should be used for return correspondence and should help prevent your message from being marked as spam.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$email = new Email(..);
|
$email = new Email(..);
|
||||||
$email->setReplyTo('me@address.com');
|
$email->setReplyTo('me@address.com');
|
||||||
|
|
||||||
### Setting Custom Headers
|
```
|
||||||
|
|
||||||
For email headers which do not have getters or setters (like setTo(), setFrom()) you can use **addCustomHeader($header,
|
For email headers which do not have getters or setters (like setTo(), setFrom()) you can use **addCustomHeader($header,
|
||||||
$value)**
|
$value)**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$email = new Email(...);
|
$email = new Email(...);
|
||||||
$email->addCustomHeader('HeaderName', 'HeaderValue');
|
$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.
|
See this [Wikipedia](http://en.wikipedia.org/wiki/E-mail#Message_header) entry for a list of header names.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
## Newsletters
|
## Newsletters
|
||||||
|
|
||||||
@ -176,17 +179,17 @@ SilverStripe supports changing out the underlying web server SMTP mailer service
|
|||||||
function. A `Mailer` subclass will commonly override the `sendPlain` and `sendHTML` methods to send emails through curl
|
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.
|
or some other process that isn't the built in `mail()` command.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
There are a number of custom mailer add-ons available like [Mandrill](https://github.com/lekoala/silverstripe-mandrill)
|
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).
|
and [Postmark](https://github.com/fullscreeninteractive/silverstripe-postmarkmailer).
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
In this example, `LocalMailer` will take any email's going while the site is in Development mode and save it to the
|
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.
|
assets folder instead.
|
||||||
|
|
||||||
**mysite/code/LocalMailer.php**
|
**mysite/code/LocalMailer.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class LocalMailer extends Mailer {
|
class LocalMailer extends Mailer {
|
||||||
@ -197,7 +200,8 @@ assets folder instead.
|
|||||||
file_put_contents($file, $htmlContent);
|
file_put_contents($file, $htmlContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
```
|
||||||
function sendPlain($to, $from, $subject, $htmlContent, $attachedFiles = false, $customheaders = false, $plainContent = false, $inlineImages = false) {
|
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 = ASSETS_PATH . '/_mail_'. urlencode(sprintf("%s_%s", $subject, $to));
|
||||||
|
|
||||||
@ -205,16 +209,16 @@ assets folder instead.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
**mysite/_config.php**
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
if(Director::isLive()) {
|
if(Director::isLive()) {
|
||||||
Email::set_mailer(new PostmarkMailer());
|
Email::set_mailer(new PostmarkMailer());
|
||||||
} else {
|
} else {
|
||||||
Email::set_mailer(new LocalMailer());
|
Email::set_mailer(new LocalMailer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
### Setting bounce handler
|
### Setting bounce handler
|
||||||
|
|
||||||
A bounce handler email can be specified one of a few ways:
|
A bounce handler email can be specified one of a few ways:
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: CSV Import
|
||||||
|
summary: Load data into your Silverstripe database in bulk
|
||||||
|
icon: upload
|
||||||
|
---
|
||||||
|
|
||||||
# Import CSV data
|
# Import CSV data
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
@ -28,24 +34,25 @@ You can use the CsvBulkLoader without subclassing or other customizations, if th
|
|||||||
in your CSV file match `$db` properties in your dataobject. E.g. a simple import for the
|
in your CSV file match `$db` properties in your dataobject. E.g. a simple import for the
|
||||||
[api:Member] class could have this data in a file:
|
[api:Member] class could have this data in a file:
|
||||||
|
|
||||||
|
```
|
||||||
FirstName,LastName,Email
|
FirstName,LastName,Email
|
||||||
Donald,Duck,donald@disney.com
|
Donald,Duck,donald@disney.com
|
||||||
Daisy,Duck,daisy@disney.com
|
Daisy,Duck,daisy@disney.com
|
||||||
|
|
||||||
The loader would be triggered through the `load()` method:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$loader = new CsvBulkLoader('Member');
|
$loader = new CsvBulkLoader('Member');
|
||||||
$result = $loader->load('<my-file-path>');
|
$result = $loader->load('<my-file-path>');
|
||||||
|
|
||||||
By the way, you can import [api:Member] and [api:Group] data through `http://localhost/admin/security`
|
```
|
||||||
interface out of the box.
|
interface out of the box.
|
||||||
|
|
||||||
## Import through ModelAdmin
|
## Import through ModelAdmin
|
||||||
|
|
||||||
The simplest way to use [api:CsvBulkLoader] is through a [api:ModelAdmin] interface - you get an upload form out of the box.
|
The simplest way to use [api:CsvBulkLoader] is through a [api:ModelAdmin] interface - you get an upload form out of the box.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class PlayerAdmin extends ModelAdmin {
|
class PlayerAdmin extends ModelAdmin {
|
||||||
private static $managed_models = array(
|
private static $managed_models = array(
|
||||||
@ -58,7 +65,7 @@ The simplest way to use [api:CsvBulkLoader] is through a [api:ModelAdmin] interf
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
The new admin interface will be available under `http://localhost/admin/players`, the import form is located
|
```
|
||||||
below the search form on the left.
|
below the search form on the left.
|
||||||
|
|
||||||
## Import through a custom controller
|
## Import through a custom controller
|
||||||
@ -68,7 +75,7 @@ Let's create a simple upload form (which is used for `MyDataObject` instances).
|
|||||||
You'll need to add a route to your controller to make it accessible via URL
|
You'll need to add a route to your controller to make it accessible via URL
|
||||||
(see [director](/reference/director)).
|
(see [director](/reference/director)).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
|
|
||||||
@ -109,7 +116,7 @@ You'll need to add a route to your controller to make it accessible via URL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Note: This interface is not secured, consider using [api:Permission::check()] to limit the controller to users
|
```
|
||||||
with certain access rights.
|
with certain access rights.
|
||||||
|
|
||||||
## Column mapping and relation import
|
## Column mapping and relation import
|
||||||
@ -118,15 +125,16 @@ We're going to use our knowledge from the previous example to import a more soph
|
|||||||
|
|
||||||
Sample CSV Content
|
Sample CSV Content
|
||||||
|
|
||||||
|
```
|
||||||
"Number","Name","Birthday","Team"
|
"Number","Name","Birthday","Team"
|
||||||
11,"John Doe",1982-05-12,"FC Bayern"
|
11,"John Doe",1982-05-12,"FC Bayern"
|
||||||
12,"Jane Johnson", 1982-05-12,"FC Bayern"
|
12,"Jane Johnson", 1982-05-12,"FC Bayern"
|
||||||
13,"Jimmy Dole",,"Schalke 04"
|
13,"Jimmy Dole",,"Schalke 04"
|
||||||
|
|
||||||
|
```
|
||||||
Datamodel for Player
|
Datamodel for Player
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
@ -141,10 +149,10 @@ Datamodel for Player
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
```
|
||||||
Datamodel for FootballTeam:
|
Datamodel for FootballTeam:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class FootballTeam extends DataObject {
|
class FootballTeam extends DataObject {
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
@ -156,7 +164,7 @@ Datamodel for FootballTeam:
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
```
|
||||||
Sample implementation of a custom loader. Assumes a CSV-file in a certain format (see below).
|
Sample implementation of a custom loader. Assumes a CSV-file in a certain format (see below).
|
||||||
|
|
||||||
* Converts property names
|
* Converts property names
|
||||||
@ -165,7 +173,7 @@ Sample implementation of a custom loader. Assumes a CSV-file in a certain format
|
|||||||
* Creates `Team` relations automatically based on the `Gruppe` column in the CSV data
|
* Creates `Team` relations automatically based on the `Gruppe` column in the CSV data
|
||||||
|
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class PlayerCsvBulkLoader extends CsvBulkLoader {
|
class PlayerCsvBulkLoader extends CsvBulkLoader {
|
||||||
public $columnMap = array(
|
public $columnMap = array(
|
||||||
@ -195,9 +203,9 @@ Sample implementation of a custom loader. Assumes a CSV-file in a certain format
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
Building off of the ModelAdmin example up top, use a custom loader instead of the default loader by adding it to `$model_importers`. In this example, `CsvBulkLoader` is replaced with `PlayerCsvBulkLoader`.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class PlayerAdmin extends ModelAdmin {
|
class PlayerAdmin extends ModelAdmin {
|
||||||
private static $managed_models = array(
|
private static $managed_models = array(
|
||||||
@ -210,7 +218,7 @@ Building off of the ModelAdmin example up top, use a custom loader instead of th
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
```
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
* [api:CsvParser]
|
* [api:CsvParser]
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
|
---
|
||||||
|
title: Restful service
|
||||||
summary: Consume external data through their RESTFul interfaces.
|
summary: Consume external data through their RESTFul interfaces.
|
||||||
|
---
|
||||||
# Restful Service
|
# Restful Service
|
||||||
|
|
||||||
[api:RestfulService] is used to enable connections to remote web services through PHP's `curl` command. It provides an
|
[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.
|
interface and utility functions for generating a valid request and parsing the response returned from the web service.
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
RestfulService currently only supports XML. It has no JSON support at this stage.
|
RestfulService currently only supports XML. It has no JSON support at this stage.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@ -19,7 +21,7 @@ in the template.
|
|||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function getWellingtonWeather() {
|
public function getWellingtonWeather() {
|
||||||
$fetch = new RestfulService(
|
$fetch = new RestfulService(
|
||||||
'https://query.yahooapis.com/v1/public/yql'
|
'https://query.yahooapis.com/v1/public/yql'
|
||||||
@ -49,17 +51,17 @@ in the template.
|
|||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
## Features
|
```
|
||||||
|
|
||||||
### Basic Authenication
|
### Basic Authenication
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$service = new RestfulService("http://example.harvestapp.com");
|
$service = new RestfulService("http://example.harvestapp.com");
|
||||||
$service->basicAuth('username', 'password');
|
$service->basicAuth('username', 'password');
|
||||||
|
|
||||||
### Make multiple requests
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$service = new RestfulService("http://example.harvestapp.com");
|
$service = new RestfulService("http://example.harvestapp.com");
|
||||||
|
|
||||||
$peopleXML = $service->request('/people');
|
$peopleXML = $service->request('/people');
|
||||||
@ -70,23 +72,23 @@ in the template.
|
|||||||
$taskXML = $service->request('/tasks');
|
$taskXML = $service->request('/tasks');
|
||||||
$tasks = $service->getValues($taskXML, 'task');
|
$tasks = $service->getValues($taskXML, 'task');
|
||||||
|
|
||||||
|
```
|
||||||
### Caching
|
### Caching
|
||||||
|
|
||||||
To set the cache interval you can pass it as the 2nd argument to constructor.
|
To set the cache interval you can pass it as the 2nd argument to constructor.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$expiry = 60 * 60; // 1 hour;
|
$expiry = 60 * 60; // 1 hour;
|
||||||
|
|
||||||
$request = new RestfulService("http://example.harvestapp.com", $expiry );
|
$request = new RestfulService("http://example.harvestapp.com", $expiry );
|
||||||
|
|
||||||
|
```
|
||||||
### Getting Values & Attributes
|
### Getting Values & Attributes
|
||||||
|
|
||||||
You can traverse through document tree to get the values or attribute of a particular node using XPath. Take for example
|
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.
|
the following XML that is returned.
|
||||||
|
|
||||||
:::xml
|
```xml
|
||||||
<entries>
|
<entries>
|
||||||
<entry id='12'>Sally</entry>
|
<entry id='12'>Sally</entry>
|
||||||
<entry id='15'>Ted</entry>
|
<entry id='15'>Ted</entry>
|
||||||
@ -94,41 +96,41 @@ the following XML that is returned.
|
|||||||
<entry id='22'>John</entry>
|
<entry id='22'>John</entry>
|
||||||
</entries>
|
</entries>
|
||||||
|
|
||||||
To extract the id attributes of the entries use:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$this->getAttributes($xml, "entries", "entry");
|
$this->getAttributes($xml, "entries", "entry");
|
||||||
|
|
||||||
// array(array('id' => 12), array('id' => '15'), ..)
|
// array(array('id' => 12), array('id' => '15'), ..)
|
||||||
|
|
||||||
To extract the values (the names) of the entries use:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$this->getValues($xml, "entries", "entry");
|
$this->getValues($xml, "entries", "entry");
|
||||||
|
|
||||||
// array('Sally', 'Ted', 'Matt', 'John')
|
// 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.
|
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">
|
[note]
|
||||||
This is the recommended method for retrieving values of name spaced nodes.
|
This is the recommended method for retrieving values of name spaced nodes.
|
||||||
</div>
|
[/note]
|
||||||
|
|
||||||
:::xml
|
```xml
|
||||||
<media:guide>
|
<media:guide>
|
||||||
<media:entry id="2030">video</media:entry>
|
<media:entry id="2030">video</media:entry>
|
||||||
</media:guide>
|
</media:guide>
|
||||||
|
|
||||||
To get the value of entry node with the namespace media, use:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$this->searchValue($response, "//media:guide/media:entry");
|
$this->searchValue($response, "//media:guide/media:entry");
|
||||||
|
|
||||||
// array('video');
|
// array('video');
|
||||||
|
|
||||||
|
```
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
### Handling Errors
|
### Handling Errors
|
||||||
@ -137,7 +139,7 @@ If the web service returned an error (for example, API key not available or inad
|
|||||||
[api:RestfulService] can delegate the error handling to it's descendant class. To handle the errors, subclass
|
[api:RestfulService] can delegate the error handling to it's descendant class. To handle the errors, subclass
|
||||||
`RestfulService and define a function called errorCatch.
|
`RestfulService and define a function called errorCatch.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyRestfulService extends RestfulService {
|
class MyRestfulService extends RestfulService {
|
||||||
@ -153,9 +155,9 @@ If the web service returned an error (for example, API key not available or inad
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
If you want to bypass error handling, define `checkErrors` in the constructor for `RestfulService`
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyRestfulService extends RestfulService {
|
class MyRestfulService extends RestfulService {
|
||||||
@ -167,7 +169,7 @@ If you want to bypass error handling, define `checkErrors` in the constructor fo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
### Setting cURL options
|
### Setting cURL options
|
||||||
|
|
||||||
Restful service uses cURL to make requests. There are various settings that can be defined on the cURL
|
Restful service uses cURL to make requests. There are various settings that can be defined on the cURL
|
||||||
@ -186,11 +188,6 @@ To set global cURL settings you can update the `RestfulService` config via the C
|
|||||||
|
|
||||||
Here is an example to increase the HTTP Timeout globally. Insert this in your `_config.php` file:
|
Here is an example to increase the HTTP Timeout globally. Insert this in your `_config.php` file:
|
||||||
|
|
||||||
```php
|
|
||||||
Config::inst()->update('RestfulService', 'default_curl_options', array(
|
|
||||||
CURLOPT_DNS_CACHE_TIMEOUT => 3600,
|
|
||||||
CURLOPT_CONNECTTIMEOUT => 10,
|
|
||||||
));
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -201,16 +198,6 @@ parameter in `RestfulService::request()`.
|
|||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```php
|
|
||||||
|
|
||||||
//cURL options
|
|
||||||
$curlOptions = array(
|
|
||||||
CURLOPT_UNRESTRICTED_AUTH => true,
|
|
||||||
);
|
|
||||||
|
|
||||||
$service = new RestfulService('http://example.com/');
|
|
||||||
$service->request('service.json', 'GET', null, null, $curlOptions);
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: RSS Feed
|
title: RSS Feed
|
||||||
summary: Output records from your database as an RSS Feed.
|
summary: Output records from your database as an RSS Feed.
|
||||||
|
icon: rss
|
||||||
|
---
|
||||||
# RSS Feed
|
# RSS Feed
|
||||||
|
|
||||||
Generating RSS / Atom-feeds is a matter of rendering a [api:SS_List] instance through the [api:RSSFeed] class.
|
Generating RSS / Atom-feeds is a matter of rendering a [api:SS_List] instance through the [api:RSSFeed] class.
|
||||||
@ -10,10 +12,10 @@ your current staff members, comments or any other custom [api:DataObject] subcla
|
|||||||
logical limitation here is that every item in the RSS-feed should be accessible through a URL on your website, so it's
|
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].
|
advisable to just create feeds from subclasses of [api:SiteTree].
|
||||||
|
|
||||||
<div class="warning" markdown="1">
|
[warning]
|
||||||
If you wish to generate an RSS feed that contains a [api:DataObject], ensure you define a `AbsoluteLink` method on
|
If you wish to generate an RSS feed that contains a [api:DataObject], ensure you define a `AbsoluteLink` method on
|
||||||
the object.
|
the object.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -22,7 +24,7 @@ web pages need to link to the URL to notify users that the RSS feed is available
|
|||||||
|
|
||||||
An outline of step one looks like:
|
An outline of step one looks like:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$feed = new RSSFeed(
|
$feed = new RSSFeed(
|
||||||
$list,
|
$list,
|
||||||
$link,
|
$link,
|
||||||
@ -37,13 +39,13 @@ An outline of step one looks like:
|
|||||||
|
|
||||||
$feed->outputToBrowser();
|
$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.
|
will normally go in your `Controllers` `init` method.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
RSSFeed::linkToFeed($link, $title);
|
RSSFeed::linkToFeed($link, $title);
|
||||||
|
|
||||||
## Examples
|
```
|
||||||
|
|
||||||
### Showing the 10 most recently updated pages
|
### Showing the 10 most recently updated pages
|
||||||
|
|
||||||
@ -52,7 +54,7 @@ You can use [api:RSSFeed] to easily create a feed showing your latest Page updat
|
|||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
..
|
..
|
||||||
@ -85,19 +87,19 @@ You can use [api:RSSFeed] to easily create a feed showing your latest Page updat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
### Rendering DataObjects in a RSSFeed
|
```
|
||||||
|
|
||||||
DataObjects can be rendered in the feed as well, however, since they aren't explicitly [api:SiteTree] subclasses we
|
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.
|
need to include a function `AbsoluteLink` to allow the RSS feed to link through to the item.
|
||||||
|
|
||||||
<div class="info">
|
[info]
|
||||||
If the items are all displayed on a single page you may simply hard code the link to point to a particular page.
|
If the items are all displayed on a single page you may simply hard code the link to point to a particular page.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
Take an example, we want to create an RSS feed of all the `Players` objects in our site. We make sure the `AbsoluteLink`
|
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.
|
method is defined and returns a string to the full website URL.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -113,9 +115,9 @@ method is defined and returns a string to the full website URL.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Then in our controller, we add a new action which returns a the XML list of `Players`.
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Page_Controller extends ContentController {
|
class Page_Controller extends ContentController {
|
||||||
@ -141,7 +143,7 @@ Then in our controller, we add a new action which returns a the XML list of `Pla
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
### 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 default template used for XML view is `framework/templates/RSSFeed.ss`. This template displays titles and links to
|
||||||
the object. To customise the XML produced use `setTemplate`.
|
the object. To customise the XML produced use `setTemplate`.
|
||||||
@ -150,7 +152,7 @@ Say from that last example we want to include the Players Team in the XML feed w
|
|||||||
|
|
||||||
**mysite/templates/PlayersRss.ss**
|
**mysite/templates/PlayersRss.ss**
|
||||||
|
|
||||||
:::xml
|
```xml
|
||||||
<?xml version="1.0"?>
|
<?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">
|
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
<channel>
|
<channel>
|
||||||
@ -168,11 +170,11 @@ Say from that last example we want to include the Players Team in the XML feed w
|
|||||||
</channel>
|
</channel>
|
||||||
</rss>
|
</rss>
|
||||||
|
|
||||||
`setTemplate` can then be used to tell RSSFeed to use that new template.
|
```
|
||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
|
|
||||||
public function players() {
|
public function players() {
|
||||||
$rss = new RSSFeed(
|
$rss = new RSSFeed(
|
||||||
@ -186,9 +188,9 @@ Say from that last example we want to include the Players Team in the XML feed w
|
|||||||
return $rss->outputToBrowser();
|
return $rss->outputToBrowser();
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="warning">
|
```
|
||||||
As we've added a new template (PlayersRss.ss) make sure you clear your SilverStripe cache.
|
As we've added a new template (PlayersRss.ss) make sure you clear your SilverStripe cache.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
|
---
|
||||||
title: Import CSV Data through a Controller
|
title: Import CSV Data through a Controller
|
||||||
|
summary: Data importing through the frontend
|
||||||
|
icon: upload
|
||||||
|
---
|
||||||
# Import CSV Data through a Controller
|
# Import CSV Data through a Controller
|
||||||
|
|
||||||
You can have more customised logic and interface feedback through a custom controller. Let's create a simple upload
|
You can have more customised 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
|
form (which is used for `MyDataObject` instances). You can access it through
|
||||||
`http://yoursite.com/MyController/?flush=all`.
|
`http://yoursite.com/MyController/?flush=all`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyController extends Controller {
|
class MyController extends Controller {
|
||||||
@ -63,7 +66,7 @@ form (which is used for `MyDataObject` instances). You can access it through
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
```
|
||||||
This interface is not secured, consider using [api:Permission::check()] to limit the controller to users with certain
|
This interface is not secured, consider using [api:Permission::check()] to limit the controller to users with certain
|
||||||
access rights.
|
access rights.
|
||||||
</div>
|
[/alert]
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
|
---
|
||||||
title: A custom CSVBulkLoader instance
|
title: A custom CSVBulkLoader instance
|
||||||
|
summary: Customise your data importing
|
||||||
|
icon: upload
|
||||||
|
---
|
||||||
# How to: 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
|
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.
|
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"
|
"SpielerNummer", "Name", "Geburtsdatum", "Gruppe"
|
||||||
11, "John Doe", 1982-05-12,"FC Bayern"
|
11, "John Doe", 1982-05-12,"FC Bayern"
|
||||||
12, "Jane Johnson", 1982-05-12,"FC Bayern"
|
12, "Jane Johnson", 1982-05-12,"FC Bayern"
|
||||||
13, "Jimmy Dole",,"Schalke 04"
|
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`.
|
information about the individual player and a relation set up for managing the `Team`.
|
||||||
|
|
||||||
**mysite/code/Player.php**.
|
**mysite/code/Player.php**.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -32,9 +36,9 @@ information about the individual player and a relation set up for managing the `
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
**mysite/code/FootballTeam.php**
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class FootballTeam extends DataObject {
|
class FootballTeam extends DataObject {
|
||||||
@ -48,7 +52,7 @@ information about the individual player and a relation set up for managing the `
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
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:
|
the custom importer are:
|
||||||
|
|
||||||
@ -62,7 +66,7 @@ Our final import looks like this.
|
|||||||
|
|
||||||
**mysite/code/PlayerCsvBulkLoader.php**
|
**mysite/code/PlayerCsvBulkLoader.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class PlayerCsvBulkLoader extends CsvBulkLoader {
|
class PlayerCsvBulkLoader extends CsvBulkLoader {
|
||||||
@ -97,7 +101,7 @@ Our final import looks like this.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## Related
|
```
|
||||||
|
|
||||||
* [api:CsvParser]
|
* [api:CsvParser]
|
||||||
* [api:ModelAdmin]
|
* [api:ModelAdmin]
|
@ -1,4 +1,8 @@
|
|||||||
title: Embed an RSS Feed
|
---
|
||||||
|
title: RSS Feed
|
||||||
|
summary: Output records from your database as an RSS Feed.
|
||||||
|
icon: rss
|
||||||
|
---
|
||||||
|
|
||||||
# Embed an RSS Feed
|
# Embed an RSS Feed
|
||||||
|
|
||||||
@ -9,7 +13,7 @@ First, we write the code to query the API feed.
|
|||||||
|
|
||||||
**mysite/code/Page.php**
|
**mysite/code/Page.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function getWellingtonWeather() {
|
public function getWellingtonWeather() {
|
||||||
$fetch = new RestfulService(
|
$fetch = new RestfulService(
|
||||||
'https://query.yahooapis.com/v1/public/yql'
|
'https://query.yahooapis.com/v1/public/yql'
|
||||||
@ -39,19 +43,19 @@ First, we write the code to query the API feed.
|
|||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
This will provide our `Page` template with a new `WellingtonWeather` variable (an [api:ArrayList]). Each item has a
|
```
|
||||||
single field `Description`.
|
single field `Description`.
|
||||||
|
|
||||||
**mysite/templates/Page.ss**
|
**mysite/templates/Page.ss**
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if WellingtonWeather %>
|
<% if WellingtonWeather %>
|
||||||
<% loop WellingtonWeather %>
|
<% loop WellingtonWeather %>
|
||||||
$Description
|
$Description
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
## Related
|
```
|
||||||
|
|
||||||
* [RestfulService Documentation](../restfulservice)
|
* [RestfulService Documentation](../restfulservice)
|
||||||
* [api:RestfulService]
|
* [api:RestfulService]
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: How To's
|
||||||
|
---
|
||||||
|
# How To's: Integration and Web Services
|
||||||
|
|
||||||
|
[CHILDREN]
|
@ -1,5 +1,6 @@
|
|||||||
|
---
|
||||||
summary: Integrate other web services within your application or make your SilverStripe data available.
|
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.
|
introduction: Integrate other web services within your application or make your SilverStripe data available.
|
||||||
title: Integration and Web Services
|
title: Integration and Web Services
|
||||||
|
---
|
||||||
[CHILDREN]
|
[CHILDREN]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Scaffolding with SearchContext
|
title: Scaffolding with SearchContext
|
||||||
summary: Configure the search form within ModelAdmin using the SearchContext class.
|
summary: Configure the search form within ModelAdmin using the SearchContext class.
|
||||||
|
icon: search
|
||||||
|
---
|
||||||
# SearchContext
|
# SearchContext
|
||||||
|
|
||||||
[api:SearchContext] manages searching of properties on one or more [api:DataObject] types, based on a given set of
|
[api:SearchContext] manages searching of properties on one or more [api:DataObject] types, based on a given set of
|
||||||
@ -10,15 +12,15 @@ 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
|
The default output of a [api:SearchContext] is either a [api:SQLQuery] object for further refinement, or a
|
||||||
[api:DataObject] instance.
|
[api:DataObject] instance.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
[api:SearchContext] is mainly used by [ModelAdmin](/developer_guides/customising_the_admin_interface/modeladmin).
|
[api:SearchContext] is mainly used by [ModelAdmin](/developer_guides/customising_the_admin_interface/modeladmin).
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Defining search-able fields on your DataObject.
|
Defining search-able fields on your DataObject.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyDataObject extends DataObject {
|
class MyDataObject extends DataObject {
|
||||||
@ -29,13 +31,13 @@ Defining search-able fields on your DataObject.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
## Customizing fields and filters
|
```
|
||||||
|
|
||||||
In this example we're defining three attributes on our MyDataObject subclass: `PublicProperty`, `HiddenProperty`
|
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
|
and `MyDate`. The attribute `HiddenProperty` should not be searchable, and `MyDate` should only search for dates
|
||||||
*after* the search entry (with a `GreaterThanFilter`).
|
*after* the search entry (with a `GreaterThanFilter`).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyDataObject extends DataObject {
|
class MyDataObject extends DataObject {
|
||||||
@ -64,19 +66,19 @@ and `MyDate`. The attribute `HiddenProperty` should not be searchable, and `MyDa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
See the [SearchFilter](../model/searchfilters) documentation for more information about filters to use such as the
|
See the [SearchFilter](../model/searchfilters) documentation for more information about filters to use such as the
|
||||||
`GreaterThanFilter`.
|
`GreaterThanFilter`.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
In case you need multiple contexts, consider name-spacing your request parameters by using `FieldList->namespace()` on
|
In case you need multiple contexts, consider name-spacing your request parameters by using `FieldList->namespace()` on
|
||||||
the `$fields` constructor parameter.
|
the `$fields` constructor parameter.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
### Generating a search form from the context
|
### Generating a search form from the context
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
..
|
..
|
||||||
@ -107,14 +109,14 @@ the `$fields` constructor parameter.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
### Pagination
|
```
|
||||||
|
|
||||||
For pagination records on multiple pages, you need to wrap the results in a
|
For pagination records on multiple pages, you need to wrap the results in a
|
||||||
`PaginatedList` object. This object is also passed the generated `SQLQuery`
|
`PaginatedList` object. This object is also passed the generated `SQLQuery`
|
||||||
in order to read page limit information. It is also passed the current
|
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.
|
`SS_HTTPRequest` object so it can read the current page from a GET var.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function getResults($searchCriteria = array()) {
|
public function getResults($searchCriteria = array()) {
|
||||||
$start = ($this->getRequest()->getVar('start')) ? (int)$this->getRequest()->getVar('start') : 0;
|
$start = ($this->getRequest()->getVar('start')) ? (int)$this->getRequest()->getVar('start') : 0;
|
||||||
$limit = 10;
|
$limit = 10;
|
||||||
@ -133,10 +135,10 @@ in order to read page limit information. It is also passed the current
|
|||||||
return $records;
|
return $records;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
notice that if you want to use this getResults function, you need to change the function doSearch for this one:
|
notice that if you want to use this getResults function, you need to change the function doSearch for this one:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function doSearch($data, $form) {
|
public function doSearch($data, $form) {
|
||||||
$context = singleton('MyDataObject')->getCustomSearchContext();
|
$context = singleton('MyDataObject')->getCustomSearchContext();
|
||||||
$results = $this->getResults($data);
|
$results = $this->getResults($data);
|
||||||
@ -145,7 +147,7 @@ notice that if you want to use this getResults function, you need to change the
|
|||||||
))->renderWith(array('Catalogo_results', 'Page'));
|
))->renderWith(array('Catalogo_results', 'Page'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
The change is in **$results = $this->getResults($data);**, because you are using a custom getResults function.
|
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
|
Another thing you cant forget is to check the name of the singleton you are using in your project. the example uses
|
||||||
@ -162,7 +164,7 @@ Results.PaginationSummary(4) defines how many pages the search will show in the
|
|||||||
**Next 1 2 *3* 4 5 … 558**
|
**Next 1 2 *3* 4 5 … 558**
|
||||||
|
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% if $Results %>
|
<% if $Results %>
|
||||||
<ul>
|
<ul>
|
||||||
<% loop $Results %>
|
<% loop $Results %>
|
||||||
@ -201,7 +203,7 @@ Results.PaginationSummary(4) defines how many pages the search will show in the
|
|||||||
</div>
|
</div>
|
||||||
<% end_if %>
|
<% end_if %>
|
||||||
|
|
||||||
|
```
|
||||||
## Available SearchFilters
|
## Available SearchFilters
|
||||||
|
|
||||||
See [api:SearchFilter] API Documentation
|
See [api:SearchFilter] API Documentation
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
|
---
|
||||||
title: Fulltext Search
|
title: Fulltext Search
|
||||||
summary: Fulltext search allows sophisticated searching on text content.
|
summary: Fulltext search allows sophisticated searching on text content.
|
||||||
|
icon: search
|
||||||
|
---
|
||||||
# FulltextSearchable
|
# FulltextSearchable
|
||||||
|
|
||||||
Fulltext search allows advanced search criteria for searching words within a text based data column. While basic
|
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
|
Fulltext search can be achieved using the built-in [api:MySQLDatabase] class a more powerful wrapper for Fulltext
|
||||||
search is provided through a module.
|
search is provided through a module.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
See the [FulltextSearch Module](https://github.com/silverstripe-labs/silverstripe-fulltextsearch/). This module provides
|
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
|
a high level wrapper for running advanced search services such as Solr, Lucene or Sphinx in the backend rather than
|
||||||
`MySQL` search.
|
`MySQL` search.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Adding Fulltext Support to MySQLDatabase
|
## Adding Fulltext Support to MySQLDatabase
|
||||||
|
|
||||||
@ -21,7 +23,7 @@ storage engine.
|
|||||||
|
|
||||||
You can do so by adding this static variable to your class definition:
|
You can do so by adding this static variable to your class definition:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyDataObject extends DataObject {
|
class MyDataObject extends DataObject {
|
||||||
@ -31,13 +33,13 @@ You can do so by adding this static variable to your class definition:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
The [api:FulltextSearchable] extension will add the correct `Fulltext` indexes to the data model.
|
```
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
The [api:SearchForm] and [api:FulltextSearchable] API's are currently hard coded to be specific to `Page` and `File`
|
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
|
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.
|
default site search, have a look at those extensions and modify as required.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
### Fulltext Filter
|
### Fulltext Filter
|
||||||
|
|
||||||
@ -46,7 +48,7 @@ SilverStripe provides a [api:FulltextFilter] which you can use to perform custom
|
|||||||
|
|
||||||
Example DataObject:
|
Example DataObject:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class SearchableDataObject extends DataObject {
|
class SearchableDataObject extends DataObject {
|
||||||
|
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
@ -68,12 +70,12 @@ Example DataObject:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Performing the search:
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
SearchableDataObject::get()->filter('SearchFields:fulltext', 'search term');
|
SearchableDataObject::get()->filter('SearchFields:fulltext', 'search term');
|
||||||
|
|
||||||
If your search index is a single field size, then you may also specify the search filter by the name of the
|
```
|
||||||
field instead of the index.
|
field instead of the index.
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
---
|
||||||
title: Search
|
title: Search
|
||||||
summary: Provide your users with advanced search functionality.
|
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.
|
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
|
See the [Site Search Tutorial](/tutorials/site_search) for a detailed walk through of adding basic Search to your
|
||||||
website.
|
website.
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: i18n
|
title: i18n
|
||||||
summary: Display templates and PHP code in different languages based on the preferences of your website users.
|
summary: Display templates and PHP code in different languages based on the preferences of your website users.
|
||||||
|
---
|
||||||
# i18n
|
# i18n
|
||||||
|
|
||||||
The i18n class (short for "internationalization") in SilverStripe enables you to display templates and PHP code in
|
The i18n class (short for "internationalization") in SilverStripe enables you to display templates and PHP code in
|
||||||
@ -27,12 +28,12 @@ The i18n class is enabled by default.
|
|||||||
To set the locale you just need to call [api:i18n::set_locale()] passing, as a parameter, the name of the locale that
|
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.
|
you want to set.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// mysite/_config.php
|
// mysite/_config.php
|
||||||
i18n::set_locale('de_DE'); // Setting the locale to German (Germany)
|
i18n::set_locale('de_DE'); // Setting the locale to German (Germany)
|
||||||
i18n::set_locale('ca_AD'); // Setting to Catalan (Andorra)
|
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
|
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)
|
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.
|
for a complete listing of available locales.
|
||||||
@ -45,14 +46,14 @@ As you set the locale you can also get the current value, just by calling [api:i
|
|||||||
|
|
||||||
To let browsers know which language they're displaying a document in, you can declare a language in your template.
|
To let browsers know which language they're displaying a document in, you can declare a language in your template.
|
||||||
|
|
||||||
:::html
|
```html
|
||||||
//'Page.ss' (HTML)
|
//'Page.ss' (HTML)
|
||||||
<html lang="$ContentLocale">
|
<html lang="$ContentLocale">
|
||||||
|
|
||||||
//'Page.ss' (XHTML)
|
//'Page.ss' (XHTML)
|
||||||
<html lang="$ContentLocale" xml:lang="$ContentLocale" xmlns="http://www.w3.org/1999/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/).
|
(meta tags, HTTP headers), explained in this [w3.org article](http://www.w3.org/International/tutorials/language-decl/).
|
||||||
|
|
||||||
@ -60,19 +61,19 @@ You can also set the [script direction](http://www.w3.org/International/question
|
|||||||
which is determined by the current locale, in order to indicate the preferred flow of characters
|
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.
|
and default alignment of paragraphs and tables to browsers.
|
||||||
|
|
||||||
:::html
|
```html
|
||||||
<html lang="$ContentLocale" dir="$i18nScriptDirection">
|
<html lang="$ContentLocale" dir="$i18nScriptDirection">
|
||||||
|
|
||||||
### 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.
|
to write your own logic for any frontend output.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Config::inst()->update('i18n', 'date_format', 'dd.MM.YYYY');
|
Config::inst()->update('i18n', 'date_format', 'dd.MM.YYYY');
|
||||||
Config::inst()->update('i18n', 'time_format', 'HH:mm');
|
Config::inst()->update('i18n', 'time_format', 'HH:mm');
|
||||||
|
|
||||||
Most localization routines in SilverStripe use the [Zend_Date API](http://framework.zend.com/manual/1.12/en/zend.date.overview.html).
|
```
|
||||||
This means all formats are defined in
|
This means all formats are defined in
|
||||||
[ISO date format](http://framework.zend.com/manual/1.12/en/zend.date.constants.html),
|
[ISO date format](http://framework.zend.com/manual/1.12/en/zend.date.constants.html),
|
||||||
not PHP's built-in [date()](http://nz.php.net/manual/en/function.date.php).
|
not PHP's built-in [date()](http://nz.php.net/manual/en/function.date.php).
|
||||||
@ -84,22 +85,22 @@ They can be accessed via the `i18n.common_languages` and `i18n.common_locales` [
|
|||||||
|
|
||||||
In order to add a value, add the following to your `config.yml`:
|
In order to add a value, add the following to your `config.yml`:
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
i18n:
|
i18n:
|
||||||
common_locales:
|
common_locales:
|
||||||
de_CGN:
|
de_CGN:
|
||||||
name: German (Cologne)
|
name: German (Cologne)
|
||||||
native: Kölsch
|
native: Kölsch
|
||||||
|
|
||||||
Similarly, to change an existing language label, you can overwrite one of these keys:
|
```
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
i18n:
|
i18n:
|
||||||
common_locales:
|
common_locales:
|
||||||
en_NZ:
|
en_NZ:
|
||||||
native: Niu Zillund
|
native: Niu Zillund
|
||||||
|
|
||||||
### i18n in URLs
|
```
|
||||||
|
|
||||||
By default, URLs for pages in SilverStripe (the `SiteTree->URLSegment` property)
|
By default, URLs for pages in SilverStripe (the `SiteTree->URLSegment` property)
|
||||||
are automatically reduced to the allowed allowed subset of ASCII characters.
|
are automatically reduced to the allowed allowed subset of ASCII characters.
|
||||||
@ -118,13 +119,13 @@ Please refer to [W3C: Introduction to IDN and IRI](http://www.w3.org/Internation
|
|||||||
|
|
||||||
Date- and time related form fields support i18n ([api:DateField], [api:TimeField], [api:DatetimeField]).
|
Date- and time related form fields support i18n ([api:DateField], [api:TimeField], [api:DatetimeField]).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
i18n::set_locale('ca_AD');
|
i18n::set_locale('ca_AD');
|
||||||
$field = new DateField(); // will automatically set date format defaults for 'ca_AD'
|
$field = new DateField(); // will automatically set date format defaults for 'ca_AD'
|
||||||
$field->setLocale('de_DE'); // will not update the date formats
|
$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"
|
$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](/developer_guides/configuration).
|
and `TimeField.default_config` [configuration arrays](/developer_guides/configuration).
|
||||||
If no 'locale' default is set on the field, [api:i18n::get_locale()] will be used.
|
If no 'locale' default is set on the field, [api:i18n::get_locale()] will be used.
|
||||||
|
|
||||||
@ -136,25 +137,25 @@ The [api:DateField] API can be enhanced by JavaScript, and comes with
|
|||||||
The field tries to translate the date formats and locales into a format compatible with jQuery UI
|
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()]).
|
(see [api:DateField_View_JQuery::$locale_map_] and [api:DateField_View_JQuery::convert_iso_to_jquery_format()]).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$field = new DateField();
|
$field = new DateField();
|
||||||
$field->setLocale('de_AT'); // set Austrian/German locale
|
$field->setLocale('de_AT'); // set Austrian/German locale
|
||||||
$field->setConfig('showcalendar', true);
|
$field->setConfig('showcalendar', true);
|
||||||
$field->setConfig('jslocale', 'de'); // jQuery UI only has a generic German localization
|
$field->setConfig('jslocale', 'de'); // jQuery UI only has a generic German localization
|
||||||
$field->setConfig('dateformat', 'dd. MMMM YYYY'); // will be transformed to 'dd. MM yy' for jQuery
|
$field->setConfig('dateformat', 'dd. MMMM YYYY'); // will be transformed to 'dd. MM yy' for jQuery
|
||||||
|
|
||||||
## Translating text
|
```
|
||||||
|
|
||||||
Adapting a module to make it localizable is easy with SilverStripe. You just need to avoid hardcoding strings that are
|
Adapting a module to make it localizable is easy with SilverStripe. You just need to avoid hardcoding strings that are
|
||||||
language-dependent and use a translator function call instead.
|
language-dependent and use a translator function call instead.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// without i18n
|
// without i18n
|
||||||
echo "This is a string";
|
echo "This is a string";
|
||||||
// with i18n
|
// with i18n
|
||||||
echo _t("Namespace.Entity","This is a string");
|
echo _t("Namespace.Entity","This is a string");
|
||||||
|
|
||||||
|
```
|
||||||
All strings passed through the `_t()` function will be collected in a separate language table (see [Collecting text](#collecting-text)), which is the starting point for translations.
|
All strings passed through the `_t()` function will be collected in a separate language table (see [Collecting text](#collecting-text)), which is the starting point for translations.
|
||||||
|
|
||||||
### The _t() function
|
### The _t() function
|
||||||
@ -171,7 +172,7 @@ to the translator.
|
|||||||
|
|
||||||
#### Usage in PHP Files
|
#### Usage in PHP Files
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
|
|
||||||
// Simple string translation
|
// Simple string translation
|
||||||
_t('LeftAndMain.FILESIMAGES','Files & Images');
|
_t('LeftAndMain.FILESIMAGES','Files & Images');
|
||||||
@ -186,11 +187,11 @@ to the translator.
|
|||||||
array('value' => $itemRestored)
|
array('value' => $itemRestored)
|
||||||
);
|
);
|
||||||
|
|
||||||
#### Usage in Template Files
|
```
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
[hint]
|
||||||
The preferred template syntax has changed somewhat since [version 2.x](http://doc.silverstripe.org/framework/en/2.4/topics/i18n#usage-2).
|
The preferred template syntax has changed somewhat since [version 2.x](http://doc.silverstripe.org/framework/en/2.4/topics/i18n#usage-2).
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
In `.ss` template files, instead of `_t(params)` the syntax `<%t params %>` is used. The syntax for passing parameters to the function is quite different to
|
In `.ss` template files, instead of `_t(params)` the syntax `<%t params %>` is used. The syntax for passing parameters to the function is quite different to
|
||||||
the PHP version of the function.
|
the PHP version of the function.
|
||||||
@ -199,7 +200,7 @@ the PHP version of the function.
|
|||||||
* The original language string and the natural language comment parameters are separated by ` on `.
|
* The original language string and the natural language comment parameters are separated by ` on `.
|
||||||
* The final parameter (which is an array in PHP) is passed as a space separated list of key/value pairs.
|
* The final parameter (which is an array in PHP) is passed as a space separated list of key/value pairs.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
// Simple string translation
|
// Simple string translation
|
||||||
<%t Namespace.Entity "String to translate" %>
|
<%t Namespace.Entity "String to translate" %>
|
||||||
|
|
||||||
@ -209,19 +210,19 @@ the PHP version of the function.
|
|||||||
// Using injection to add variables into the translated strings (note that $Name and $Greeting must be available in the current template scope).
|
// 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 %>
|
<%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
|
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.
|
otherwise it won't pick up locale changes.
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<% cached 'MyIdentifier', $CurrentLocale %>
|
<% cached 'MyIdentifier', $CurrentLocale %>
|
||||||
<% loop $Students %>
|
<% loop $Students %>
|
||||||
$Name
|
$Name
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
|
|
||||||
## 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`
|
||||||
|
|
||||||
@ -231,9 +232,9 @@ underscore function, and tell you about the created files and any possible entit
|
|||||||
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`
|
`http://localhost/dev/tasks/i18nTextCollectorTask/?module=cms`
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
[hint]
|
||||||
You'll need to install PHPUnit to run the text collector (see [testing-guide](/developer_guides/testing)).
|
You'll need to install PHPUnit to run the text collector (see [testing-guide](/developer_guides/testing)).
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
## Module Priority
|
## Module Priority
|
||||||
|
|
||||||
@ -252,16 +253,18 @@ This default order is configured in `framework/_config/i18n.yml`. This file spe
|
|||||||
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
|
Name: customi18n
|
||||||
Before: 'defaulti18n'
|
Before: 'defaulti18n'
|
||||||
---
|
```
|
||||||
|
```
|
||||||
i18n:
|
i18n:
|
||||||
module_priority:
|
module_priority:
|
||||||
- module1
|
- module1
|
||||||
- module2
|
- module2
|
||||||
- module3
|
- module3
|
||||||
|
|
||||||
The config option being set is `i18n.module_priority`, and it is a list of module names.
|
```
|
||||||
|
|
||||||
There are a few special cases:
|
There are a few special cases:
|
||||||
|
|
||||||
@ -279,32 +282,34 @@ By default, SilverStripe 3.x uses a YAML format (through the [Zend_Translate_Rai
|
|||||||
|
|
||||||
Example: framework/lang/en.yml (extract)
|
Example: framework/lang/en.yml (extract)
|
||||||
|
|
||||||
|
```
|
||||||
en:
|
en:
|
||||||
ImageUploader:
|
ImageUploader:
|
||||||
Attach: 'Attach %s'
|
Attach: 'Attach %s'
|
||||||
UploadField:
|
UploadField:
|
||||||
NOTEADDFILES: 'You can add files once you have saved for the first time.'
|
NOTEADDFILES: 'You can add files once you have saved for the first time.'
|
||||||
|
|
||||||
Translation table: framework/lang/de.yml (extract)
|
```
|
||||||
|
|
||||||
|
```
|
||||||
de:
|
de:
|
||||||
ImageUploader:
|
ImageUploader:
|
||||||
ATTACH: '%s anhängen'
|
ATTACH: '%s anhängen'
|
||||||
UploadField:
|
UploadField:
|
||||||
NOTEADDFILES: 'Sie können Dateien hinzufügen sobald Sie das erste mal gespeichert haben'
|
NOTEADDFILES: 'Sie können Dateien hinzufügen sobald Sie das erste mal gespeichert haben'
|
||||||
|
|
||||||
Note that translations are cached across requests.
|
```
|
||||||
The cache can be cleared through the `?flush=1` query parameter,
|
The cache can be cleared through the `?flush=1` query parameter,
|
||||||
or explicitly through `Zend_Translate::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL)`.
|
or explicitly through `Zend_Translate::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL)`.
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
[hint]
|
||||||
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>
|
[/hint]
|
||||||
|
|
||||||
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
|
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
|
||||||
in your `mysite/_config.php`:
|
in your `mysite/_config.php`:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
i18n::register_translator(
|
i18n::register_translator(
|
||||||
new Zend_Translate(array(
|
new Zend_Translate(array(
|
||||||
'adapter' => 'i18nSSLegacyAdapter',
|
'adapter' => 'i18nSSLegacyAdapter',
|
||||||
@ -315,7 +320,7 @@ in your `mysite/_config.php`:
|
|||||||
9 // priority lower than standard translator
|
9 // priority lower than standard translator
|
||||||
);
|
);
|
||||||
|
|
||||||
## Javascript Usage
|
```
|
||||||
|
|
||||||
The i18n system in JavaScript is similar to its PHP equivalent.
|
The i18n system in JavaScript is similar to its PHP equivalent.
|
||||||
Languages are typically stored in `<my-module-dir>/javascript/lang`.
|
Languages are typically stored in `<my-module-dir>/javascript/lang`.
|
||||||
@ -329,10 +334,10 @@ the browser: The current locale, and the default locale as a fallback.
|
|||||||
The `Requirements` class has a special method to determine these includes:
|
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.
|
Just point it to a directory instead of a file, and the class will figure out the includes.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Requirements::add_i18n_javascript('<my-module-dir>/javascript/lang');
|
Requirements::add_i18n_javascript('<my-module-dir>/javascript/lang');
|
||||||
|
|
||||||
|
```
|
||||||
### Translation Tables in JavaScript
|
### Translation Tables in JavaScript
|
||||||
|
|
||||||
Translation tables are automatically included as required, depending on the configured locale in `i18n::get_locale()`.
|
Translation tables are automatically included as required, depending on the configured locale in `i18n::get_locale()`.
|
||||||
@ -340,7 +345,7 @@ As a fallback for partially translated tables we always include the master table
|
|||||||
|
|
||||||
Master Table (`<my-module-dir>/javascript/lang/en.js`)
|
Master Table (`<my-module-dir>/javascript/lang/en.js`)
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
|
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
|
||||||
console.error('Class ss.i18n not defined');
|
console.error('Class ss.i18n not defined');
|
||||||
} else {
|
} else {
|
||||||
@ -349,34 +354,32 @@ Master Table (`<my-module-dir>/javascript/lang/en.js`)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
Example Translation Table (`<my-module-dir>/javascript/lang/de.js`)
|
Example Translation Table (`<my-module-dir>/javascript/lang/de.js`)
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
ss.i18n.addDictionary('de', {
|
ss.i18n.addDictionary('de', {
|
||||||
'MYMODULE.MYENTITY' : "Artikel wirklich löschen?"
|
'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),
|
[build task](https://github.com/silverstripe/silverstripe-buildtools/blob/master/src/GenerateJavascriptI18nTask.php),
|
||||||
with the actual source files in a JSON
|
with the actual source files in a JSON
|
||||||
format which can be processed more easily by external translation providers (see `javascript/lang/src`).
|
format which can be processed more easily by external translation providers (see `javascript/lang/src`).
|
||||||
|
|
||||||
### Basic Usage
|
### Basic Usage
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
alert(ss.i18n._t('MYMODULE.MYENTITY'));
|
alert(ss.i18n._t('MYMODULE.MYENTITY'));
|
||||||
|
|
||||||
|
```
|
||||||
### Advanced Use
|
### Advanced Use
|
||||||
|
|
||||||
The `ss.i18n` object contain a couple functions to help and replace dynamic variable from within a string.
|
The `ss.i18n` object contain a couple functions to help and replace dynamic variable from within a string.
|
||||||
|
|
||||||
#### Legacy sequential replacement with sprintf()
|
#### Legacy sequential replacement with sprintf()
|
||||||
|
|
||||||
`sprintf()` will substitute occurencies of `%s` in the main string with each of the following arguments passed to the function. The substitution is done sequentially.
|
```js
|
||||||
|
|
||||||
:::js
|
|
||||||
// MYMODULE.MYENTITY contains "Really delete %s articles by %s?"
|
// MYMODULE.MYENTITY contains "Really delete %s articles by %s?"
|
||||||
alert(ss.i18n.sprintf(
|
alert(ss.i18n.sprintf(
|
||||||
ss.i18n._t('MYMODULE.MYENTITY'),
|
ss.i18n._t('MYMODULE.MYENTITY'),
|
||||||
@ -385,12 +388,10 @@ The `ss.i18n` object contain a couple functions to help and replace dynamic vari
|
|||||||
));
|
));
|
||||||
// Displays: "Really delete 42 articles by Douglas Adams?"
|
// Displays: "Really delete 42 articles by Douglas Adams?"
|
||||||
|
|
||||||
|
```
|
||||||
#### Variable injection with inject()
|
#### Variable injection with inject()
|
||||||
|
|
||||||
`inject()` will substitute variables in the main string like `{myVar}` by the keys in the object passed as second argument. Each variable can be in any order and appear multiple times.
|
```js
|
||||||
|
|
||||||
:::js
|
|
||||||
// MYMODULE.MYENTITY contains "Really delete {count} articles by {author}?"
|
// MYMODULE.MYENTITY contains "Really delete {count} articles by {author}?"
|
||||||
alert(ss.i18n.inject(
|
alert(ss.i18n.inject(
|
||||||
ss.i18n._t('MYMODULE.MYENTITY'),
|
ss.i18n._t('MYMODULE.MYENTITY'),
|
||||||
@ -398,7 +399,7 @@ The `ss.i18n` object contain a couple functions to help and replace dynamic vari
|
|||||||
));
|
));
|
||||||
// Displays: "Really delete 42 articles by Douglas Adams?"
|
// Displays: "Really delete 42 articles by Douglas Adams?"
|
||||||
|
|
||||||
|
```
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
* No detecting/conversion of character encodings (we rely fully on UTF-8)
|
* No detecting/conversion of character encodings (we rely fully on UTF-8)
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: File management
|
||||||
summary: Learn how to work with File and Image records
|
summary: Learn how to work with File and Image records
|
||||||
|
icon: file-signature
|
||||||
|
---
|
||||||
# File Management
|
# File Management
|
||||||
|
|
||||||
## Files, Images and Folders as database records
|
## Files, Images and Folders as database records
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
---
|
||||||
summary: Learn how to crop and resize images in templates and PHP code
|
summary: Learn how to crop and resize images in templates and PHP code
|
||||||
|
---
|
||||||
# Image
|
# Image
|
||||||
|
|
||||||
Represents an image object through the [api:Image] class, inheriting all base functionality from the [api:File] class with extra functionality including resizing.
|
Represents an image object through the [api:Image] class, inheriting all base functionality from the [api:File] class with extra functionality including resizing.
|
||||||
@ -30,7 +31,7 @@ images are preserved (meaning images are not stretched).
|
|||||||
|
|
||||||
Here are some examples, assuming the `$Image` object has dimensions of 200x100px:
|
Here are some examples, assuming the `$Image` object has dimensions of 200x100px:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
// Scaling functions
|
// Scaling functions
|
||||||
$Image.ScaleWidth(150) // Returns a 150x75px image
|
$Image.ScaleWidth(150) // Returns a 150x75px image
|
||||||
$Image.ScaleMaxWidth(100) // Returns a 100x50px image (like ScaleWidth but prevents up-sampling)
|
$Image.ScaleMaxWidth(100) // Returns a 100x50px image (like ScaleWidth but prevents up-sampling)
|
||||||
@ -59,12 +60,12 @@ Here are some examples, assuming the `$Image` object has dimensions of 200x100px
|
|||||||
$Image.Link // Returns relative URL path to image
|
$Image.Link // Returns relative URL path to image
|
||||||
$Image.AbsoluteLink // Returns absolute URL path to image
|
$Image.AbsoluteLink // Returns absolute URL path to image
|
||||||
|
|
||||||
Image methods are chainable. Example:
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<body style="background-image:url($Image.ScaleWidth(800).CropHeight(800).Link)">
|
<body style="background-image:url($Image.ScaleWidth(800).CropHeight(800).Link)">
|
||||||
|
|
||||||
### Padded Image Resize
|
```
|
||||||
|
|
||||||
The Pad method allows you to resize an image with existing ratio and will
|
The Pad method allows you to resize an image with existing ratio and will
|
||||||
pad any surplus space. You can specify the color of the padding using a hex code such as FFFFFF or 000000.
|
pad any surplus space. You can specify the color of the padding using a hex code such as FFFFFF or 000000.
|
||||||
@ -72,12 +73,12 @@ pad any surplus space. You can specify the color of the padding using a hex code
|
|||||||
You can also specify a level of transparency to apply to the padding color in a fourth param. This will only effect
|
You can also specify a level of transparency to apply to the padding color in a fourth param. This will only effect
|
||||||
png images.
|
png images.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$Image.Pad(80, 80, FFFFFF, 50) // white padding with 50% transparency
|
$Image.Pad(80, 80, FFFFFF, 50) // white padding with 50% transparency
|
||||||
$Image.Pad(80, 80, FFFFFF, 100) // white padding with 100% transparency
|
$Image.Pad(80, 80, FFFFFF, 100) // white padding with 100% transparency
|
||||||
$Image.Pad(80, 80, FFFFFF) // white padding with no transparency
|
$Image.Pad(80, 80, FFFFFF) // white padding with no transparency
|
||||||
|
|
||||||
### Manipulating images in PHP
|
```
|
||||||
|
|
||||||
The image manipulation functions can be used in your code with the same names, example: `$image->Fill(150,150)`.
|
The image manipulation functions can be used in your code with the same names, example: `$image->Fill(150,150)`.
|
||||||
|
|
||||||
@ -89,46 +90,12 @@ Please refer to the [api:Image] API documentation for specific functions.
|
|||||||
|
|
||||||
You can also create your own functions by extending the image class, for example
|
You can also create your own functions by extending the image class, for example
|
||||||
|
|
||||||
:::php
|
```yml
|
||||||
class MyImage extends DataExtension {
|
|
||||||
|
|
||||||
public function Landscape() {
|
|
||||||
return $this->owner->getWidth() > $this->owner->getHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function Portrait() {
|
|
||||||
return $this->owner->getWidth() < $this->owner->getHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function PerfectSquare() {
|
|
||||||
return $this->owner->getFormattedImage('PerfectSquare');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function generatePerfectSquare(Image_Backend $backend) {
|
|
||||||
return $backend->croppedResize(100,100);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function Exif(){
|
|
||||||
//http://www.v-nessa.net/2010/08/02/using-php-to-extract-image-exif-data
|
|
||||||
$image = $this->owner->AbsoluteLink();
|
|
||||||
$d=new ArrayList();
|
|
||||||
$exif = exif_read_data($image, 0, true);
|
|
||||||
foreach ($exif as $key => $section) {
|
|
||||||
$a=new ArrayList();
|
|
||||||
foreach ($section as $name => $val)
|
|
||||||
$a->push(new ArrayData(array("Title"=>$name,"Content"=>$val)));
|
|
||||||
$d->push(new ArrayData(array("Title"=>strtolower($key),"Content"=>$a)));
|
|
||||||
}
|
|
||||||
return $d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:::yml
|
|
||||||
Image:
|
Image:
|
||||||
extensions:
|
extensions:
|
||||||
- MyImage
|
- MyImage
|
||||||
|
|
||||||
### Form Upload
|
```
|
||||||
|
|
||||||
For usage on a website form, see [api:FileField].
|
For usage on a website form, see [api:FileField].
|
||||||
If you want to upload images within the CMS, see [api:UploadField].
|
If you want to upload images within the CMS, see [api:UploadField].
|
||||||
@ -138,25 +105,25 @@ If you want to upload images within the CMS, see [api:UploadField].
|
|||||||
To adjust the quality of the generated images when they are resized add the
|
To adjust the quality of the generated images when they are resized add the
|
||||||
following to your mysite/config/config.yml file:
|
following to your mysite/config/config.yml file:
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
GDBackend:
|
GDBackend:
|
||||||
default_quality: 90
|
default_quality: 90
|
||||||
# or
|
# or
|
||||||
ImagickBackend:
|
ImagickBackend:
|
||||||
default_quality: 90
|
default_quality: 90
|
||||||
|
|
||||||
The default value is 75.
|
```
|
||||||
|
|
||||||
By default SilverStripe image functions will not resample an image if no
|
By default SilverStripe image functions will not resample an image if no
|
||||||
cropping or resizing is taking place. You can tell SilverStripe to always to
|
cropping or resizing is taking place. You can tell SilverStripe to always to
|
||||||
always produce resampled output by adding this to your
|
always produce resampled output by adding this to your
|
||||||
mysite/config/config.yml file:
|
mysite/config/config.yml file:
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Image:
|
Image:
|
||||||
force_resample: true
|
force_resample: true
|
||||||
|
|
||||||
If you are intending to resample images with SilverStripe it is good practice
|
```
|
||||||
to upload high quality (minimal compression) images as these will produce
|
to upload high quality (minimal compression) images as these will produce
|
||||||
better results when resampled. Very high resolution images may cause GD to
|
better results when resampled. Very high resolution images may cause GD to
|
||||||
crash so a good size for website images is around 2000px on the longest edge.
|
crash so a good size for website images is around 2000px on the longest edge.
|
||||||
@ -170,11 +137,12 @@ and whenever you upload or modify an Image through SilverStripe.
|
|||||||
If you encounter problems with images not appearing, or have mysteriously
|
If you encounter problems with images not appearing, or have mysteriously
|
||||||
disappeared, you can try manually flushing the image cache.
|
disappeared, you can try manually flushing the image cache.
|
||||||
|
|
||||||
|
```
|
||||||
http://localhost/dev/tasks/RegenerateCachedImagesTask
|
http://localhost/dev/tasks/RegenerateCachedImagesTask
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
```
|
||||||
This task was renamed to `RegenerateCachedImagesTask` (originally `FlushGeneratedImagesTask`) circa SilverStripe 3.2.
|
This task was renamed to `RegenerateCachedImagesTask` (originally `FlushGeneratedImagesTask`) circa SilverStripe 3.2.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
[api:Image]
|
[api:Image]
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
---
|
||||||
title: Files
|
title: Files
|
||||||
summary: Upload, manage and manipulate files and images.
|
summary: Upload, manage and manipulate files and images.
|
||||||
introduction: Upload, manage and manipulate files and images.
|
introduction: Upload, manage and manipulate files and images.
|
||||||
|
icon: folder-open
|
||||||
|
---
|
||||||
[CHILDREN]
|
[CHILDREN]
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: ModelAdmin
|
title: ModelAdmin
|
||||||
summary: Create admin UI's for managing your data records.
|
summary: Create admin UI's for managing your data records.
|
||||||
|
---
|
||||||
# ModelAdmin
|
# ModelAdmin
|
||||||
|
|
||||||
[api:ModelAdmin] provides a simple way to utilize the SilverStripe Admin UI with your own data models. It can create
|
[api:ModelAdmin] provides a simple way to utilize the SilverStripe Admin UI with your own data models. It can create
|
||||||
@ -9,17 +10,17 @@ searchables list and edit views of [api:DataObject] subclasses, and even provide
|
|||||||
It uses the framework's knowledge about the model to provide sensible defaults, allowing you to get started in a couple
|
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.
|
of lines of code, while still providing a solid base for customization.
|
||||||
|
|
||||||
<div class="info" markdown="1">
|
[info]
|
||||||
The interface is mainly powered by the [api:GridField] class ([documentation](../forms/field_types/gridfield)), which can
|
The interface is mainly powered by the [api:GridField] class ([documentation](../forms/field_types/gridfield)), which can
|
||||||
also be used in other areas of your application.
|
also be used in other areas of your application.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
Let's assume we want to manage a simple product listing as a sample data model: A product can have a name, price, and
|
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.
|
a category.
|
||||||
|
|
||||||
**mysite/code/Product.php**
|
**mysite/code/Product.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Product extends DataObject {
|
class Product extends DataObject {
|
||||||
@ -35,9 +36,9 @@ a category.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
**mysite/code/Category.php**
|
```
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Category extends DataObject {
|
class Category extends DataObject {
|
||||||
@ -51,14 +52,14 @@ a category.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
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.
|
We'll name it `MyAdmin`, but the class name can be anything you want.
|
||||||
|
|
||||||
**mysite/code/MyAdmin.php**
|
**mysite/code/MyAdmin.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyAdmin extends ModelAdmin {
|
class MyAdmin extends ModelAdmin {
|
||||||
@ -73,12 +74,12 @@ We'll name it `MyAdmin`, but the class name can be anything you want.
|
|||||||
private static $menu_title = 'My Product Admin';
|
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.
|
users will be able to upload and manage `Product` and `Category` instances through http://yoursite.com/admin/products.
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
[alert]
|
||||||
After defining these classes, make sure you have rebuilt your SilverStripe database and flushed your cache.
|
After defining these classes, make sure you have rebuilt your SilverStripe database and flushed your cache.
|
||||||
</div>
|
[/alert]
|
||||||
|
|
||||||
## Permissions
|
## Permissions
|
||||||
|
|
||||||
@ -86,9 +87,9 @@ Each new `ModelAdmin` subclass creates its' own [permission code](../security),
|
|||||||
`CMS_ACCESS_MyAdmin`. Users with access to the Admin UI will need to have this permission assigned through
|
`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.
|
`admin/security/` or have the `ADMIN` permission code in order to gain access to the controller.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
For more information on the security and permission system see the [Security Documentation](../security)
|
For more information on the security and permission system see the [Security Documentation](../security)
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
The [api:DataObject] API has more granular permission control, which is enforced in [api:ModelAdmin] by default.
|
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
|
Available checks are `canEdit()`, `canCreate()`, `canView()` and `canDelete()`. Models check for administrator
|
||||||
@ -96,7 +97,7 @@ permissions by default. For most cases, less restrictive checks make sense, e.g.
|
|||||||
|
|
||||||
**mysite/code/Category.php**
|
**mysite/code/Category.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Category extends DataObject {
|
class Category extends DataObject {
|
||||||
@ -117,7 +118,7 @@ permissions by default. For most cases, less restrictive checks make sense, e.g.
|
|||||||
return Permission::check('CMS_ACCESS_MyAdmin', 'any', $member);
|
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
|
[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
|
searched results. Every [api:DataObject] can have its own context, based on the fields which should be searchable. The
|
||||||
@ -129,7 +130,7 @@ class (see [SearchContext](../search/searchcontext) docs for details).
|
|||||||
|
|
||||||
**mysite/code/Product.php**
|
**mysite/code/Product.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Product extends DataObject {
|
class Product extends DataObject {
|
||||||
@ -140,9 +141,9 @@ class (see [SearchContext](../search/searchcontext) docs for details).
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="hint" markdown="1">
|
```
|
||||||
[SearchContext](../search/searchcontext) documentation has more information on providing the search functionality.
|
[SearchContext](../search/searchcontext) documentation has more information on providing the search functionality.
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
## Displaying Results
|
## Displaying Results
|
||||||
|
|
||||||
@ -152,7 +153,7 @@ model class, where you can add or remove columns. To change the title, use [api:
|
|||||||
|
|
||||||
**mysite/code/Product.php**
|
**mysite/code/Product.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Product extends DataObject {
|
class Product extends DataObject {
|
||||||
@ -167,7 +168,7 @@ model class, where you can add or remove columns. To change the title, use [api:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
form. If no search parameters are given, the results will show every record. Results are a [api:DataList] instance, so
|
||||||
can be customised by additional SQL filters, joins.
|
can be customised by additional SQL filters, joins.
|
||||||
|
|
||||||
@ -175,7 +176,7 @@ For example, we might want to exclude all products without prices in our sample
|
|||||||
|
|
||||||
**mysite/code/MyAdmin.php**
|
**mysite/code/MyAdmin.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyAdmin extends ModelAdmin {
|
class MyAdmin extends ModelAdmin {
|
||||||
@ -192,12 +193,12 @@ For example, we might want to exclude all products without prices in our sample
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
You can also customise 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).
|
checkbox which limits search results to expensive products (over $100).
|
||||||
|
|
||||||
**mysite/code/MyAdmin.php**
|
**mysite/code/MyAdmin.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyAdmin extends ModelAdmin {
|
class MyAdmin extends ModelAdmin {
|
||||||
@ -225,12 +226,12 @@ checkbox which limits search results to expensive products (over $100).
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
To alter how the results are displayed (via [api:GridField]), you can also overload the `getEditForm()` method. For
|
```
|
||||||
example, to add a new component.
|
example, to add a new component.
|
||||||
|
|
||||||
**mysite/code/MyAdmin.php**
|
**mysite/code/MyAdmin.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyAdmin extends ModelAdmin {
|
class MyAdmin extends ModelAdmin {
|
||||||
@ -257,12 +258,12 @@ example, to add a new component.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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`:
|
to only one specific `GridField`:
|
||||||
|
|
||||||
**mysite/code/MyAdmin.php**
|
**mysite/code/MyAdmin.php**
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyAdmin extends ModelAdmin {
|
class MyAdmin extends ModelAdmin {
|
||||||
@ -286,7 +287,7 @@ to only one specific `GridField`:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## Data Import
|
```
|
||||||
|
|
||||||
The `ModelAdmin` class provides import of CSV files through the [api:CsvBulkLoader] API. which has support for column
|
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
|
mapping, updating existing records, and identifying relationships - so its a powerful tool to get your data into a
|
||||||
@ -302,7 +303,7 @@ This is handled through the [api:GridFieldExportButton] component.
|
|||||||
|
|
||||||
To customise the exported columns, create a new method called `getExportFields` in your `ModelAdmin`:
|
To customise the exported columns, create a new method called `getExportFields` in your `ModelAdmin`:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyAdmin extends ModelAdmin {
|
class MyAdmin extends ModelAdmin {
|
||||||
@ -317,7 +318,7 @@ To customise the exported columns, create a new method called `getExportFields`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
* [GridField](../forms/field_types/gridfield)
|
* [GridField](../forms/field_types/gridfield)
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: CMS Architecture
|
||||||
|
summary: An overview of the code architecture of the CMS
|
||||||
|
icon: sitemap
|
||||||
|
---
|
||||||
|
|
||||||
# CMS architecture
|
# CMS architecture
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
@ -111,7 +117,7 @@ of a `PjaxResponseNegotiator` to handle its display.
|
|||||||
|
|
||||||
Basic example form in a CMS controller subclass:
|
Basic example form in a CMS controller subclass:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyAdmin extends LeftAndMain {
|
class MyAdmin extends LeftAndMain {
|
||||||
function getEditForm() {
|
function getEditForm() {
|
||||||
return CMSForm::create(
|
return CMSForm::create(
|
||||||
@ -139,7 +145,7 @@ Basic example form in a CMS controller subclass:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Note: Usually you don't need to worry about these settings,
|
```
|
||||||
and will simply call `parent::getEditForm()` to modify an existing,
|
and will simply call `parent::getEditForm()` to modify an existing,
|
||||||
correctly configured form.
|
correctly configured form.
|
||||||
|
|
||||||
@ -234,50 +240,25 @@ Example: Create a bare-bones CMS subclass which shows breadcrumbs (a built-in me
|
|||||||
as well as info on the current record. A single link updates both sections independently
|
as well as info on the current record. A single link updates both sections independently
|
||||||
in a single Ajax request.
|
in a single Ajax request.
|
||||||
|
|
||||||
:::php
|
```ss
|
||||||
// mysite/code/MyAdmin.php
|
|
||||||
class MyAdmin extends LeftAndMain {
|
|
||||||
private static $url_segment = 'myadmin';
|
|
||||||
public function getResponseNegotiator() {
|
|
||||||
$negotiator = parent::getResponseNegotiator();
|
|
||||||
$controller = $this;
|
|
||||||
// Register a new callback
|
|
||||||
$negotiator->setCallback('MyRecordInfo', function() use(&$controller) {
|
|
||||||
return $controller->MyRecordInfo();
|
|
||||||
});
|
|
||||||
return $negotiator;
|
|
||||||
}
|
|
||||||
public function MyRecordInfo() {
|
|
||||||
return $this->renderWith('MyRecordInfo');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:::js
|
|
||||||
// MyAdmin.ss
|
|
||||||
<% include CMSBreadcrumbs %>
|
|
||||||
<div>Static content (not affected by update)</div>
|
|
||||||
<% include MyRecordInfo %>
|
|
||||||
<a href="admin/myadmin" class="cms-panel-link" data-pjax-target="MyRecordInfo,Breadcrumbs">
|
|
||||||
Update record info
|
|
||||||
</a>
|
|
||||||
|
|
||||||
:::ss
|
|
||||||
// MyRecordInfo.ss
|
// MyRecordInfo.ss
|
||||||
<div data-pjax-fragment="MyRecordInfo">
|
<div data-pjax-fragment="MyRecordInfo">
|
||||||
Current Record: $currentPage.Title
|
Current Record: $currentPage.Title
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
A click on the link will cause the following (abbreviated) ajax HTTP request:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
GET /admin/myadmin HTTP/1.1
|
GET /admin/myadmin HTTP/1.1
|
||||||
X-Pjax:MyRecordInfo,Breadcrumbs
|
X-Pjax:MyRecordInfo,Breadcrumbs
|
||||||
X-Requested-With:XMLHttpRequest
|
X-Requested-With:XMLHttpRequest
|
||||||
|
|
||||||
... and result in the following response:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
{"MyRecordInfo": "<div...", "CMSBreadcrumbs": "<div..."}
|
{"MyRecordInfo": "<div...", "CMSBreadcrumbs": "<div..."}
|
||||||
|
|
||||||
Keep in mind that the returned view isn't always decided upon when the Ajax request
|
```
|
||||||
is fired, so the server might decide to change it based on its own logic,
|
is fired, so the server might decide to change it based on its own logic,
|
||||||
sending back different `X-Pjax` headers and content.
|
sending back different `X-Pjax` headers and content.
|
||||||
|
|
||||||
@ -285,9 +266,10 @@ On the client, you can set your preference through the `data-pjax-target` attrib
|
|||||||
on links or through the `X-Pjax` header. For firing off an Ajax request that is
|
on links or through the `X-Pjax` header. For firing off an Ajax request that is
|
||||||
tracked in the browser history, use the `pjax` attribute on the state data.
|
tracked in the browser history, use the `pjax` attribute on the state data.
|
||||||
|
|
||||||
|
```
|
||||||
$('.cms-container').loadPanel('admin/pages', null, {pjax: 'Content'});
|
$('.cms-container').loadPanel('admin/pages', null, {pjax: 'Content'});
|
||||||
|
|
||||||
## Loading lightweight PJAX fragments
|
```
|
||||||
|
|
||||||
Normal navigation between URLs in the admin section of the Framework occurs through `loadPanel` and `submitForm`.
|
Normal navigation between URLs in the admin section of the Framework occurs through `loadPanel` and `submitForm`.
|
||||||
These calls make sure the HTML5 history is updated correctly and back and forward buttons work. They also take
|
These calls make sure the HTML5 history is updated correctly and back and forward buttons work. They also take
|
||||||
@ -301,18 +283,20 @@ unrelated to the main flow.
|
|||||||
In this case you can use the `loadFragment` call supplied by `LeftAndMain.js`. You can trigger as many of these in
|
In this case you can use the `loadFragment` call supplied by `LeftAndMain.js`. You can trigger as many of these in
|
||||||
parallel as you want. This will not disturb the main navigation.
|
parallel as you want. This will not disturb the main navigation.
|
||||||
|
|
||||||
|
```
|
||||||
$('.cms-container').loadFragment('admin/foobar/', 'Fragment1');
|
$('.cms-container').loadFragment('admin/foobar/', 'Fragment1');
|
||||||
$('.cms-container').loadFragment('admin/foobar/', 'Fragment2');
|
$('.cms-container').loadFragment('admin/foobar/', 'Fragment2');
|
||||||
$('.cms-container').loadFragment('admin/foobar/', 'Fragment3');
|
$('.cms-container').loadFragment('admin/foobar/', 'Fragment3');
|
||||||
|
|
||||||
The ongoing requests are tracked by the PJAX fragment name (Fragment1, 2, and 3 above) - resubmission will
|
```
|
||||||
result in the prior request for this fragment to be aborted. Other parallel requests will continue undisturbed.
|
result in the prior request for this fragment to be aborted. Other parallel requests will continue undisturbed.
|
||||||
|
|
||||||
You can also load multiple fragments in one request, as long as they are to the same controller (i.e. URL):
|
You can also load multiple fragments in one request, as long as they are to the same controller (i.e. URL):
|
||||||
|
|
||||||
|
```
|
||||||
$('.cms-container').loadFragment('admin/foobar/', 'Fragment2,Fragment3');
|
$('.cms-container').loadFragment('admin/foobar/', 'Fragment2,Fragment3');
|
||||||
|
|
||||||
This counts as a separate request type from the perspective of the request tracking, so will not abort the singular
|
```
|
||||||
`Fragment2` nor `Fragment3`.
|
`Fragment2` nor `Fragment3`.
|
||||||
|
|
||||||
Upon the receipt of the response, the fragment will be injected into DOM where a matching `data-pjax-fragment` attribute
|
Upon the receipt of the response, the fragment will be injected into DOM where a matching `data-pjax-fragment` attribute
|
||||||
@ -321,6 +305,7 @@ will be triggered. In case of a request error a `loadfragmenterror` will be rais
|
|||||||
|
|
||||||
You can hook up a response handler that obtains all the details of the XHR request via Entwine handler:
|
You can hook up a response handler that obtains all the details of the XHR request via Entwine handler:
|
||||||
|
|
||||||
|
```
|
||||||
'from .cms-container': {
|
'from .cms-container': {
|
||||||
onafterloadfragment: function(e, data) {
|
onafterloadfragment: function(e, data) {
|
||||||
// Say 'success'!
|
// Say 'success'!
|
||||||
@ -328,8 +313,9 @@ You can hook up a response handler that obtains all the details of the XHR reque
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Alternatively you can use the jQuery deferred API:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
$('.cms-container')
|
$('.cms-container')
|
||||||
.loadFragment('admin/foobar/', 'Fragment1')
|
.loadFragment('admin/foobar/', 'Fragment1')
|
||||||
.success(function(data, status, xhr) {
|
.success(function(data, status, xhr) {
|
||||||
@ -337,7 +323,7 @@ Alternatively you can use the jQuery deferred API:
|
|||||||
alert(status);
|
alert(status);
|
||||||
});
|
});
|
||||||
|
|
||||||
## Ajax Redirects
|
```
|
||||||
|
|
||||||
Sometimes, a server response represents a new URL state, e.g. when submitting an "add record" form,
|
Sometimes, a server response represents a new URL state, e.g. when submitting an "add record" form,
|
||||||
the resulting view will be the edit form of the new record. On non-ajax submissions, that's easily
|
the resulting view will be the edit form of the new record. On non-ajax submissions, that's easily
|
||||||
@ -356,7 +342,7 @@ For example, the currently used controller class might've changed due to a "redi
|
|||||||
which affects the currently active menu entry. We're using HTTP response headers to contain this data
|
which affects the currently active menu entry. We're using HTTP response headers to contain this data
|
||||||
without affecting the response body.
|
without affecting the response body.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyController extends LeftAndMain {
|
class MyController extends LeftAndMain {
|
||||||
class myaction() {
|
class myaction() {
|
||||||
// ...
|
// ...
|
||||||
@ -365,7 +351,7 @@ without affecting the response body.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Built-in headers are:
|
```
|
||||||
|
|
||||||
* `X-Title`: Set window title (requires URL encoding)
|
* `X-Title`: Set window title (requires URL encoding)
|
||||||
* `X-Controller`: PHP class name matching a menu entry, which is marked active
|
* `X-Controller`: PHP class name matching a menu entry, which is marked active
|
||||||
@ -415,14 +401,14 @@ from "Page" to "Files & Images". To communicate this state change, a controller
|
|||||||
response has the option to pass along a special HTTP response header,
|
response has the option to pass along a special HTTP response header,
|
||||||
which is picked up by the menu:
|
which is picked up by the menu:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function mycontrollermethod() {
|
public function mycontrollermethod() {
|
||||||
// .. logic here
|
// .. logic here
|
||||||
$this->getResponse()->addHeader('X-Controller', 'AssetAdmin');
|
$this->getResponse()->addHeader('X-Controller', 'AssetAdmin');
|
||||||
return 'my response';
|
return 'my response';
|
||||||
}
|
}
|
||||||
|
|
||||||
This is usually handled by the existing [api:LeftAndMain] logic,
|
```
|
||||||
so you don't need to worry about it. The same concept applies for
|
so you don't need to worry about it. The same concept applies for
|
||||||
'X-Title' (change the window title) and 'X-ControllerURL' (change the URL recorded in browser history).
|
'X-Title' (change the window title) and 'X-ControllerURL' (change the URL recorded in browser history).
|
||||||
Note: You can see any additional HTTP headers through the web developer tools in your browser of choice.
|
Note: You can see any additional HTTP headers through the web developer tools in your browser of choice.
|
||||||
@ -465,7 +451,7 @@ since all others should render with their tab navigation inline.
|
|||||||
|
|
||||||
Form template with custom tab navigation (trimmed down):
|
Form template with custom tab navigation (trimmed down):
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<form $FormAttributes data-layout-type="border">
|
<form $FormAttributes data-layout-type="border">
|
||||||
|
|
||||||
<div class="cms-content-header north">
|
<div class="cms-content-header north">
|
||||||
@ -490,9 +476,9 @@ Form template with custom tab navigation (trimmed down):
|
|||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
Tabset template without tab navigation (e.g. `CMSTabset.ss`)
|
```
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<div $AttributesHTML>
|
<div $AttributesHTML>
|
||||||
<% loop Tabs %>
|
<% loop Tabs %>
|
||||||
<% if Tabs %>
|
<% if Tabs %>
|
||||||
@ -507,7 +493,7 @@ Tabset template without tab navigation (e.g. `CMSTabset.ss`)
|
|||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Lazy loading works based on the `href` attribute of the tab navigation.
|
```
|
||||||
The base behaviour is applied through adding a class `.cms-tabset` to a container.
|
The base behaviour is applied through adding a class `.cms-tabset` to a container.
|
||||||
Assuming that each tab has its own URL which is tracked in the HTML5 history,
|
Assuming that each tab has its own URL which is tracked in the HTML5 history,
|
||||||
the current tab display also has to work when loaded directly without Ajax.
|
the current tab display also has to work when loaded directly without Ajax.
|
||||||
@ -515,7 +501,7 @@ This is achieved by template conditionals (see "MyActiveCondition").
|
|||||||
The `.cms-panel-link` class will automatically trigger the ajax loading,
|
The `.cms-panel-link` class will automatically trigger the ajax loading,
|
||||||
and load the HTML content into the main view. Example:
|
and load the HTML content into the main view. Example:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<div id="my-tab-id" class="cms-tabset" data-ignore-tab-state="true">
|
<div id="my-tab-id" class="cms-tabset" data-ignore-tab-state="true">
|
||||||
<ul>
|
<ul>
|
||||||
<li class="<% if MyActiveCondition %> ui-tabs-active<% end_if %>">
|
<li class="<% if MyActiveCondition %> ui-tabs-active<% end_if %>">
|
||||||
@ -531,7 +517,7 @@ and load the HTML content into the main view. Example:
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
The URL endpoints `admin/mytabs/tab1` and `admin/mytabs/tab2`
|
```
|
||||||
should return HTML fragments suitable for inserting into the content area,
|
should return HTML fragments suitable for inserting into the content area,
|
||||||
through the `PjaxResponseNegotiator` class (see above).
|
through the `PjaxResponseNegotiator` class (see above).
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
---
|
||||||
title: Admin Layout
|
title: Admin Layout
|
||||||
|
summary: Add interactivity enhancements to the admin with Javascript
|
||||||
|
---
|
||||||
# CMS layout
|
# CMS layout
|
||||||
|
|
||||||
The CMS markup is structured into "panels", which are the base units containing interface components (or other panels),
|
The CMS markup is structured into "panels", which are the base units containing interface components (or other panels),
|
||||||
@ -16,10 +18,10 @@ children setting sizes and positions, which in turn requires redrawing of some o
|
|||||||
|
|
||||||
The easiest way to update the layout of the CMS is to call `redraw` on the top-level `.cms-container` element.
|
The easiest way to update the layout of the CMS is to call `redraw` on the top-level `.cms-container` element.
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
$('.cms-container').redraw();
|
$('.cms-container').redraw();
|
||||||
|
|
||||||
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`)
|
`updateLayoutOptions`)
|
||||||
@ -27,17 +29,17 @@ This causes the framework to:
|
|||||||
to the layout manager)
|
to the layout manager)
|
||||||
* trigger `redraw` on children which also cascades deeper into the hierarchy (this is framework activity)
|
* trigger `redraw` on children which also cascades deeper into the hierarchy (this is framework activity)
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
Caveat: `layout` is also triggered when a DOM element is replaced with AJAX in `LeftAndMain::handleAjaxResponse`. In
|
Caveat: `layout` is also triggered when a DOM element is replaced with AJAX in `LeftAndMain::handleAjaxResponse`. In
|
||||||
this case it is triggered on the parent of the element being replaced so jLayout has a chance to rebuild its algorithms.
|
this case it is triggered on the parent of the element being replaced so jLayout has a chance to rebuild its algorithms.
|
||||||
Calling the top level `layout` is not enough as it will wrongly descend down the detached element's hierarchy.
|
Calling the top level `layout` is not enough as it will wrongly descend down the detached element's hierarchy.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
Caveat: invocation order of the `redraws` is crucial here, generally going from innermost to outermost elements. For
|
Caveat: invocation order of the `redraws` is crucial here, generally going from innermost to outermost elements. For
|
||||||
example, the tab panels have be applied in the CMS form before the form itself is layouted with its sibling panels to
|
example, the tab panels have be applied in the CMS form before the form itself is layouted with its sibling panels to
|
||||||
avoid incorrect dimensions.
|
avoid incorrect dimensions.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
![Layout variations](../../_images/cms-architecture.png)
|
![Layout variations](../../_images/cms-architecture.png)
|
||||||
|
|
||||||
@ -55,7 +57,7 @@ Call `redraw` on `.cms-container` to re-layout the CMS.
|
|||||||
Layout manager will automatically apply algorithms to the children of `.cms-container` by inspecting the
|
Layout manager will automatically apply algorithms to the children of `.cms-container` by inspecting the
|
||||||
`data-layout-type` attribute. Let's take the content toolbar as an example of a second-level layout application:
|
`data-layout-type` attribute. Let's take the content toolbar as an example of a second-level layout application:
|
||||||
|
|
||||||
:::html
|
```html
|
||||||
<div class="cms-content-tools west cms-panel cms-panel-layout"
|
<div class="cms-content-tools west cms-panel cms-panel-layout"
|
||||||
data-expandOnClick="true"
|
data-expandOnClick="true"
|
||||||
data-layout-type="border"
|
data-layout-type="border"
|
||||||
@ -63,7 +65,7 @@ Layout manager will automatically apply algorithms to the children of `.cms-cont
|
|||||||
<%-- content utilising border's north, south, east, west and center classes --%>
|
<%-- content utilising border's north, south, east, west and center classes --%>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
For detailed discussion on available algorithms refer to
|
```
|
||||||
[jLayout algorithms](https://github.com/bramstein/jlayout#layout-algorithms).
|
[jLayout algorithms](https://github.com/bramstein/jlayout#layout-algorithms).
|
||||||
|
|
||||||
Our [Howto: Extend the CMS Interface](how_tos/extend_cms_interface) has a practical example on how to add a bottom
|
Our [Howto: Extend the CMS Interface](how_tos/extend_cms_interface) has a practical example on how to add a bottom
|
||||||
@ -99,10 +101,10 @@ by the algorithm that are initially taken from the `LeftAndMain::LayoutOptions`
|
|||||||
|
|
||||||
Use provided factory method to generate algorithm instances.
|
Use provided factory method to generate algorithm instances.
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
jLayout.threeColumnCompressor(<column-spec-object>, <options-object>);
|
jLayout.threeColumnCompressor(<column-spec-object>, <options-object>);
|
||||||
|
|
||||||
The parameters are as follows:
|
```
|
||||||
|
|
||||||
* **column-spec-object**: object providing the _menu_, _content_ and _preview_ elements (all fields mandatory)
|
* **column-spec-object**: object providing the _menu_, _content_ and _preview_ elements (all fields mandatory)
|
||||||
* **options-object**: object providing the configuration (all fields mandatory, see options below)
|
* **options-object**: object providing the configuration (all fields mandatory, see options below)
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Preview
|
||||||
|
summary: How content previews work in the CMS
|
||||||
|
---
|
||||||
|
|
||||||
# CMS preview
|
# CMS preview
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
@ -21,12 +26,12 @@ first segment has to match current _LeftAndMain_-derived class (e.g.
|
|||||||
|
|
||||||
We use `ss.preview` entwine namespace for all preview-related entwines.
|
We use `ss.preview` entwine namespace for all preview-related entwines.
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
Caveat: `SilverStripeNavigator` and `CMSPreviewable` interface currently only
|
Caveat: `SilverStripeNavigator` and `CMSPreviewable` interface currently only
|
||||||
support SiteTree objects that are _Versioned_. They are not general enough for
|
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
|
using on any other DataObject. That pretty much limits the extendability of the
|
||||||
feature.
|
feature.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Configuration and Defaults
|
## Configuration and Defaults
|
||||||
|
|
||||||
@ -45,7 +50,7 @@ Note how the configuration happens in different entwine namespaces
|
|||||||
("ss.preview" and "ss"), as well as applies to different selectors
|
("ss.preview" and "ss"), as well as applies to different selectors
|
||||||
(".cms-preview" and ".cms-container").
|
(".cms-preview" and ".cms-container").
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
(function($) {
|
(function($) {
|
||||||
$.entwine('ss.preview', function($){
|
$.entwine('ss.preview', function($){
|
||||||
$('.cms-preview').entwine({
|
$('.cms-preview').entwine({
|
||||||
@ -68,15 +73,15 @@ Note how the configuration happens in different entwine namespaces
|
|||||||
});
|
});
|
||||||
}(jQuery));
|
}(jQuery));
|
||||||
|
|
||||||
Load the file in the CMS via setting adding 'mysite/javascript/MyLeftAndMain.Preview.js'
|
```
|
||||||
to the `LeftAndMain.extra_requirements_javascript` [configuration value](../configuration)
|
to the `LeftAndMain.extra_requirements_javascript` [configuration value](../configuration)
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
LeftAndMain:
|
LeftAndMain:
|
||||||
extra_requirements_javascript:
|
extra_requirements_javascript:
|
||||||
- mysite/javascript/MyLeftAndMain.Preview.js
|
- mysite/javascript/MyLeftAndMain.Preview.js
|
||||||
|
|
||||||
In order to find out which configuration values are available, the source code
|
```
|
||||||
is your best reference at the moment - have a look in `framework/admin/javascript/LeftAndMain.Preview.js`.
|
is your best reference at the moment - have a look in `framework/admin/javascript/LeftAndMain.Preview.js`.
|
||||||
To understand how layouts are handled in the CMS UI, have a look at the
|
To understand how layouts are handled in the CMS UI, have a look at the
|
||||||
[CMS Architecture](cms_architecture) guide.
|
[CMS Architecture](cms_architecture) guide.
|
||||||
@ -103,22 +108,20 @@ property.
|
|||||||
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:
|
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
|
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
|
with the internal state. See `AllowedStates` in `.cms-preview` entwine for the
|
||||||
list of supported states.
|
list of supported states.
|
||||||
|
|
||||||
You can get the current state by calling:
|
You can get the current state by calling:
|
||||||
|
|
||||||
```js
|
```
|
||||||
$('.cms-preview').entwine('.ss.preview').getCurrentStateName();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Preview sizes
|
```
|
||||||
|
|
||||||
This selector defines how the preview iframe is rendered, and try to emulate
|
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
|
different device sizes. The options are hardcoded. The option names map directly
|
||||||
@ -133,40 +136,36 @@ You can switch between different types of display sizes programmatically, which
|
|||||||
has the benefit of redrawing the related selector and maintaining a consistent
|
has the benefit of redrawing the related selector and maintaining a consistent
|
||||||
internal state:
|
internal state:
|
||||||
|
|
||||||
```js
|
```
|
||||||
$('.cms-preview').entwine('.ss.preview').changeSize('auto');
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can find out current size by calling:
|
```
|
||||||
|
|
||||||
```js
|
```
|
||||||
$('.cms-preview').entwine('.ss.preview').getCurrentSizeName();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Preview modes
|
```
|
||||||
|
|
||||||
Preview modes map to the modes supported by the _threeColumnCompressor_ layout
|
Preview modes map to the modes supported by the _threeColumnCompressor_ layout
|
||||||
algorithm, see [layout reference](cms_layout) for more details. You
|
algorithm, see [layout reference](cms_layout) for more details. You
|
||||||
can change modes by calling:
|
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
|
```
|
||||||
internal states of the layout. You can reach it by calling:
|
internal states of the layout. You can reach it by calling:
|
||||||
|
|
||||||
```js
|
```
|
||||||
$('.cms-container').entwine('.ss').getLayoutOptions().mode;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
```
|
||||||
Caveat: the `.preview-mode-selector` appears twice, once in the preview and
|
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
|
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
|
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.
|
option selectors, even if they try to appear as one horizontal bar.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Preview API
|
## Preview API
|
||||||
|
|
||||||
|
@ -1,32 +1,34 @@
|
|||||||
|
---
|
||||||
title: WYSIWYG Styles
|
title: WYSIWYG Styles
|
||||||
summary: Add custom CSS properties to the rich-text editor.
|
summary: Add custom CSS properties to the rich-text editor.
|
||||||
|
icon: text-width
|
||||||
|
---
|
||||||
# WYSIWYG Styles
|
# WYSIWYG Styles
|
||||||
|
|
||||||
SilverStripe lets you customise the style of content in the CMS. This is done by setting up a CSS file called
|
SilverStripe lets you customise 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
|
`editor.css` in either your theme or in your `mysite` folder. This is set through
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
HtmlEditorConfig::get('cms')->setOption('content_css', project() . '/css/editor.css');
|
HtmlEditorConfig::get('cms')->setOption('content_css', project() . '/css/editor.css');
|
||||||
|
|
||||||
Will load the `mysite/css/editor.css` file.
|
```
|
||||||
|
|
||||||
If using this config option in `mysite/_config.php`, you will have to instead call:
|
If using this config option in `mysite/_config.php`, you will have to instead call:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
HtmlEditorConfig::get('cms')->setOption('content_css', project() . '/css/editor.css');
|
HtmlEditorConfig::get('cms')->setOption('content_css', project() . '/css/editor.css');
|
||||||
|
|
||||||
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`
|
add the color 'red' as an option within the `WYSIWYG` add the following to the `editor.css`
|
||||||
|
|
||||||
:::css
|
```css
|
||||||
.red {
|
.red {
|
||||||
color: 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.
|
After you have defined the `editor.css` make sure you clear your SilverStripe cache for it to take effect.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Javascript Development
|
title: Javascript Development
|
||||||
summary: Advanced documentation about writing and customizing javascript within SilverStripe.
|
summary: Advanced documentation about writing and customizing javascript within SilverStripe.
|
||||||
|
iconBrand: js
|
||||||
|
---
|
||||||
# Javascript Development
|
# Javascript Development
|
||||||
|
|
||||||
The following document is an advanced guide on building rich javascript interactions within the SilverStripe CMS and
|
The following document is an advanced guide on building rich javascript interactions within the SilverStripe CMS and
|
||||||
@ -25,14 +27,14 @@ plugin. See "[How jQuery Works](http://docs.jquery.com/How_jQuery_Works)" for a
|
|||||||
|
|
||||||
You should write all your custom jQuery code in a closure.
|
You should write all your custom jQuery code in a closure.
|
||||||
|
|
||||||
:::javascript
|
```javascript
|
||||||
(function($) {
|
(function($) {
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
// your code here.
|
// your code here.
|
||||||
})
|
})
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|
||||||
## jQuery Plugins
|
```
|
||||||
|
|
||||||
A jQuery Plugin is essentially a method call which can act on a collection of DOM elements. It is contained within 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
|
`jQuery.fn` namespace, and attaches itself automatically to all jQuery collections. The basics for are outlined in the
|
||||||
@ -51,7 +53,7 @@ development, most importantly:
|
|||||||
Example: A plugin to highlight a collection of elements with a configurable foreground and background colour
|
Example: A plugin to highlight a collection of elements with a configurable foreground and background colour
|
||||||
(abbreviated example from [learningjquery.com](http://www.learningjquery.com/2007/10/a-plugin-development-pattern)).
|
(abbreviated example from [learningjquery.com](http://www.learningjquery.com/2007/10/a-plugin-development-pattern)).
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
// create closure
|
// create closure
|
||||||
(function($) {
|
(function($) {
|
||||||
// plugin definition
|
// plugin definition
|
||||||
@ -78,10 +80,10 @@ Example: A plugin to highlight a collection of elements with a configurable fore
|
|||||||
// end of closure
|
// end of closure
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|
||||||
|
```
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
(function($) {
|
(function($) {
|
||||||
// Highlight all buttons with default colours
|
// Highlight all buttons with default colours
|
||||||
jQuery(':button').highlight();
|
jQuery(':button').highlight();
|
||||||
@ -93,7 +95,7 @@ Usage:
|
|||||||
$.fn.hilight.defaults.background = "green";
|
$.fn.hilight.defaults.background = "green";
|
||||||
})(jQuery);
|
})(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
|
UI Widgets are jQuery Plugins with a bit more structure, targeted towards interactive elements. They require jQuery and
|
||||||
@ -111,7 +113,7 @@ See the [official developer guide](http://jqueryui.com/docs/Developer_Guide) and
|
|||||||
|
|
||||||
Example: Highlighter
|
Example: Highlighter
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
(function($) {
|
(function($) {
|
||||||
$.widget("ui.myHighlight", {
|
$.widget("ui.myHighlight", {
|
||||||
getBlink: function () {
|
getBlink: function () {
|
||||||
@ -138,10 +140,10 @@ Example: Highlighter
|
|||||||
};
|
};
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|
||||||
|
```
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
(function($) {
|
(function($) {
|
||||||
// call with default options
|
// call with default options
|
||||||
$(':button').myHighlight();
|
$(':button').myHighlight();
|
||||||
@ -159,7 +161,7 @@ Usage:
|
|||||||
$(':button').myHighlight('getBlink');
|
$(':button').myHighlight('getBlink');
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|
||||||
|
```
|
||||||
### jQuery.Entwine
|
### jQuery.Entwine
|
||||||
|
|
||||||
jQuery.entwine is a third-party plugin, from its documentation:
|
jQuery.entwine is a third-party plugin, from its documentation:
|
||||||
@ -173,7 +175,7 @@ It is also suited for more complex applications beyond a single-purpose plugin.
|
|||||||
|
|
||||||
Example: Highlighter
|
Example: Highlighter
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
(function($) {
|
(function($) {
|
||||||
$(':button').entwine({
|
$(':button').entwine({
|
||||||
Foreground: 'red',
|
Foreground: 'red',
|
||||||
@ -185,10 +187,10 @@ Example: Highlighter
|
|||||||
});
|
});
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|
||||||
|
```
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
(function($) {
|
(function($) {
|
||||||
// call with default options
|
// call with default options
|
||||||
$(':button').entwine().highlight();
|
$(':button').entwine().highlight();
|
||||||
@ -200,7 +202,7 @@ Usage:
|
|||||||
$(':button').entwine().getBackground();
|
$(':button').entwine().getBackground();
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|
||||||
|
```
|
||||||
This is a deliberately simple example, the strength of jQuery.entwine over simple jQuery plugins lies in its public
|
This is a deliberately simple example, the strength of jQuery.entwine over simple jQuery plugins lies in its public
|
||||||
properties, namespacing, as well as its inheritance based on CSS selectors. Please see the [project
|
properties, namespacing, as well as its inheritance based on CSS selectors. Please see the [project
|
||||||
documentation](http://github.com/hafriedlander/jquery.entwine/tree/master) for more complete examples.
|
documentation](http://github.com/hafriedlander/jquery.entwine/tree/master) for more complete examples.
|
||||||
@ -219,14 +221,14 @@ jQuery with a few lines of code. Your jQuery code will normally end up as a ser
|
|||||||
|
|
||||||
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
|
```js
|
||||||
// you can't rely on '$' being defined outside of the closure
|
// you can't rely on '$' being defined outside of the closure
|
||||||
(function($) {
|
(function($) {
|
||||||
var myPrivateVar; // only available inside the closure
|
var myPrivateVar; // only available inside the closure
|
||||||
// inside here you can use the 'jQuery' object as '$'
|
// inside here you can use the 'jQuery' object as '$'
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|
||||||
|
```
|
||||||
You can run `[jQuery.noConflict()](http://docs.jquery.com/Core/jQuery.noConflict)` to avoid namespace clashes.
|
You can run `[jQuery.noConflict()](http://docs.jquery.com/Core/jQuery.noConflict)` to avoid namespace clashes.
|
||||||
NoConflict mode is enabled by default in the SilverStripe CMS javascript.
|
NoConflict mode is enabled by default in the SilverStripe CMS javascript.
|
||||||
|
|
||||||
@ -235,13 +237,13 @@ NoConflict mode is enabled by default in the SilverStripe CMS javascript.
|
|||||||
You have to ensure that DOM elements you want to act on are loaded before using them. jQuery provides a wrapper around
|
You have to ensure that DOM elements you want to act on are loaded before using them. jQuery provides a wrapper around
|
||||||
the `window.onload` and `document.ready` events.
|
the `window.onload` and `document.ready` events.
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
// DOM elements might not be available here
|
// DOM elements might not be available here
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// The DOM is fully loaded here
|
// The DOM is fully loaded here
|
||||||
});
|
});
|
||||||
|
|
||||||
|
```
|
||||||
See [jQuery FAQ: Launching Code on Document
|
See [jQuery FAQ: Launching Code on Document
|
||||||
Ready](http://docs.jquery.com/How_jQuery_Works#Launching_Code_on_Document_Ready).
|
Ready](http://docs.jquery.com/How_jQuery_Works#Launching_Code_on_Document_Ready).
|
||||||
|
|
||||||
@ -254,7 +256,7 @@ Caution: Only applies to certain events, see the [jQuery.on() documentation](htt
|
|||||||
|
|
||||||
Example: Add a 'loading' classname to all pressed buttons
|
Example: Add a 'loading' classname to all pressed buttons
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
// manual binding, only applies to existing elements
|
// manual binding, only applies to existing elements
|
||||||
$('input[[type=submit]]').on('click', function() {
|
$('input[[type=submit]]').on('click', function() {
|
||||||
$(this).addClass('loading');
|
$(this).addClass('loading');
|
||||||
@ -265,7 +267,7 @@ Example: Add a 'loading' classname to all pressed buttons
|
|||||||
$(this).addClass('loading');
|
$(this).addClass('loading');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
```
|
||||||
### Assume Element Collections
|
### Assume Element Collections
|
||||||
|
|
||||||
jQuery is based around collections of DOM elements, the library functions typically handle multiple elements (where it
|
jQuery is based around collections of DOM elements, the library functions typically handle multiple elements (where it
|
||||||
@ -273,7 +275,7 @@ makes sense). Encapsulate your code by nesting your jQuery commands inside a `jQ
|
|||||||
|
|
||||||
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
|
```js
|
||||||
$('div.ComplexTableField').each(function() {
|
$('div.ComplexTableField').each(function() {
|
||||||
// This is the over code for the tr elements inside a ComplexTableField.
|
// This is the over code for the tr elements inside a ComplexTableField.
|
||||||
$(this).find('tr').hover(
|
$(this).find('tr').hover(
|
||||||
@ -281,7 +283,7 @@ Example: ComplexTableField implements a paginated table with a pop-up for displa
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
```
|
||||||
### Use plain HTML and jQuery.data() to store data
|
### Use plain HTML and jQuery.data() to store data
|
||||||
|
|
||||||
The DOM can make javascript configuration and state-keeping a lot easier, without having to resort to javascript
|
The DOM can make javascript configuration and state-keeping a lot easier, without having to resort to javascript
|
||||||
@ -291,7 +293,7 @@ Example: Simple form change tracking to prevent submission of unchanged data
|
|||||||
|
|
||||||
Through CSS properties
|
Through CSS properties
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
$('form :input').bind('change', function(e) {
|
$('form :input').bind('change', function(e) {
|
||||||
$(this.form).addClass('isChanged');
|
$(this.form).addClass('isChanged');
|
||||||
});
|
});
|
||||||
@ -299,10 +301,10 @@ Through CSS properties
|
|||||||
if($(this).hasClass('isChanged')) return false;
|
if($(this).hasClass('isChanged')) return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
```
|
||||||
Through jQuery.data()
|
Through jQuery.data()
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
$('form :input').bind('change', function(e) {
|
$('form :input').bind('change', function(e) {
|
||||||
$(this.form).data('isChanged', true);
|
$(this.form).data('isChanged', true);
|
||||||
});
|
});
|
||||||
@ -311,7 +313,7 @@ Through jQuery.data()
|
|||||||
if($(this).data('isChanged')) return false;
|
if($(this).data('isChanged')) return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
```
|
||||||
See [interactive example on jsbin.com](http://jsbin.com/opuva)
|
See [interactive example on jsbin.com](http://jsbin.com/opuva)
|
||||||
|
|
||||||
You can also use the [jQuery.metadata Plugin](http://docs.jquery.com/Plugins/Metadata/metadata) to serialize data into
|
You can also use the [jQuery.metadata Plugin](http://docs.jquery.com/Plugins/Metadata/metadata) to serialize data into
|
||||||
@ -320,11 +322,11 @@ rendering a form element through the SilverStripe templating engine.
|
|||||||
|
|
||||||
Example: Restricted numeric value field
|
Example: Restricted numeric value field
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<input type="text" class="restricted-text {min:4,max:10}" />
|
<input type="text" class="restricted-text {min:4,max:10}" />
|
||||||
|
|
||||||
|
```
|
||||||
:::js
|
```js
|
||||||
$('.restricted-text').bind('change', function(e) {
|
$('.restricted-text').bind('change', function(e) {
|
||||||
if(
|
if(
|
||||||
e.target.value < $(this).metadata().min
|
e.target.value < $(this).metadata().min
|
||||||
@ -335,7 +337,7 @@ Example: Restricted numeric value field
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
```
|
||||||
See [interactive example on jsbin.com](http://jsbin.com/axafa)
|
See [interactive example on jsbin.com](http://jsbin.com/axafa)
|
||||||
|
|
||||||
### Return HTML/JSON and HTTPResponse class for AJAX responses
|
### Return HTML/JSON and HTTPResponse class for AJAX responses
|
||||||
@ -356,17 +358,17 @@ Example: Autocomplete input field loading page matches through AJAX
|
|||||||
|
|
||||||
Template:
|
Template:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul>
|
<ul>
|
||||||
<% loop $Results %>
|
<% loop $Results %>
|
||||||
<li id="Result-$ID">$Title</li>
|
<li id="Result-$ID">$Title</li>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
```
|
||||||
PHP:
|
PHP:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyController {
|
class MyController {
|
||||||
public function autocomplete($request) {
|
public function autocomplete($request) {
|
||||||
$results = Page::get()->filter("Title", $request->getVar('title'));
|
$results = Page::get()->filter("Title", $request->getVar('title'));
|
||||||
@ -383,10 +385,10 @@ PHP:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<form action"#">
|
<form action"#">
|
||||||
<div class="autocomplete {url:'MyController/autocomplete'}">
|
<div class="autocomplete {url:'MyController/autocomplete'}">
|
||||||
<input type="text" name="title" />
|
<input type="text" name="title" />
|
||||||
@ -395,10 +397,10 @@ HTML
|
|||||||
<input type="submit" value="action_autocomplete" />
|
<input type="submit" value="action_autocomplete" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
```
|
||||||
JavaScript:
|
JavaScript:
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
$('.autocomplete input').on('change', function() {
|
$('.autocomplete input').on('change', function() {
|
||||||
var resultsEl = $(this).siblings('.results');
|
var resultsEl = $(this).siblings('.results');
|
||||||
resultsEl.load(
|
resultsEl.load(
|
||||||
@ -417,7 +419,7 @@ JavaScript:
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
```
|
||||||
Although they are the minority of cases, there are times when a simple HTML fragment isn't enough. For example, if you
|
Although they are the minority of cases, there are times when a simple HTML fragment isn't enough. For example, if you
|
||||||
have server side code that needs to trigger the update of a couple of elements in the CMS left-hand tree, it would be
|
have server side code that needs to trigger the update of a couple of elements in the CMS left-hand tree, it would be
|
||||||
inefficient to send back the HTML of entire tree. SilverStripe can serialize to and from JSON (see the [api:Convert] class), and jQuery deals very well with it through
|
inefficient to send back the HTML of entire tree. SilverStripe can serialize to and from JSON (see the [api:Convert] class), and jQuery deals very well with it through
|
||||||
@ -437,7 +439,7 @@ events](http://docs.jquery.com/Namespaced_Events).
|
|||||||
|
|
||||||
Example: Trigger custom 'validationfailed' event on form submission for each empty element
|
Example: Trigger custom 'validationfailed' event on form submission for each empty element
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
$('form').bind('submit', function(e) {
|
$('form').bind('submit', function(e) {
|
||||||
// $(this) refers to form
|
// $(this) refers to form
|
||||||
$(this).find(':input').each(function() {
|
$(this).find(':input').each(function() {
|
||||||
@ -453,7 +455,7 @@ Example: Trigger custom 'validationfailed' event on form submission for each emp
|
|||||||
alert($(this).attr('name'));
|
alert($(this).attr('name'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
```
|
||||||
See [interactive example on jsbin.com](http://jsbin.com/ipeca).
|
See [interactive example on jsbin.com](http://jsbin.com/ipeca).
|
||||||
|
|
||||||
Don't use event handlers in the following situations:
|
Don't use event handlers in the following situations:
|
||||||
@ -494,7 +496,7 @@ JSDoc-toolkit is a command line utility, see [usage](http://code.google.com/p/js
|
|||||||
|
|
||||||
Example: jQuery.entwine
|
Example: jQuery.entwine
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
/**
|
/**
|
||||||
|
|
||||||
* Available Custom Events:
|
* Available Custom Events:
|
||||||
@ -542,7 +544,7 @@ Example: jQuery.entwine
|
|||||||
};
|
};
|
||||||
]]);
|
]]);
|
||||||
|
|
||||||
|
```
|
||||||
### Unit Testing
|
### Unit Testing
|
||||||
|
|
||||||
It is important to verify that your code actually does what it says, and the best way to ensure this are **automated
|
It is important to verify that your code actually does what it says, and the best way to ensure this are **automated
|
||||||
@ -553,16 +555,17 @@ start with JSpec, as it provides a much more powerful testing framework.
|
|||||||
|
|
||||||
Example: QUnit test (from [jquery.com](http://docs.jquery.com/QUnit#Using_QUnit)):
|
Example: QUnit test (from [jquery.com](http://docs.jquery.com/QUnit#Using_QUnit)):
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
test("a basic test example", function() {
|
test("a basic test example", function() {
|
||||||
ok( true, "this test is fine" );
|
ok( true, "this test is fine" );
|
||||||
var value = "hello";
|
var value = "hello";
|
||||||
equals( "hello", value, "We expect value to be hello" );
|
equals( "hello", value, "We expect value to be hello" );
|
||||||
});
|
});
|
||||||
|
|
||||||
|
```
|
||||||
Example: JSpec Shopping cart test (from [visionmedia.github.com](http://visionmedia.github.com/jspec/))
|
Example: JSpec Shopping cart test (from [visionmedia.github.com](http://visionmedia.github.com/jspec/))
|
||||||
|
|
||||||
|
```
|
||||||
describe 'ShoppingCart'
|
describe 'ShoppingCart'
|
||||||
before_each
|
before_each
|
||||||
cart = new ShoppingCart
|
cart = new ShoppingCart
|
||||||
@ -576,7 +579,7 @@ Example: JSpec Shopping cart test (from [visionmedia.github.com](http://visionme
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
## Related
|
```
|
||||||
|
|
||||||
* [Unobtrusive Javascript](http://www.onlinetools.org/articles/unobtrusivejavascript/chapter1.html)
|
* [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)
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: CMS alternating button
|
||||||
|
summary: Add an "active" and "neutral" state to the CMS buttons
|
||||||
|
---
|
||||||
|
|
||||||
# How to implement an alternating button
|
# How to implement an alternating button
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
@ -26,7 +31,7 @@ state already, so you just need to add the alternate state using two data additi
|
|||||||
|
|
||||||
Here is the configuration code for the button:
|
Here is the configuration code for the button:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function getCMSActions() {
|
public function getCMSActions() {
|
||||||
$fields = parent::getCMSActions();
|
$fields = parent::getCMSActions();
|
||||||
|
|
||||||
@ -42,7 +47,7 @@ Here is the configuration code for the button:
|
|||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
You can control the state of the button from the backend by applying `ss-ui-alternate` class to the `FormAction`. To
|
```
|
||||||
simplify our example, let's assume the button state is controlled on the backend only, but you'd usually be better off
|
simplify our example, let's assume the button state is controlled on the backend only, but you'd usually be better off
|
||||||
adjusting the state in the frontend to give the user the benefit of immediate feedback. This technique might still be
|
adjusting the state in the frontend to give the user the benefit of immediate feedback. This technique might still be
|
||||||
used for initialisation though.
|
used for initialisation though.
|
||||||
@ -50,7 +55,7 @@ used for initialisation though.
|
|||||||
Here we initialise the button based on the backend check, and assume that the button will only update after page reload
|
Here we initialise the button based on the backend check, and assume that the button will only update after page reload
|
||||||
(or on CMS action).
|
(or on CMS action).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
public function getCMSActions() {
|
public function getCMSActions() {
|
||||||
// ...
|
// ...
|
||||||
if ($this->needsCleaning()) {
|
if ($this->needsCleaning()) {
|
||||||
@ -60,31 +65,31 @@ 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
|
As with the *Save* and *Save & publish* buttons, you might want to add some scripted reactions to user actions on the
|
||||||
frontend. You can affect the state of the button through the jQuery UI calls.
|
frontend. You can affect the state of the button through the jQuery UI calls.
|
||||||
|
|
||||||
First of all, you can toggle the state of the button - execute this code in the browser's console to see how it works.
|
First of all, you can toggle the state of the button - execute this code in the browser's console to see how it works.
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
jQuery('.cms-edit-form .Actions #Form_EditForm_action_cleanup').button('toggleAlternate');
|
jQuery('.cms-edit-form .Actions #Form_EditForm_action_cleanup').button('toggleAlternate');
|
||||||
|
|
||||||
Another, more useful, scenario is to check the current state.
|
```
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
jQuery('.cms-edit-form .Actions #Form_EditForm_action_cleanup').button('option', 'showingAlternate');
|
jQuery('.cms-edit-form .Actions #Form_EditForm_action_cleanup').button('option', 'showingAlternate');
|
||||||
|
|
||||||
You can also force the button into a specific state by using UI options.
|
```
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
jQuery('.cms-edit-form .Actions #Form_EditForm_action_cleanup').button({showingAlternate: true});
|
jQuery('.cms-edit-form .Actions #Form_EditForm_action_cleanup').button({showingAlternate: true});
|
||||||
|
|
||||||
This will allow you to react to user actions in the CMS and give immediate feedback. Here is an example taken from the
|
```
|
||||||
CMS core that tracks the changes to the input fields and reacts by enabling the *Save* and *Save & publish* buttons
|
CMS core that tracks the changes to the input fields and reacts by enabling the *Save* and *Save & publish* buttons
|
||||||
(changetracker will automatically add `changed` class to the form if a modification is detected).
|
(changetracker will automatically add `changed` class to the form if a modification is detected).
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
/**
|
/**
|
||||||
* Enable save buttons upon detecting changes to content.
|
* Enable save buttons upon detecting changes to content.
|
||||||
* "changed" class is added by jQuery.changetracker.
|
* "changed" class is added by jQuery.changetracker.
|
||||||
@ -103,7 +108,7 @@ CMS core that tracks the changes to the input fields and reacts by enabling the
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
## Frontend hooks
|
```
|
||||||
|
|
||||||
`ssui.button` defines several additional events so that you can extend the code with your own behaviours. For example
|
`ssui.button` defines several additional events so that you can extend the code with your own behaviours. For example
|
||||||
this is used in the CMS to style the buttons. Three events are available:
|
this is used in the CMS to style the buttons. Three events are available:
|
||||||
@ -131,7 +136,7 @@ disassembled into:
|
|||||||
Here is the entire handler put together. You don't need to add any separate initialisation code, this will handle all
|
Here is the entire handler put together. You don't need to add any separate initialisation code, this will handle all
|
||||||
cases.
|
cases.
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
(function($) {
|
(function($) {
|
||||||
|
|
||||||
$.entwine('mysite', function($){
|
$.entwine('mysite', function($){
|
||||||
@ -153,7 +158,7 @@ cases.
|
|||||||
|
|
||||||
}(jQuery));
|
}(jQuery));
|
||||||
|
|
||||||
## Summary
|
```
|
||||||
|
|
||||||
The code presented gives you a fully functioning alternating button, similar to the defaults that come with the the CMS.
|
The code presented gives you a fully functioning alternating button, similar to the defaults that come with the the CMS.
|
||||||
These alternating buttons can be used to give user the advantage of visual feedback upon their actions.
|
These alternating buttons can be used to give user the advantage of visual feedback upon their actions.
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: CMS form field help text
|
||||||
|
summary: Add help text to the form fields in the CMS
|
||||||
|
icon: help
|
||||||
|
---
|
||||||
|
|
||||||
# How to Show Help Text on CMS Form Fields
|
# How to Show Help Text on CMS Form Fields
|
||||||
|
|
||||||
Sometimes you need to express more context for a form field
|
Sometimes you need to express more context for a form field
|
||||||
@ -9,19 +15,19 @@ shown alongside the field, a tooltip which shows on demand, or toggleable descri
|
|||||||
The `FormField->setDescription()` method will add a `<span class="description">`
|
The `FormField->setDescription()` method will add a `<span class="description">`
|
||||||
at the last position within the field, and expects unescaped HTML content.
|
at the last position within the field, and expects unescaped HTML content.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
TextField::create('MyText', 'My Text Label')
|
TextField::create('MyText', 'My Text Label')
|
||||||
->setDescription('More <strong>detailed</strong> help');
|
->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
|
```php
|
||||||
TextField::create('MyText', 'My Text Label')
|
TextField::create('MyText', 'My Text Label')
|
||||||
->setDescription('More <strong>detailed</strong> help')
|
->setDescription('More <strong>detailed</strong> help')
|
||||||
->addExtraClass('cms-description-tooltip');
|
->addExtraClass('cms-description-tooltip');
|
||||||
|
|
||||||
Tooltips are only supported
|
```
|
||||||
for native, focusable input elements, which excludes
|
for native, focusable input elements, which excludes
|
||||||
more complex fields like `GridField`, `UploadField`
|
more complex fields like `GridField`, `UploadField`
|
||||||
or `DropdownField` with the chosen.js behaviour applied.
|
or `DropdownField` with the chosen.js behaviour applied.
|
||||||
@ -34,19 +40,19 @@ Another option you have available is making the field's description togglable. T
|
|||||||
the UI tidy by hiding the description until the user requests more information
|
the UI tidy by hiding the description until the user requests more information
|
||||||
by clicking the 'info' icon displayed alongside the field.
|
by clicking the 'info' icon displayed alongside the field.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
TextField::create('MyText', 'My Text Label')
|
TextField::create('MyText', 'My Text Label')
|
||||||
->setDescription('More <strong>detailed</strong> help')
|
->setDescription('More <strong>detailed</strong> help')
|
||||||
->addExtraClass('cms-description-toggle');
|
->addExtraClass('cms-description-toggle');
|
||||||
|
|
||||||
If you want to provide a custom icon for toggling the description, you can do that
|
```
|
||||||
by setting an additional `RightTitle`.
|
by setting an additional `RightTitle`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
TextField::create('MyText', 'My Text Label')
|
TextField::create('MyText', 'My Text Label')
|
||||||
->setDescription('More <strong>detailed</strong> help')
|
->setDescription('More <strong>detailed</strong> help')
|
||||||
->addExtraClass('cms-description-toggle')
|
->addExtraClass('cms-description-toggle')
|
||||||
->setRightTitle('<a class="cms-description-trigger">My custom icon</a>');
|
->setRightTitle('<a class="cms-description-trigger">My custom icon</a>');
|
||||||
|
|
||||||
Note: For more advanced help text we recommend using
|
```
|
||||||
[Custom form field templates](/developer_guides/forms/form_templates);
|
[Custom form field templates](/developer_guides/forms/form_templates);
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Customise the CMS Menu
|
||||||
|
summary: Make custom changes to the left hand menu in the CMS
|
||||||
|
---
|
||||||
|
|
||||||
# How to customise the CMS Menu
|
# How to customise the CMS Menu
|
||||||
|
|
||||||
## Adding an administration panel
|
## Adding an administration panel
|
||||||
@ -20,25 +25,25 @@ 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
|
black-and-transparent PNG graphics. In this case we'll place the icon in
|
||||||
`mysite/images`, but you are free to use any location.
|
`mysite/images`, but you are free to use any location.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class ProductAdmin extends ModelAdmin {
|
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.
|
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.
|
controller, removing the "Admin" bit at the end.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class ProductAdmin extends ModelAdmin {
|
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
|
`<classname>.MENUTITLE` entity name, which is automatically created when running
|
||||||
the i18n text collection.
|
the i18n text collection.
|
||||||
|
|
||||||
@ -54,7 +59,7 @@ Google to the menu.
|
|||||||
First, we need to define a [api:LeftAndMainExtension] which will contain our
|
First, we need to define a [api:LeftAndMainExtension] which will contain our
|
||||||
button configuration.
|
button configuration.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class CustomLeftAndMain extends LeftAndMainExtension {
|
class CustomLeftAndMain extends LeftAndMainExtension {
|
||||||
@ -83,14 +88,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
|
class. For more information about configuring extensions see the
|
||||||
[extensions reference](/developer_guides/extending/extensions).
|
[extensions reference](/developer_guides/extending/extensions).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
LeftAndMain::add_extension('CustomLeftAndMain')
|
LeftAndMain::add_extension('CustomLeftAndMain')
|
||||||
|
|
||||||
|
```
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
* [How to extend the CMS interface](extend_cms_interface)
|
* [How to extend the CMS interface](extend_cms_interface)
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: Customise the CMS pages list
|
||||||
|
---
|
||||||
|
|
||||||
# Howto: Customize the Pages List in the CMS
|
# Howto: Customize the Pages List in the CMS
|
||||||
|
|
||||||
The pages "list" view in the CMS is a powerful alternative to visualizing
|
The pages "list" view in the CMS is a powerful alternative to visualizing
|
||||||
@ -17,7 +21,7 @@ You can use these two classes as a starting point for your customizations.
|
|||||||
Here's a brief example on how to add sorting and a new column for a
|
Here's a brief example on how to add sorting and a new column for a
|
||||||
hypothetical `NewsPageHolder` type, which contains `NewsPage` children.
|
hypothetical `NewsPageHolder` type, which contains `NewsPage` children.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// mysite/code/NewsPageHolder.php
|
// mysite/code/NewsPageHolder.php
|
||||||
class NewsPageHolder extends Page {
|
class NewsPageHolder extends Page {
|
||||||
private static $allowed_children = array('NewsPage');
|
private static $allowed_children = array('NewsPage');
|
||||||
@ -30,13 +34,13 @@ hypothetical `NewsPageHolder` type, which contains `NewsPage` children.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
We'll now add an `Extension` subclass to `LeftAndMain`, which is the main CMS controller.
|
```
|
||||||
This allows us to intercept the list building logic, and alter the `GridField`
|
This allows us to intercept the list building logic, and alter the `GridField`
|
||||||
before its rendered. In this case, we limit our logic to the desired page type,
|
before its rendered. In this case, we limit our logic to the desired page type,
|
||||||
although it's just as easy to implement changes which apply to all page types,
|
although it's just as easy to implement changes which apply to all page types,
|
||||||
or across page types with common characteristics.
|
or across page types with common characteristics.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// mysite/code/NewsPageHolderCMSMainExtension.php
|
// mysite/code/NewsPageHolderCMSMainExtension.php
|
||||||
class NewsPageHolderCMSMainExtension extends Extension {
|
class NewsPageHolderCMSMainExtension extends Extension {
|
||||||
function updateListView($listView) {
|
function updateListView($listView) {
|
||||||
@ -62,11 +66,12 @@ or across page types with common characteristics.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Now you just need to enable the extension in your [configuration file](../../configuration).
|
```
|
||||||
|
|
||||||
|
```
|
||||||
// mysite/_config/config.yml
|
// mysite/_config/config.yml
|
||||||
LeftAndMain:
|
LeftAndMain:
|
||||||
extensions:
|
extensions:
|
||||||
- NewsPageHolderCMSMainExtension
|
- NewsPageHolderCMSMainExtension
|
||||||
|
|
||||||
You're all set! Don't forget to flush the caches by appending `?flush=all` to the URL.
|
```
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Customise the CMS tree
|
||||||
|
summary: Learn how to add custom UI elements to the CMS page navigation
|
||||||
|
icon: sitemap
|
||||||
|
---
|
||||||
|
|
||||||
# How to customise the CMS tree
|
# How to customise the CMS tree
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
@ -20,7 +26,7 @@ link that wraps around the node title, a node's id which is given as id attribut
|
|||||||
tags showing the node status, etc. SilverStripe tree node will be typically rendered into html
|
tags showing the node status, etc. SilverStripe tree node will be typically rendered into html
|
||||||
code like this:
|
code like this:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
...
|
...
|
||||||
<ul>
|
<ul>
|
||||||
...
|
...
|
||||||
@ -40,7 +46,7 @@ code like this:
|
|||||||
</ul>
|
</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")
|
![Page Node Screenshot](../../../_images/tree_node.png "Page Node")
|
||||||
|
|
||||||
SiteTree is a [api:DataObject] which is versioned by [api:Versioned] extension.
|
SiteTree is a [api:DataObject] which is versioned by [api:Versioned] extension.
|
||||||
@ -61,7 +67,7 @@ will be used for the class attribute of <li> tag of the tree node.
|
|||||||
### Add new flag
|
### Add new flag
|
||||||
__Example: using a subclass__
|
__Example: using a subclass__
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class Page extends SiteTree {
|
class Page extends SiteTree {
|
||||||
public function getScheduledToPublish(){
|
public function getScheduledToPublish(){
|
||||||
// return either true or false
|
// return either true or false
|
||||||
@ -74,7 +80,7 @@ __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
|
__'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
|
extension point, so the flags can be modified through `DataExtension` rather than
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Customise site reports
|
title: Customise site reports
|
||||||
summary: Creating your own custom data or content reports.
|
summary: Creating your own custom data or content reports.
|
||||||
# Customise site reports
|
---
|
||||||
|
Customise site reports
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
Reports are a useful feature in the CMS designed to provide a view 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
|
||||||
@ -34,7 +36,7 @@ The following example will create a report to list every page on the current sit
|
|||||||
|
|
||||||
###CustomSideReport.php
|
###CustomSideReport.php
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class CustomSideReport_NameOfReport extends SS_Report {
|
class CustomSideReport_NameOfReport extends SS_Report {
|
||||||
|
|
||||||
// the name of the report
|
// the name of the report
|
||||||
@ -57,6 +59,7 @@ The following example will create a report to list every page on the current sit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
More useful reports can be created by changing the `DataList` returned in the `sourceRecords` function.
|
More useful reports can be created by changing the `DataList` returned in the `sourceRecords` function.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Extend the CMS interface
|
||||||
|
summary: Customise the UI of the CMS backend
|
||||||
|
---
|
||||||
|
|
||||||
# How to extend the CMS interface
|
# How to extend the CMS interface
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
@ -28,7 +33,7 @@ Copy the template markup of the base implementation at `framework/admin/template
|
|||||||
into `mysite/templates/Includes/LeftAndMain_Menu.ss`. It will automatically be picked up by
|
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">`
|
the CMS logic. Add a new section into the `<ul class="cms-menu-list">`
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
...
|
...
|
||||||
<ul class="cms-menu-list">
|
<ul class="cms-menu-list">
|
||||||
<!-- ... -->
|
<!-- ... -->
|
||||||
@ -41,7 +46,7 @@ the CMS logic. Add a new section into the `<ul class="cms-menu-list">`
|
|||||||
</ul>
|
</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
|
## Include custom CSS in the CMS
|
||||||
@ -51,25 +56,25 @@ we'll add some CSS, and get it to load
|
|||||||
with the CMS interface. Paste the following content into a new file called
|
with the CMS interface. Paste the following content into a new file called
|
||||||
`mysite/css/BookmarkedPages.css`:
|
`mysite/css/BookmarkedPages.css`:
|
||||||
|
|
||||||
:::css
|
```css
|
||||||
.bookmarked-link.first {margin-top: 1em;}
|
.bookmarked-link.first {margin-top: 1em;}
|
||||||
|
|
||||||
Load the new CSS file into the CMS, by setting the `LeftAndMain.extra_requirements_css`
|
```
|
||||||
[configuration value](../../configuration).
|
[configuration value](../../configuration).
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
LeftAndMain:
|
LeftAndMain:
|
||||||
extra_requirements_css:
|
extra_requirements_css:
|
||||||
- mysite/css/BookmarkedPages.css
|
- mysite/css/BookmarkedPages.css
|
||||||
|
|
||||||
## Create a "bookmark" flag on pages
|
```
|
||||||
|
|
||||||
Now we'll define which pages are actually bookmarked, a flag that is stored in
|
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
|
the database. For this we need to decorate the page record with a
|
||||||
`DataExtension`. Create a new file called `mysite/code/BookmarkedPageExtension.php`
|
`DataExtension`. Create a new file called `mysite/code/BookmarkedPageExtension.php`
|
||||||
and insert the following code.
|
and insert the following code.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class BookmarkedPageExtension extends DataExtension {
|
class BookmarkedPageExtension extends DataExtension {
|
||||||
@ -85,14 +90,14 @@ and insert the following code.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Enable the extension in your [configuration file](../../configuration)
|
```
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
SiteTree:
|
SiteTree:
|
||||||
extensions:
|
extensions:
|
||||||
- BookmarkedPageExtension
|
- BookmarkedPageExtension
|
||||||
|
|
||||||
In order to add the field to the database, run a `dev/build/?flush=all`.
|
```
|
||||||
Refresh the CMS, open a page for editing and you should see the new checkbox.
|
Refresh the CMS, open a page for editing and you should see the new checkbox.
|
||||||
|
|
||||||
## Retrieve the list of bookmarks from the database
|
## Retrieve the list of bookmarks from the database
|
||||||
@ -104,7 +109,7 @@ links)? Again, we extend a core class: The main CMS controller called
|
|||||||
|
|
||||||
Add the following code to a new file `mysite/code/BookmarkedLeftAndMainExtension.php`;
|
Add the following code to a new file `mysite/code/BookmarkedLeftAndMainExtension.php`;
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class BookmarkedPagesLeftAndMainExtension extends LeftAndMainExtension {
|
class BookmarkedPagesLeftAndMainExtension extends LeftAndMainExtension {
|
||||||
@ -114,18 +119,18 @@ Add the following code to a new file `mysite/code/BookmarkedLeftAndMainExtension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Enable the extension in your [configuration file](../../configuration)
|
```
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
LeftAndMain:
|
LeftAndMain:
|
||||||
extensions:
|
extensions:
|
||||||
- BookmarkedPagesLeftAndMainExtension
|
- BookmarkedPagesLeftAndMainExtension
|
||||||
|
|
||||||
As the last step, replace the hardcoded links with our list from the database.
|
```
|
||||||
Find the `<ul>` you created earlier in `mysite/admin/templates/LeftAndMain.ss`
|
Find the `<ul>` you created earlier in `mysite/admin/templates/LeftAndMain.ss`
|
||||||
and replace it with the following:
|
and replace it with the following:
|
||||||
|
|
||||||
:::ss
|
```ss
|
||||||
<ul class="cms-menu-list">
|
<ul class="cms-menu-list">
|
||||||
<!-- ... -->
|
<!-- ... -->
|
||||||
<% loop $BookmarkedPages %>
|
<% loop $BookmarkedPages %>
|
||||||
@ -135,7 +140,7 @@ and replace it with the following:
|
|||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
## Extending the CMS actions
|
```
|
||||||
|
|
||||||
CMS actions follow a principle similar to the CMS fields: they are built in the
|
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
|
backend with the help of `FormFields` and `FormActions`, and the frontend is
|
||||||
@ -165,30 +170,30 @@ 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
|
we are inserting it in the front of all other actions. We could also add a
|
||||||
button group (`CompositeField`) in a similar fashion.
|
button group (`CompositeField`) in a similar fashion.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields->unshift(FormAction::create('normal', 'Normal button'));
|
$fields->unshift(FormAction::create('normal', 'Normal button'));
|
||||||
|
|
||||||
We can affect the existing button group by manipulating the `CompositeField`
|
```
|
||||||
already present in the `FieldList`.
|
already present in the `FieldList`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields->fieldByName('MajorActions')->push(FormAction::create('grouped', 'New group button'));
|
$fields->fieldByName('MajorActions')->push(FormAction::create('grouped', 'New group button'));
|
||||||
|
|
||||||
Another option is adding actions into the drop-up - best place for placing
|
```
|
||||||
infrequently used minor actions.
|
infrequently used minor actions.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields->addFieldToTab('ActionMenus.MoreOptions', FormAction::create('minor', 'Minor action'));
|
$fields->addFieldToTab('ActionMenus.MoreOptions', FormAction::create('minor', 'Minor action'));
|
||||||
|
|
||||||
We can also easily create new drop-up menus by defining new tabs within the
|
```
|
||||||
`TabSet`.
|
`TabSet`.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$fields->addFieldToTab('ActionMenus.MyDropUp', FormAction::create('minor', 'Minor action in a new drop-up'));
|
$fields->addFieldToTab('ActionMenus.MyDropUp', FormAction::create('minor', 'Minor action in a new drop-up'));
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
```
|
||||||
Empty tabs will be automatically removed from the `FieldList` to prevent clutter.
|
Empty tabs will be automatically removed from the `FieldList` to prevent clutter.
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
To make the actions more user-friendly you can also use alternating buttons as
|
To make the actions more user-friendly you can also use alternating buttons as
|
||||||
detailed in the [CMS Alternating Button](cms_alternating_button)
|
detailed in the [CMS Alternating Button](cms_alternating_button)
|
||||||
@ -200,7 +205,7 @@ Your newly created buttons need handlers to bind to before they will do anything
|
|||||||
To implement these handlers, you will need to create a `LeftAndMainExtension` and add
|
To implement these handlers, you will need to create a `LeftAndMainExtension` and add
|
||||||
applicable controller actions to it:
|
applicable controller actions to it:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class CustomActionsExtension extends LeftAndMainExtension {
|
class CustomActionsExtension extends LeftAndMainExtension {
|
||||||
|
|
||||||
private static $allowed_actions = array(
|
private static $allowed_actions = array(
|
||||||
@ -214,19 +219,21 @@ applicable controller actions to it:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
The extension then needs to be registered:
|
The extension then needs to be registered:
|
||||||
|
|
||||||
:::yaml
|
```yaml
|
||||||
LeftAndMain:
|
LeftAndMain:
|
||||||
extensions:
|
extensions:
|
||||||
- CustomActionsExtension
|
- CustomActionsExtension
|
||||||
|
|
||||||
You can now use these handlers with your buttons:
|
|
||||||
|
|
||||||
:::php
|
```
|
||||||
|
|
||||||
|
```php
|
||||||
$fields->push(FormAction::create('sampleAction', 'Perform Sample Action'));
|
$fields->push(FormAction::create('sampleAction', 'Perform Sample Action'));
|
||||||
|
|
||||||
## Summary
|
```
|
||||||
|
|
||||||
In a few lines of code, we've customised the look and feel of the CMS.
|
In a few lines of code, we've customised the look and feel of the CMS.
|
||||||
|
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
|
---
|
||||||
|
title: Extending an existing ModelAdmin
|
||||||
|
summary: ModelAdmin interfaces that come with the core can be customised easily
|
||||||
|
---
|
||||||
|
|
||||||
## Extending existing ModelAdmin
|
## Extending existing ModelAdmin
|
||||||
|
|
||||||
Sometimes you'll work with ModelAdmins from other modules. To customise these interfaces, you can always subclass. But there's
|
Sometimes you'll work with ModelAdmins from other modules. To customise these interfaces, you can always subclass. But there's
|
||||||
also another tool at your disposal: The [api:Extension] API.
|
also another tool at your disposal: The [api:Extension] API.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
class MyAdminExtension extends Extension {
|
class MyAdminExtension extends Extension {
|
||||||
// ...
|
// ...
|
||||||
public function updateEditForm(&$form) {
|
public function updateEditForm(&$form) {
|
||||||
@ -11,12 +16,12 @@ also another tool at your disposal: The [api:Extension] API.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Now enable this extension through your `[config.yml](/topics/configuration)` file.
|
```
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
MyAdmin:
|
MyAdmin:
|
||||||
extensions:
|
extensions:
|
||||||
- MyAdminExtension
|
- MyAdminExtension
|
||||||
|
|
||||||
The following extension points are available: `updateEditForm()`, `updateSearchContext()`,
|
```
|
||||||
`updateSearchForm()`, `updateList()`, `updateImportForm`.
|
`updateSearchForm()`, `updateList()`, `updateImportForm`.
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: How To's
|
||||||
|
---
|
||||||
|
# How To's: Customising the Admin Interface
|
||||||
|
|
||||||
|
[CHILDREN]
|
@ -1,7 +1,9 @@
|
|||||||
|
---
|
||||||
title: Customising the Admin Interface
|
title: Customising the Admin Interface
|
||||||
summary: Extend the admin view to provide custom behavior or new features for CMS and admin users.
|
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.
|
introduction: The Admin interface can be extended to provide additional functionality to users and custom interfaces for managing data.
|
||||||
|
icon: react
|
||||||
|
---
|
||||||
The Admin interface is bundled within the SilverStripe Framework but is most commonly used in conjunction with the `CMS`
|
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
|
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.
|
as it is designed around a left hand navigation and a main edit form.
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
---
|
||||||
title: Flushable
|
title: Flushable
|
||||||
summary: Allows a class to define it's own flush functionality.
|
summary: Allows a class to define it's own flush functionality.
|
||||||
|
---
|
||||||
# Flushable
|
Flushable
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ this defines the actions that need to be executed on a flush request.
|
|||||||
|
|
||||||
This example uses [api:SS_Cache] in some custom code, and the same cache is cleaned on flush:
|
This example uses [api:SS_Cache] in some custom code, and the same cache is cleaned on flush:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class MyClass extends DataObject implements Flushable {
|
class MyClass extends DataObject implements Flushable {
|
||||||
|
|
||||||
@ -38,13 +39,13 @@ This example uses [api:SS_Cache] in some custom code, and the same cache is clea
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
### Using with filesystem
|
```
|
||||||
|
|
||||||
Another example, some temporary files are created in a directory in assets, and are deleted on flush. This would be
|
Another example, some temporary files are created in a directory in assets, and are deleted on flush. This would be
|
||||||
useful in an example like `GD` or `Imagick` generating resampled images, but we want to delete any cached images on
|
useful in an example like `GD` or `Imagick` generating resampled images, but we want to delete any cached images on
|
||||||
flush so they are re-created on demand.
|
flush so they are re-created on demand.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
class MyClass extends DataObject implements Flushable {
|
class MyClass extends DataObject implements Flushable {
|
||||||
|
|
||||||
@ -56,3 +57,4 @@ flush so they are re-created on demand.
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: Manifests
|
title: Manifests
|
||||||
summary: Manage caches of file path maps and other expensive information
|
summary: Manage caches of file path maps and other expensive information
|
||||||
|
---
|
||||||
# Manifests
|
# Manifests
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Execution pipeline
|
||||||
summary: An overview of the steps involved in delivering a SilverStripe web page.
|
summary: An overview of the steps involved in delivering a SilverStripe web page.
|
||||||
|
icon: route
|
||||||
|
---
|
||||||
# Execution Pipeline
|
# Execution Pipeline
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
@ -20,6 +23,7 @@ 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.
|
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`.
|
If no file can be directly matched, control is handed off to `framework/main.php`.
|
||||||
|
|
||||||
|
```
|
||||||
### SILVERSTRIPE START ###
|
### SILVERSTRIPE START ###
|
||||||
|
|
||||||
# Deny access to templates (but allow from localhost)
|
# Deny access to templates (but allow from localhost)
|
||||||
@ -68,13 +72,13 @@ If no file can be directly matched, control is handed off to `framework/main.php
|
|||||||
</IfModule>
|
</IfModule>
|
||||||
### SILVERSTRIPE END ###
|
### 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.
|
through an `index.php` script in the webroot.
|
||||||
|
|
||||||
<div class="notice" markdown="1">
|
[notice]
|
||||||
Running SilverStripe without web server based rewriting is not recommended since it
|
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).
|
can leave sensitive files exposed to public access (the `RewriteRule` conditions from above don't apply).
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Bootstrap
|
## Bootstrap
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
---
|
||||||
title: Command Line Interface
|
title: Command Line Interface
|
||||||
summary: Automate SilverStripe, run Cron Jobs or sync with other platforms through the 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.
|
introduction: Automate SilverStripe, run Cron Jobs or sync with other platforms through the Command Line Interface.
|
||||||
|
icon: terminal
|
||||||
|
---
|
||||||
SilverStripe can call [Controllers](../controllers) through a command line interface (CLI) just as easily as through a
|
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
|
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.
|
needs to interface over the command line.
|
||||||
@ -9,36 +11,37 @@ 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
|
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:
|
rebuild from the command line, use this command:
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
cd your-webroot/
|
cd your-webroot/
|
||||||
php framework/cli-script.php dev/build
|
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
|
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
|
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.
|
to have.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Sake - SilverStripe Make
|
## 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
|
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.
|
are available.
|
||||||
|
|
||||||
<div class="info" markdown='1'>
|
[info]
|
||||||
If you are using a Debian server: Check you have the php-cli package installed for sake to work. If you get an error
|
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.
|
when running the command php -v, then you may not have php-cli installed so sake won't work.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
`sake` can be invoked using `./framework/sake`. For easier access, copy the `sake` file into `/usr/bin/sake`.
|
`sake` can be invoked using `./framework/sake`. For easier access, copy the `sake` file into `/usr/bin/sake`.
|
||||||
|
|
||||||
|
```
|
||||||
cd your-webroot/
|
cd your-webroot/
|
||||||
sudo ./framework/sake installsake
|
sudo ./framework/sake installsake
|
||||||
|
|
||||||
<div class="warning">
|
```
|
||||||
This currently only works on UNIX like systems, not on Windows.
|
This currently only works on UNIX like systems, not on Windows.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
@ -47,50 +50,50 @@ files. When you're visiting the site in a web browser this is easy to work out,
|
|||||||
command line, it has no way of knowing. To work this out, add lines to your
|
command line, it has no way of knowing. To work this out, add lines to your
|
||||||
[_ss_environment.php](/getting_started/environment_management) file.
|
[_ss_environment.php](/getting_started/environment_management) file.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
global $_FILE_TO_URL_MAPPING;
|
global $_FILE_TO_URL_MAPPING;
|
||||||
|
|
||||||
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites'] = 'http://localhost';
|
$_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
|
base URL `http://localhost`. The site `/Users/sminnee/Sites/my_silverstripe_project` will translate to the URL
|
||||||
`http://localhost/my_silverstripe_project`.
|
`http://localhost/my_silverstripe_project`.
|
||||||
|
|
||||||
You can add multiple file to url mapping definitions. The most specific mapping will be used.
|
You can add multiple file to url mapping definitions. The most specific mapping will be used.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
global $_FILE_TO_URL_MAPPING;
|
global $_FILE_TO_URL_MAPPING;
|
||||||
|
|
||||||
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites'] = 'http://localhost';
|
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites'] = 'http://localhost';
|
||||||
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites/my_silverstripe_project'] = 'http://project.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.
|
Sake can run any controller by passing the relative URL to that controller.
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
sake /
|
sake /
|
||||||
# returns the homepage
|
# returns the homepage
|
||||||
|
|
||||||
sake dev/
|
sake dev/
|
||||||
# shows a list of development operations
|
# shows a list of development operations
|
||||||
|
|
||||||
Sake is particularly useful for running build tasks.
|
```
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
sake dev/build "flush=1"
|
sake dev/build "flush=1"
|
||||||
|
|
||||||
Or running unit tests..
|
```
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
sake dev/tests/all
|
sake dev/tests/all
|
||||||
|
|
||||||
It can also be handy if you have a long running script..
|
```
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
sake dev/tasks/MyReallyLongTask
|
sake dev/tasks/MyReallyLongTask
|
||||||
|
|
||||||
### Running processes
|
```
|
||||||
|
|
||||||
`sake` can be used to make daemon processes for your application.
|
`sake` can be used to make daemon processes for your application.
|
||||||
|
|
||||||
@ -102,7 +105,7 @@ sleep when the process is in the middle of doing things, and a long sleep when d
|
|||||||
|
|
||||||
This code provides a good template:
|
This code provides a good template:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class MyProcess extends Controller {
|
class MyProcess extends Controller {
|
||||||
@ -125,37 +128,37 @@ This code provides a good template:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Then the process can be managed through `sake`
|
```
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
sake -start MyProcess
|
sake -start MyProcess
|
||||||
sake -stop MyProcess
|
sake -stop MyProcess
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
[notice]
|
||||||
<div class="notice">
|
|
||||||
`sake` stores `pid` and log files in the site root directory.
|
`sake` stores `pid` and log files in the site root directory.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## Arguments
|
## Arguments
|
||||||
|
|
||||||
Parameters can be added to the command. All parameters will be available in `$_GET` array on the server.
|
Parameters can be added to the command. All parameters will be available in `$_GET` array on the server.
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
cd your-webroot/
|
cd your-webroot/
|
||||||
php framework/cli-script.php myurl myparam=1 myotherparam=2
|
php framework/cli-script.php myurl myparam=1 myotherparam=2
|
||||||
|
|
||||||
Or if you're using `sake`
|
```
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
sake myurl "myparam=1&myotherparam=2"
|
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
|
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`.
|
`BuildTask` in SilverStripe as a cron job using `sake`.
|
||||||
|
|
||||||
The following will run `MyTask` every minute.
|
The following will run `MyTask` every minute.
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
* * * * * /your/site/folder/sake dev/tasks/MyTask
|
```
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Cookies
|
title: Cookies
|
||||||
summary: A set of static methods for manipulating PHP cookies.
|
summary: A set of static methods for manipulating PHP cookies.
|
||||||
|
icon: cookie-bite
|
||||||
|
---
|
||||||
# Cookies
|
# Cookies
|
||||||
|
|
||||||
Cookies are a mechanism for storing data in the remote browser and thus tracking or identifying return users.
|
Cookies are a mechanism for storing data in the remote browser and thus tracking or identifying return users.
|
||||||
@ -12,31 +14,31 @@ the [api:Cookie] class. This class mostly follows the PHP API.
|
|||||||
|
|
||||||
Sets the value of cookie with configuration.
|
Sets the value of cookie with configuration.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Cookie::set($name, $value, $expiry = 90, $path = null, $domain = null, $secure = false, $httpOnly = false);
|
Cookie::set($name, $value, $expiry = 90, $path = null, $domain = null, $secure = false, $httpOnly = false);
|
||||||
|
|
||||||
// Cookie::set('MyApplicationPreference', 'Yes');
|
// Cookie::set('MyApplicationPreference', 'Yes');
|
||||||
|
|
||||||
## get
|
```
|
||||||
|
|
||||||
Returns the value of cookie.
|
Returns the value of cookie.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Cookie::get($name);
|
Cookie::get($name);
|
||||||
|
|
||||||
// Cookie::get('MyApplicationPreference');
|
// Cookie::get('MyApplicationPreference');
|
||||||
// returns 'Yes'
|
// returns 'Yes'
|
||||||
|
|
||||||
## force_expiry
|
```
|
||||||
|
|
||||||
Clears a given cookie.
|
Clears a given cookie.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Cookie::force_expiry($name, $path = null, $domain = null);
|
Cookie::force_expiry($name, $path = null, $domain = null);
|
||||||
|
|
||||||
// Cookie::force_expiry('MyApplicationPreference')
|
// Cookie::force_expiry('MyApplicationPreference')
|
||||||
|
|
||||||
|
```
|
||||||
## Cookie_Backend
|
## Cookie_Backend
|
||||||
|
|
||||||
The [api:Cookie] class manipulates and sets cookies using a [api:Cookie_Backend]. The backend is in charge of the logic
|
The [api:Cookie] class manipulates and sets cookies using a [api:Cookie_Backend]. The backend is in charge of the logic
|
||||||
@ -46,7 +48,7 @@ that fetches, sets and expires cookies. By default we use a [api:CookieJar] back
|
|||||||
The [api:CookieJar] keeps track of cookies that have been set by the current process as well as those that were received
|
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.
|
from the browser.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
$myCookies = array(
|
$myCookies = array(
|
||||||
'cookie1' => 'value1',
|
'cookie1' => 'value1',
|
||||||
);
|
);
|
||||||
@ -57,43 +59,45 @@ from the browser.
|
|||||||
|
|
||||||
Cookie::get('cookie1');
|
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
|
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
|
`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.
|
create a new service for you using the `$_COOKIE` superglobal.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Injector::inst()->unregisterNamedObject('Cookie_Backend');
|
Injector::inst()->unregisterNamedObject('Cookie_Backend');
|
||||||
|
|
||||||
Cookie::get('cookiename'); // will return $_COOKIE['cookiename'] if set
|
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
|
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.
|
the current `CookieJar` service to tell you what it was like when it was registered.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
//store the cookies that were loaded into the `CookieJar`
|
//store the cookies that were loaded into the `CookieJar`
|
||||||
$recievedCookie = Cookie::get_inst()->getAll(false);
|
$recievedCookie = Cookie::get_inst()->getAll(false);
|
||||||
|
|
||||||
//set a new `CookieJar`
|
//set a new `CookieJar`
|
||||||
Injector::inst()->registerService(new CookieJar($recievedCookie), 'CookieJar');
|
Injector::inst()->registerService(new CookieJar($recievedCookie), 'CookieJar');
|
||||||
|
|
||||||
|
```
|
||||||
### Using your own Cookie_Backend
|
### 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.
|
If you need to implement your own Cookie_Backend you can use the injector system to force a different class to be used.
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
---
|
```
|
||||||
|
```
|
||||||
Name: mycookie
|
Name: mycookie
|
||||||
After: '#cookie'
|
After: '#cookie'
|
||||||
---
|
```
|
||||||
|
```
|
||||||
Injector:
|
Injector:
|
||||||
Cookie_Backend:
|
Cookie_Backend:
|
||||||
class: MyCookieJar
|
class: MyCookieJar
|
||||||
|
|
||||||
To be a valid backend your class must implement the [api:Cookie_Backend] interface.
|
```
|
||||||
|
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Sessions
|
title: Sessions
|
||||||
summary: A set of static methods for manipulating PHP sessions.
|
summary: A set of static methods for manipulating PHP sessions.
|
||||||
|
icon: user
|
||||||
|
---
|
||||||
# Sessions
|
# Sessions
|
||||||
|
|
||||||
Session support in PHP consists of a way to preserve certain data across subsequent accesses such as logged in user
|
Session support in PHP consists of a way to preserve certain data across subsequent accesses such as logged in user
|
||||||
@ -12,13 +14,13 @@ unit-testing, you can create multiple Controllers, each with their own session.
|
|||||||
|
|
||||||
## set
|
## set
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Session::set('MyValue', 6);
|
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).
|
size restrictions as to how much you can save).
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
// saves an array
|
// saves an array
|
||||||
Session::set('MyArrayOfValues', array('1','2','3'));
|
Session::set('MyArrayOfValues', array('1','2','3'));
|
||||||
|
|
||||||
@ -26,12 +28,12 @@ size restrictions as to how much you can save).
|
|||||||
$object = new Object();
|
$object = new Object();
|
||||||
Session::set('MyObject', serialize($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
|
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.
|
can use this anywhere in your PHP files.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
echo Session::get('MyValue');
|
echo Session::get('MyValue');
|
||||||
// returns 6
|
// returns 6
|
||||||
|
|
||||||
@ -41,37 +43,37 @@ can use this anywhere in your PHP files.
|
|||||||
$object = unserialize(Session::get('MyObject', $object));
|
$object = unserialize(Session::get('MyObject', $object));
|
||||||
// $object = Object()
|
// $object = Object()
|
||||||
|
|
||||||
## get_all
|
```
|
||||||
|
|
||||||
You can also get all the values in the session at once. This is useful for debugging.
|
You can also get all the values in the session at once. This is useful for debugging.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Session::get_all();
|
Session::get_all();
|
||||||
// returns an array of all the session values.
|
// 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
|
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.
|
to specifically remove it.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Session::clear('MyValue');
|
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.
|
including form and page comment information. None of this is vital but `clear_all` will clear everything.
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Session::clear_all();
|
Session::clear_all();
|
||||||
|
|
||||||
## Secure Session Cookie
|
```
|
||||||
|
|
||||||
In certain circumstances, you may want to use a different `session_name` cookie when using the `https` protocol for security purposes. To do this, you may set the `cookie_secure` parameter to `true` on your `config.yml`
|
In certain circumstances, you may want to use a different `session_name` cookie when using the `https` protocol for security purposes. To do this, you may set the `cookie_secure` parameter to `true` on your `config.yml`
|
||||||
|
|
||||||
:::yml
|
```yml
|
||||||
Session:
|
Session:
|
||||||
cookie_secure: true
|
cookie_secure: true
|
||||||
|
|
||||||
This uses the session_name `SECSESSID` for `https` connections instead of the default `PHPSESSID`. Doing so adds an extra layer of security to your session cookie since you no longer share `http` and `https` sessions.
|
```
|
||||||
|
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
---
|
||||||
title: Cookies and Sessions
|
title: Cookies and Sessions
|
||||||
summary: Save state information using the Cookie class and the Session class.
|
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.
|
introduction: Both the Cookie and Session classes can be used to preserve certain data across subsequent page requests.
|
||||||
|
icon: cookie
|
||||||
|
---
|
||||||
[CHILDREN]
|
[CHILDREN]
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: Developer Guides
|
title: Developer Guides
|
||||||
introduction: The following guides take a more detailed look into the core concepts and code examples for building SilverStripe applications.
|
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
|
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.
|
snippets of code to use in your own projects.
|
||||||
|
|
||||||
|
@ -1,23 +1,19 @@
|
|||||||
|
---
|
||||||
title: Upgrading
|
title: Upgrading
|
||||||
introduction: Keep your SilverStripe installations up to date with the latest fixes, security patches and new features.
|
summary: The following guides will help you upgrade your project or module to SilverStripe 4.
|
||||||
|
---
|
||||||
# Upgrading to SilverStripe 3.2
|
# Upgrading to SilverStripe 3.2
|
||||||
|
|
||||||
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.
|
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">
|
[info]
|
||||||
See our [upgrade notes and changelogs](/changelogs/3.2.0) for 3.2.0 specific information, bugfixes and API changes.
|
See our [upgrade notes and changelogs](/changelogs/3.2.0) for 3.2.0 specific information, bugfixes and API changes.
|
||||||
</div>
|
[/info]
|
||||||
|
|
||||||
## Composer
|
## Composer
|
||||||
|
|
||||||
For projects managed through Composer, update the version number of `framework` and `cms` to `^3.2` in your `composer.json` file and run `composer update`.
|
For projects managed through Composer, update the version number of `framework` and `cms` to `^3.2` in your `composer.json` file and run `composer update`.
|
||||||
|
|
||||||
```json
|
|
||||||
"require": {
|
|
||||||
"silverstripe/framework": "^3.2",
|
|
||||||
"silverstripe/cms": "^3.2"
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
This will also add extra dependencies, the `reports` and `siteconfig` modules. SilverStripe CMS is becoming more modular, and [composer is becoming the preferred way to manage your code](/getting_started/composer).
|
This will also add extra dependencies, the `reports` and `siteconfig` modules. SilverStripe CMS is becoming more modular, and [composer is becoming the preferred way to manage your code](/getting_started/composer).
|
||||||
|
|
||||||
@ -36,9 +32,9 @@ This will also add extra dependencies, the `reports` and `siteconfig` modules. S
|
|||||||
* Check if you need to adapt your code to changed PHP APIs
|
* 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.
|
* Check if you have overwritten any core templates or styles which might need an update.
|
||||||
|
|
||||||
<div class="warning" markdown="1">
|
[warning]
|
||||||
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>
|
[/warning]
|
||||||
|
|
||||||
## Decision Helpers
|
## Decision Helpers
|
||||||
|
|
||||||
|
@ -226,12 +226,12 @@ Some examples of changed notations (not exhaustive, there's over a hundred in to
|
|||||||
* `Director::setBaseURL`: Use `Director.alternate_base_url` instead
|
* `Director::setBaseURL`: Use `Director.alternate_base_url` instead
|
||||||
* `SSViewer::setOption('rewriteHashlinks', ...)`: Use `SSViewer.rewrite_hashlinks` instead
|
* `SSViewer::setOption('rewriteHashlinks', ...)`: Use `SSViewer.rewrite_hashlinks` instead
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
[warning]
|
||||||
Please remember to upgrade the installer project as well, particularly
|
Please remember to upgrade the installer project as well, particularly
|
||||||
your `.htaccess` or `web.config` files. Web access to these sensitive YAML configuration files
|
your `.htaccess` or `web.config` files. Web access to these sensitive YAML configuration files
|
||||||
needs to be explicitly denied through these configuration files (see the [3.0.5 security release](/changelogs/3.0.4))
|
needs to be explicitly denied through these configuration files (see the [3.0.5 security release](/changelogs/3.0.4))
|
||||||
for details.
|
for details.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
For more information about how to use the config system, see the ["Configuration" topic](/developer_guides/configuration).
|
For more information about how to use the config system, see the ["Configuration" topic](/developer_guides/configuration).
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Changelogs
|
title: Changelogs
|
||||||
introduction: Key information on new features and improvements in each version.
|
introduction: Key information on new features and improvements in each version.
|
||||||
|
hideChildren: true
|
||||||
|
---
|
||||||
Keep up to date with new releases by subscribing to the [SilverStripe Release Announcements](https://groups.google.com/group/silverstripe-announce) group,
|
Keep up to date with new releases by subscribing 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).
|
or read our [blog posts about releases](http://silverstripe.org/blog/tag/release).
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Bug Reports
|
title: Bug Reports
|
||||||
summary: Report bugs or problems with SilverStripe, feature requests or other issues.
|
summary: Report bugs or problems with SilverStripe, feature requests or other issues.
|
||||||
|
icon: bug
|
||||||
|
---
|
||||||
# Contributing Issues and Opinions
|
# Contributing Issues and Opinions
|
||||||
|
|
||||||
## Reporting Bugs
|
## Reporting Bugs
|
||||||
@ -39,13 +41,13 @@ problem can collaborate with you to develop a fix.
|
|||||||
|
|
||||||
## Feature Requests
|
## Feature Requests
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
[warning]
|
||||||
Please don't file "feature requests" as Github issues. If there's a new feature
|
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
|
you'd like to see in SilverStripe, you either need to write it yourself (and
|
||||||
[submit a pull request](/contributing/code/#step-by-step-from-forking-to-sending-the-pull-request) or convince somebody else to
|
[submit a pull request](/contributing/code/#step-by-step-from-forking-to-sending-the-pull-request) or convince somebody else to
|
||||||
write it for you. Any "wishlist" type issues without code attached can be
|
write it for you. Any "wishlist" type issues without code attached can be
|
||||||
expected to be closed as soon as they're reviewed.
|
expected to be closed as soon as they're reviewed.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
In order to gain interest and feedback in your feature, we encourage you to
|
In order to gain interest and feedback in your feature, we encourage you to
|
||||||
present it to the community through the [forums](http://www.silverstripe.org/community/forums),
|
present it to the community through the [forums](http://www.silverstripe.org/community/forums),
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Contributing Code
|
title: Contributing Code
|
||||||
summary: Fix bugs and add new features to help make SilverStripe better.
|
summary: Fix bugs and add new features to help make SilverStripe better.
|
||||||
|
icon: code
|
||||||
|
---
|
||||||
# Contributing Code - Submitting Bugfixes and Enhancements
|
# Contributing Code - Submitting Bugfixes and Enhancements
|
||||||
|
|
||||||
SilverStripe will never be finished, and we need your help to keep making it better. If you're a developer a great way to get involved is to contribute patches to our modules and core codebase, fixing bugs or adding features.
|
SilverStripe will never be finished, and we need your help to keep making it better. If you're a developer a great way to get involved is to contribute patches to our modules and core codebase, fixing bugs or adding features.
|
||||||
@ -11,17 +13,17 @@ which creates a copy that you can commit to (see github's [guide to "forking"](h
|
|||||||
|
|
||||||
For other modules, our [add-ons site](https://addons.silverstripe.org/add-ons/) lists the repository locations, typically using the version control system like "git".
|
For other modules, our [add-ons site](https://addons.silverstripe.org/add-ons/) lists the repository locations, typically using the version control system like "git".
|
||||||
|
|
||||||
<div class="hint" markdown="1">
|
[hint]
|
||||||
Note: By supplying code to the SilverStripe core team in patches, tickets and pull requests, you agree to assign copyright of that code to SilverStripe Limited, on the condition that SilverStripe Limited releases that code under the BSD license.
|
Note: By supplying code to the SilverStripe core team in patches, tickets and pull requests, you agree to assign copyright of that code to SilverStripe Limited, on the condition that SilverStripe Limited releases that code under the BSD license.
|
||||||
|
|
||||||
We ask for this so that the ownership in the license is clear and unambiguous, and so that community involvement doesn't stop us from being able to continue supporting these projects. By releasing this code under a permissive license, this copyright assignment won't prevent you from using the code in any way you see fit.
|
We ask for this so that the ownership in the license is clear and unambiguous, and so that community involvement doesn't stop us from being able to continue supporting these projects. By releasing this code under a permissive license, this copyright assignment won't prevent you from using the code in any way you see fit.
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
## Step-by-step: From forking to sending the pull request
|
## Step-by-step: From forking to sending the pull request
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
[notice]
|
||||||
**Note:** Please adjust the commands below to the version of SilverStripe that you're targeting.
|
**Note:** Please adjust the commands below to the version of SilverStripe that you're targeting.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
1. Install the project through composer. The process is described in detail in "[Installation through Composer](../getting_started/composer#contributing)".
|
1. Install the project through composer. The process is described in detail in "[Installation through Composer](../getting_started/composer#contributing)".
|
||||||
|
|
||||||
@ -32,6 +34,7 @@ We ask for this so that the ownership in the license is clear and unambiguous, a
|
|||||||
Add your fork URLs, in this example a fork of the `cms` module on the `sminnee` github account
|
Add your fork URLs, in this example a fork of the `cms` module on the `sminnee` github account
|
||||||
(replace with your own fork URL). Run a `composer update` afterwards.
|
(replace with your own fork URL). Run a `composer update` afterwards.
|
||||||
|
|
||||||
|
```
|
||||||
"repositories": [
|
"repositories": [
|
||||||
{
|
{
|
||||||
"type": "vcs",
|
"type": "vcs",
|
||||||
@ -39,34 +42,39 @@ We ask for this so that the ownership in the license is clear and unambiguous, a
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
3. Add a new "upstream" remote so you can track the original repository for changes, and rebase/merge your fork as required.
|
```
|
||||||
|
|
||||||
|
```
|
||||||
cd cms
|
cd cms
|
||||||
git remote add -f upstream git://github.com/silverstripe/silverstripe-cms.git
|
git remote add -f upstream git://github.com/silverstripe/silverstripe-cms.git
|
||||||
|
|
||||||
4. [Branch for new issue and develop on issue branch](code#branch-for-new-issue-and-develop-on-issue-branch)
|
```
|
||||||
|
|
||||||
|
```
|
||||||
# verify current branch 'base' then branch and switch
|
# verify current branch 'base' then branch and switch
|
||||||
git status
|
git status
|
||||||
git branch ###-description
|
git branch ###-description
|
||||||
git checkout ###-description
|
git checkout ###-description
|
||||||
|
|
||||||
5. As time passes, the upstream repository accumulates new commits. Keep your working copy's branch and issue branch up to date by periodically [rebasing your development branch on the latest upstream](code#rebase-your-development-branch-on-the-latest-upstream).
|
```
|
||||||
|
|
||||||
|
```
|
||||||
# [make sure all your changes are committed as necessary in branch]
|
# [make sure all your changes are committed as necessary in branch]
|
||||||
git fetch upstream
|
git fetch upstream
|
||||||
git rebase upstream/3.2
|
git rebase upstream/3.2
|
||||||
|
|
||||||
6. When development is complete, [squash all commit related to a single issue into a single commit](code#squash-all-commits-related-to-a-single-issue-into-a-single-commit).
|
```
|
||||||
|
|
||||||
|
```
|
||||||
git fetch upstream
|
git fetch upstream
|
||||||
git rebase -i upstream/3.2
|
git rebase -i upstream/3.2
|
||||||
|
|
||||||
7. Push release candidate branch to GitHub
|
```
|
||||||
|
|
||||||
|
```
|
||||||
git push origin ###-description
|
git push origin ###-description
|
||||||
|
|
||||||
8. Issue pull request on GitHub. Visit your forked repository on GitHub.com and click the "Create Pull Request" button next to the new branch.
|
```
|
||||||
|
|
||||||
The core team is then responsible for reviewing patches and deciding if they will make it into core. If
|
The core team is then responsible for reviewing patches and deciding if they will make it into core. If
|
||||||
there are any problems they will follow up with you, so please ensure they have a way to contact you!
|
there are any problems they will follow up with you, so please ensure they have a way to contact you!
|
||||||
@ -83,11 +91,13 @@ Each release is labeled in the format `$MAJOR`.`$MINOR`.`$PATCH`. For example, 3
|
|||||||
|
|
||||||
* `$MAJOR` version is incremented if any backwards incompatible changes are introduced to the public API.
|
* `$MAJOR` version is incremented if any backwards incompatible changes are introduced to the public API.
|
||||||
* `$MINOR` version is incremented if new, backwards compatible **functionality** is introduced to the public API or
|
* `$MINOR` version is incremented if new, backwards compatible **functionality** is introduced to the public API or
|
||||||
|
```
|
||||||
improvements are introduced within the private code.
|
improvements are introduced within the private code.
|
||||||
* `$PATCH` version is incremented if only backwards compatible **bug fixes** are introduced. A bug fix is defined as
|
```
|
||||||
|
```
|
||||||
an internal change that fixes incorrect behavior.
|
an internal change that fixes incorrect behavior.
|
||||||
|
|
||||||
**Public API** refers to any aspect of the system that has been designed to be used by SilverStripe modules & site developers. In SilverStripe 3, because we haven't been clear, in principle we have to treat every public or protected method as *potentially* part of the public API, but sometimes it comes to a judgement call about how likely it is that a given method will have been used in a particular way. If we were strict about never changing publicly exposed behaviour, it would be difficult to fix any bug whatsoever, which isn't in the interests of our user community.
|
```
|
||||||
|
|
||||||
In future major releases of SilverStripe, we will endeavour to be more explicit about documenting the public API.
|
In future major releases of SilverStripe, we will endeavour to be more explicit about documenting the public API.
|
||||||
|
|
||||||
@ -214,37 +224,42 @@ Further guidelines:
|
|||||||
|
|
||||||
Example: Bad commit message
|
Example: Bad commit message
|
||||||
|
|
||||||
|
```
|
||||||
finally fixed this dumb rendering bug that Joe talked about ... LOL
|
finally fixed this dumb rendering bug that Joe talked about ... LOL
|
||||||
also added another form field for password validation
|
also added another form field for password validation
|
||||||
|
|
||||||
Example: Good commit message
|
```
|
||||||
|
|
||||||
|
```
|
||||||
BUG Formatting through prepValueForDB()
|
BUG Formatting through prepValueForDB()
|
||||||
|
|
||||||
Added prepValueForDB() which is called on DBField->writeToManipulation()
|
Added prepValueForDB() which is called on DBField->writeToManipulation()
|
||||||
to ensure formatting of value before insertion to DB on a per-DBField type basis (fixes #1234).
|
to ensure formatting of value before insertion to DB on a per-DBField type basis (fixes #1234).
|
||||||
Added documentation for DBField->writeToManipulation() (related to a4bd42fd).
|
Added documentation for DBField->writeToManipulation() (related to a4bd42fd).
|
||||||
|
|
||||||
## The steps in more detail
|
```
|
||||||
|
|
||||||
### Branch for new issue and develop on issue branch
|
### Branch for new issue and develop on issue branch
|
||||||
|
|
||||||
Before you start working on a new feature or bugfix, create a new branch dedicated to that one change named by issue number and description. If you're working on Issue #100, a `DataObject::get_one()` bugfix, create a new branch with the issue number and description, like this:
|
Before you start working on a new feature or bugfix, create a new branch dedicated to that one change named by issue number and description. If you're working on Issue #100, a `DataObject::get_one()` bugfix, create a new branch with the issue number and description, like this:
|
||||||
|
|
||||||
|
```
|
||||||
$ git checkout -b 100-dataobject-get-one
|
$ git checkout -b 100-dataobject-get-one
|
||||||
|
|
||||||
Edit and test the files on your development environment. When you've got something the way you want and established that it works, commit the changes to your branch on your local git repo.
|
```
|
||||||
|
|
||||||
|
```
|
||||||
$ git add <filename>
|
$ git add <filename>
|
||||||
$ git commit -m 'Some kind of descriptive message (fixes #100)'
|
$ git commit -m 'Some kind of descriptive message (fixes #100)'
|
||||||
|
|
||||||
You'll need to use git add for each file that you created or modified. There are ways to add multiple files, but I highly recommend a more deliberate approach unless you know what you're doing.
|
```
|
||||||
|
|
||||||
Then, you can push your new branch to GitHub, like this (replace `100-dataobject-get-one` with your branch name):
|
Then, you can push your new branch to GitHub, like this (replace `100-dataobject-get-one` with your branch name):
|
||||||
|
|
||||||
|
```
|
||||||
$ git push origin 100-dataobject-get-one
|
$ git push origin 100-dataobject-get-one
|
||||||
|
|
||||||
You should be able to log into your GitHub account, switch to the branch, and see that your changes have been committed. Then click the Pull button to request that your commits get merged into the development master.
|
```
|
||||||
|
|
||||||
### Rebase Your Development Branch on the Latest Upstream
|
### Rebase Your Development Branch on the Latest Upstream
|
||||||
|
|
||||||
@ -252,12 +267,14 @@ To keep your development branch up to date, rebase your changes on top of the cu
|
|||||||
|
|
||||||
If you've set up an upstream branch as detailed above, and a development branch called `100-dataobject-get-one`, you can update `upstream` and rebase your branch from it like so:
|
If you've set up an upstream branch as detailed above, and a development branch called `100-dataobject-get-one`, you can update `upstream` and rebase your branch from it like so:
|
||||||
|
|
||||||
|
```
|
||||||
# make sure all your changes are committed as necessary in branch
|
# make sure all your changes are committed as necessary in branch
|
||||||
$ git fetch upstream
|
$ git fetch upstream
|
||||||
$ git rebase upstream/master
|
$ git rebase upstream/master
|
||||||
|
|
||||||
Note that the example doesn't keep your own master branch up to date. If you wanted to that, you might take the following approach instead:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
# make sure all your changes are committed as necessary in branch
|
# make sure all your changes are committed as necessary in branch
|
||||||
$ git fetch upstream
|
$ git fetch upstream
|
||||||
$ git checkout master
|
$ git checkout master
|
||||||
@ -265,22 +282,24 @@ Note that the example doesn't keep your own master branch up to date. If you wa
|
|||||||
$ git checkout 100-dataobject-get-one
|
$ git checkout 100-dataobject-get-one
|
||||||
$ git rebase master
|
$ git rebase master
|
||||||
|
|
||||||
You may need to resolve conflicts that occur when a file on the development trunk and one of your files have both been changed. Edit each file to resolve the differences, then commit the fixes to your development server repo and test. Each file will need to be "added" before running a "commit."
|
```
|
||||||
|
|
||||||
Conflicts are clearly marked in the code files. Make sure to take time in determining what version of the conflict you want to keep and what you want to discard.
|
Conflicts are clearly marked in the code files. Make sure to take time in determining what version of the conflict you want to keep and what you want to discard.
|
||||||
|
|
||||||
|
```
|
||||||
$ git add <filename>
|
$ git add <filename>
|
||||||
$ git rebase --continue
|
$ git rebase --continue
|
||||||
|
|
||||||
### Squash All Commits Related to a Single Issue into a Single Commit
|
```
|
||||||
|
|
||||||
Once you have rebased your work on top of the latest state of the upstream master, you may have several commits related to the issue you were working on. Once everything is done, squash them into a single commit with a descriptive message (see ["Contributing: Commit Messages"](code#commit-messages)).
|
Once you have rebased your work on top of the latest state of the upstream master, you may have several commits related to the issue you were working on. Once everything is done, squash them into a single commit with a descriptive message (see ["Contributing: Commit Messages"](code#commit-messages)).
|
||||||
|
|
||||||
To squash four commits into one, do the following:
|
To squash four commits into one, do the following:
|
||||||
|
|
||||||
|
```
|
||||||
$ git rebase -i upstream/master
|
$ git rebase -i upstream/master
|
||||||
|
|
||||||
In the text editor that comes up, replace the words "pick" with "squash" or just "s" next to the commits you want to squash into the commit before it.
|
```
|
||||||
Save and close the editor, and git will combine the "squash"'ed commits with the one before it.
|
Save and close the editor, and git will combine the "squash"'ed commits with the one before it.
|
||||||
Git will then give you the opportunity to change your commit message to something like, `BUG DataObject::get_one() parameter order (fixes #100)`.
|
Git will then give you the opportunity to change your commit message to something like, `BUG DataObject::get_one() parameter order (fixes #100)`.
|
||||||
|
|
||||||
@ -288,29 +307,34 @@ If you want to discard the commit messages from the commits you're squashing and
|
|||||||
|
|
||||||
Important: If you've already pushed commits to GitHub, and then squash them locally, you will have to force-push to your GitHub again. Add the `-f` argument to your git push command:
|
Important: If you've already pushed commits to GitHub, and then squash them locally, you will have to force-push to your GitHub again. Add the `-f` argument to your git push command:
|
||||||
|
|
||||||
|
```
|
||||||
$ git push -f origin 100-dataobject-get-one
|
$ git push -f origin 100-dataobject-get-one
|
||||||
|
|
||||||
Helpful hint: You can always edit your last commit message by using:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
$ git commit --amend
|
$ git commit --amend
|
||||||
|
|
||||||
## Some gotchas
|
```
|
||||||
|
|
||||||
Be careful not to commit any of your configuration files, logs, or throwaway test files to your GitHub repo. These files can contain information you wouldn't want publicly viewable and they will make it impossible to merge your contributions into the main development trunk.
|
Be careful not to commit any of your configuration files, logs, or throwaway test files to your GitHub repo. These files can contain information you wouldn't want publicly viewable and they will make it impossible to merge your contributions into the main development trunk.
|
||||||
|
|
||||||
Most of these special files are listed in the `.gitignore` file and won't be included in any commit, but you should carefully review the files you have modified and added before staging them and committing them to your repo. The git status command will display detailed information about any new files, modifications and staged.
|
Most of these special files are listed in the `.gitignore` file and won't be included in any commit, but you should carefully review the files you have modified and added before staging them and committing them to your repo. The git status command will display detailed information about any new files, modifications and staged.
|
||||||
|
|
||||||
|
```
|
||||||
$ git status
|
$ git status
|
||||||
|
|
||||||
One thing you do not want to do is to issue a git commit with the -a option. This automatically stages and commits every modified file that's not expressly defined in .gitignore, including your crawler logs.
|
```
|
||||||
|
|
||||||
|
```
|
||||||
$ git commit -a
|
$ git commit -a
|
||||||
|
|
||||||
Sometimes, you might correct an issue which was reported in a different repo. In these cases, don't simply refer to the issue number as GitHub will infer that as correcting an issue in the current repo. In these cases, use the full GitHub path to reference the issue.
|
```
|
||||||
|
|
||||||
|
```
|
||||||
$ git commit -m 'Issue silverstripe/silverstripe-cms#100: Some kind of descriptive message'
|
$ git commit -m 'Issue silverstripe/silverstripe-cms#100: Some kind of descriptive message'
|
||||||
|
|
||||||
Sometimes, you might correct an issue which was reported in a different repo. In these cases, don't simply refer to the issue number as GitHub will infer that as correcting an issue in the current repo. See [Commit Messages](code#commit-messages) above for the correct way to reference these issues.
|
```
|
||||||
|
|
||||||
## What is git rebase?
|
## What is git rebase?
|
||||||
|
|
||||||
@ -318,17 +342,19 @@ Using `git rebase` helps create clean commit trees and makes keeping your code u
|
|||||||
|
|
||||||
Let's say you're working on Issue #212 a new plugin in your own branch and you start with something like this:
|
Let's say you're working on Issue #212 a new plugin in your own branch and you start with something like this:
|
||||||
|
|
||||||
|
```
|
||||||
1---2---3 #212-my-new-plugin
|
1---2---3 #212-my-new-plugin
|
||||||
/
|
/
|
||||||
A---B #master
|
A---B #master
|
||||||
|
|
||||||
You keep coding for a few days and then pull the latest upstream stuff and you end up like this:
|
```
|
||||||
|
|
||||||
|
```
|
||||||
1---2---3 #212-my-new-plugin
|
1---2---3 #212-my-new-plugin
|
||||||
/
|
/
|
||||||
A---B--C--D--E--F #master
|
A---B--C--D--E--F #master
|
||||||
|
|
||||||
So all these new things (C,D,..F) have happened since you started. Normally you would just keep going (let's say you're not finished with the plugin yet) and then deal with a merge later on, which becomes a commit, which get moved upstream and ends up grafted on the tree forever.
|
```
|
||||||
|
|
||||||
A cleaner way to do this is to use rebase to essentially rewrite your commits as if you had started at point F instead of point B. So just do:
|
A cleaner way to do this is to use rebase to essentially rewrite your commits as if you had started at point F instead of point B. So just do:
|
||||||
|
|
||||||
@ -336,11 +362,12 @@ git rebase master 212-my-new-plugin
|
|||||||
|
|
||||||
git will rewrite your commits like this:
|
git will rewrite your commits like this:
|
||||||
|
|
||||||
|
```
|
||||||
1---2---3 #212-my-new-plugin
|
1---2---3 #212-my-new-plugin
|
||||||
/
|
/
|
||||||
A---B--C--D--E--F #master
|
A---B--C--D--E--F #master
|
||||||
|
|
||||||
It's as if you had just started your branch. One immediate advantage you get is that you can test your branch now to see if C, D, E, or F had any impact on your code (you don't need to wait until you're finished with your plugin and merge to find this out). And, since you can keep doing this over and over again as you develop your plugin, at the end your merge will just be a fast-forward (in other words no merge at all).
|
```
|
||||||
|
|
||||||
So when you're ready to send the new plugin upstream, you do one last rebase, test, and then merge (which is really no merge at all) and send out your pull request. Then in most cases, we have a simple fast-forward on our end (or at worst a very small rebase or merge) and over time that adds up to a simpler tree.
|
So when you're ready to send the new plugin upstream, you do one last rebase, test, and then merge (which is really no merge at all) and send out your pull request. Then in most cases, we have a simple fast-forward on our end (or at worst a very small rebase or merge) and over time that adds up to a simpler tree.
|
||||||
|
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Request for comment
|
||||||
|
summary: Our approach to decision-making around impactful changes to the product
|
||||||
|
icon: comments
|
||||||
|
---
|
||||||
|
|
||||||
# Request for comment (RFC)
|
# Request for comment (RFC)
|
||||||
|
|
||||||
## Why RFCs?
|
## Why RFCs?
|
||||||
@ -22,7 +28,9 @@ The benefits of writing an RFC for non-trivial feature proposals are:
|
|||||||
* Community becomes aware of incoming changes prior to the implementation
|
* Community becomes aware of incoming changes prior to the implementation
|
||||||
* RFC can be used as a basis for documentation of the feature
|
* RFC can be used as a basis for documentation of the feature
|
||||||
|
|
||||||
|
|
||||||
## How to write an RFC?
|
## How to write an RFC?
|
||||||
|
|
||||||
### Template
|
### Template
|
||||||
The following heading can act as a template to starting your RFC.
|
The following heading can act as a template to starting your RFC.
|
||||||
* **Introduction** - include a reference #, title, author
|
* **Introduction** - include a reference #, title, author
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Release process
|
||||||
summary: Describes the process followed for "core" releases.
|
summary: Describes the process followed for "core" releases.
|
||||||
|
iconBrand: git-alt
|
||||||
|
---
|
||||||
|
|
||||||
# Release Process
|
# Release Process
|
||||||
|
|
||||||
@ -65,7 +69,7 @@ How to deprecate an API:
|
|||||||
|
|
||||||
Here's an example for replacing `Director::isDev()` with a (theoretical) `Env::is_dev()`:
|
Here's an example for replacing `Director::isDev()` with a (theoretical) `Env::is_dev()`:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
/**
|
/**
|
||||||
* Returns true if your are in development mode
|
* Returns true if your are in development mode
|
||||||
* @deprecated 4.0 Use {@link Env::is_dev()} instead.
|
* @deprecated 4.0 Use {@link Env::is_dev()} instead.
|
||||||
@ -75,7 +79,7 @@ Here's an example for replacing `Director::isDev()` with a (theoretical) `Env::i
|
|||||||
return Env::is_dev();
|
return Env::is_dev();
|
||||||
}
|
}
|
||||||
|
|
||||||
This change could be committed to a minor release like *3.2.0*, and remains deprecated in all subsequent minor releases
|
```
|
||||||
(e.g. *3.3.0*, *3.4.0*), until a new major release (e.g. *4.0.0*), at which point it gets removed from the codebase.
|
(e.g. *3.3.0*, *3.4.0*), until a new major release (e.g. *4.0.0*), at which point it gets removed from the codebase.
|
||||||
|
|
||||||
Deprecation notices are enabled by default on dev environment, but can be
|
Deprecation notices are enabled by default on dev environment, but can be
|
||||||
@ -86,17 +90,17 @@ notices are always disabled on both live and test.
|
|||||||
`mysite/_config.php`
|
`mysite/_config.php`
|
||||||
|
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
Deprecation::set_enabled(false);
|
Deprecation::set_enabled(false);
|
||||||
|
|
||||||
|
```
|
||||||
`_ss_environment.php`
|
`_ss_environment.php`
|
||||||
|
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
define('SS_DEPRECATION_ENABLED', false);
|
define('SS_DEPRECATION_ENABLED', false);
|
||||||
|
|
||||||
|
```
|
||||||
## Security Releases
|
## Security Releases
|
||||||
|
|
||||||
### Reporting an issue
|
### Reporting an issue
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
title: Making a SilverStripe core release
|
title: Making a SilverStripe core release
|
||||||
summary: Development guide for core contributors to build and publish a new release
|
summary: Development guide for core contributors to build and publish a new release
|
||||||
|
iconBrand: github-alt
|
||||||
|
---
|
||||||
|
|
||||||
# Making a SilverStripe core release
|
# Making a SilverStripe core release
|
||||||
|
|
||||||
@ -40,7 +43,7 @@ As a core contributor it is necessary to have installed the following set of too
|
|||||||
|
|
||||||
Example `_ss_environment.php`:
|
Example `_ss_environment.php`:
|
||||||
|
|
||||||
:::php
|
```php
|
||||||
<?php
|
<?php
|
||||||
// Environent
|
// Environent
|
||||||
define('SS_TRUSTED_PROXY_IPS', '*');
|
define('SS_TRUSTED_PROXY_IPS', '*');
|
||||||
@ -63,7 +66,7 @@ Example `_ss_environment.php`:
|
|||||||
global $_FILE_TO_URL_MAPPING;
|
global $_FILE_TO_URL_MAPPING;
|
||||||
$_FILE_TO_URL_MAPPING[__DIR__] = "http://localhost";
|
$_FILE_TO_URL_MAPPING[__DIR__] = "http://localhost";
|
||||||
|
|
||||||
|
```
|
||||||
You will also need to be assigned the following permissions. Contact one of the SS staff from
|
You will also need to be assigned the following permissions. Contact one of the SS staff from
|
||||||
the [core committers](core_committers), who will assist with setting up your credentials.
|
the [core committers](core_committers), who will assist with setting up your credentials.
|
||||||
|
|
||||||
@ -97,10 +100,10 @@ exactly the same for these.
|
|||||||
Standard practice is to produce a pre-release for any patched modules on the security
|
Standard practice is to produce a pre-release for any patched modules on the security
|
||||||
forks for cms and framework (see [silverstripe-security](https://github.com/silverstripe-security)).
|
forks for cms and framework (see [silverstripe-security](https://github.com/silverstripe-security)).
|
||||||
|
|
||||||
<div class="warning" markdown="1">
|
[warning]
|
||||||
Security issues are never disclosed until a public stable release containing this fix
|
Security issues are never disclosed until a public stable release containing this fix
|
||||||
is available, or within a reasonable period of time of such a release.
|
is available, or within a reasonable period of time of such a release.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
Producing a security fix follows this general process:
|
Producing a security fix follows this general process:
|
||||||
|
|
||||||
@ -128,11 +131,11 @@ Producing a security fix follows this general process:
|
|||||||
* After the final release has been published, close related JIRA issues
|
* After the final release has been published, close related JIRA issues
|
||||||
at [open source security jira](https://silverstripe.atlassian.net/secure/RapidBoard.jspa?rapidView=198&view=detail)
|
at [open source security jira](https://silverstripe.atlassian.net/secure/RapidBoard.jspa?rapidView=198&view=detail)
|
||||||
|
|
||||||
<div class="warning" markdown="1">
|
[warning]
|
||||||
Note: It's not considered acceptable to disclose any security vulnerability until a fix exists in
|
Note: It's not considered acceptable to disclose any security vulnerability until a fix exists in
|
||||||
a public stable, not an RC or dev-branch. Security warnings that do not require a stable release
|
a public stable, not an RC or dev-branch. Security warnings that do not require a stable release
|
||||||
can be published as soon as a workaround or usable resolution exists.
|
can be published as soon as a workaround or usable resolution exists.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
## Standard release process
|
## Standard release process
|
||||||
|
|
||||||
@ -170,9 +173,10 @@ any mistakes migrating their way into the public sphere).
|
|||||||
|
|
||||||
Invoked by running `cow release` in the format as below:
|
Invoked by running `cow release` in the format as below:
|
||||||
|
|
||||||
|
```
|
||||||
cow release <version> --from=<prior-version> --branch-auto -vvv
|
cow release <version> --from=<prior-version> --branch-auto -vvv
|
||||||
|
|
||||||
This command has the following parameters:
|
```
|
||||||
|
|
||||||
* `<version>` The version that is to be released. E.g. 3.2.4 or 3.2.4-rc1
|
* `<version>` The version that is to be released. E.g. 3.2.4 or 3.2.4-rc1
|
||||||
* `<prior-version>` The version from which to compare to generate a changelog.
|
* `<prior-version>` The version from which to compare to generate a changelog.
|
||||||
@ -247,9 +251,10 @@ building an archive, and uploading to
|
|||||||
|
|
||||||
Invoked by running `cow release:publish` in the format as below:
|
Invoked by running `cow release:publish` in the format as below:
|
||||||
|
|
||||||
|
```
|
||||||
cow release:publish <version> -vvv
|
cow release:publish <version> -vvv
|
||||||
|
|
||||||
As with the `cow release` command, this step is broken down into the following
|
```
|
||||||
subtasks which are invoked in sequence:
|
subtasks which are invoked in sequence:
|
||||||
|
|
||||||
* `release:tag` Each module will have the appropriate tag applied (except the theme).
|
* `release:tag` Each module will have the appropriate tag applied (except the theme).
|
||||||
@ -297,18 +302,22 @@ minor version will require a new branch option to be made available on each site
|
|||||||
|
|
||||||
* [docs.silverstripe.org](https://docs.silverstripe.org):
|
* [docs.silverstripe.org](https://docs.silverstripe.org):
|
||||||
* New branches (minor releases) require a code update. Changes are made to
|
* New branches (minor releases) require a code update. Changes are made to
|
||||||
|
```
|
||||||
[github](https://github.com/silverstripe/doc.silverstripe.org) and deployed via
|
[github](https://github.com/silverstripe/doc.silverstripe.org) and deployed via
|
||||||
[SilverStripe Platform](https://platform.silverstripe.com/naut/project/SS-Developer-Docs/environment/Production/)
|
[SilverStripe Platform](https://platform.silverstripe.com/naut/project/SS-Developer-Docs/environment/Production/)
|
||||||
* Updates to markdown only can be made via the [build tasks](https://docs.silverstripe.org/dev/tasks).
|
```
|
||||||
|
```
|
||||||
See below for more details.
|
See below for more details.
|
||||||
* [userhelp.silverstripe.org](https://userhelp.silverstripe.org/en/3.2):
|
```
|
||||||
* Updated similarly to docs.silverstripe.org: Code changes are made to
|
* Updated similarly to docs.silverstripe.org: Code changes are made to
|
||||||
|
```
|
||||||
[github](https://github.com/silverstripe/userhelp.silverstripe.org) and deployed via
|
[github](https://github.com/silverstripe/userhelp.silverstripe.org) and deployed via
|
||||||
[SilverStripe Platform](https://platform.silverstripe.com/naut/project/SS-User-Docs/environment/Production/).
|
[SilverStripe Platform](https://platform.silverstripe.com/naut/project/SS-User-Docs/environment/Production/).
|
||||||
* The content for this site is pulled from [silverstripe-userhelp-content](https://github.com/silverstripe/silverstripe-userhelp-content)
|
```
|
||||||
* Updates to markdown made via the [build tasks](https://userhelp.silverstripe.org/dev/tasks).
|
* Updates to markdown made via the [build tasks](https://userhelp.silverstripe.org/dev/tasks).
|
||||||
|
```
|
||||||
See below for more details.
|
See below for more details.
|
||||||
* [demo.silverstripe.org](http://demo.silverstripe.org/): Update code on
|
```
|
||||||
[github](https://github.com/silverstripe/demo.silverstripe.org/)
|
[github](https://github.com/silverstripe/demo.silverstripe.org/)
|
||||||
and deployed via [SilverStripe Platform](https://platform.silverstripe.com/naut/project/ss3demo/environment/live).
|
and deployed via [SilverStripe Platform](https://platform.silverstripe.com/naut/project/ss3demo/environment/live).
|
||||||
* [api.silverstripe.org](https://api.silverstripe.org): Update on [github](https://github.com/silverstripe/api.silverstripe.org)
|
* [api.silverstripe.org](https://api.silverstripe.org): Update on [github](https://github.com/silverstripe/api.silverstripe.org)
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
title: Documentation
|
title: Documentation
|
||||||
summary: Writing guide for contributing to SilverStripe developer and CMS user help documentation.
|
summary: Writing guide for contributing to SilverStripe developer and CMS user help documentation.
|
||||||
|
icon: file-alt
|
||||||
|
---
|
||||||
|
|
||||||
# Contributing documentation
|
# Contributing documentation
|
||||||
|
|
||||||
@ -18,9 +21,9 @@ page you want to edit. Alternatively, locate the appropriate .md file in the
|
|||||||
* After editing the documentation, describe your changes in the "commit summary" and "extended description" fields below then press "Commit Changes".
|
* After editing the documentation, describe your changes in the "commit summary" and "extended description" fields below then press "Commit Changes".
|
||||||
* After committing you changes, you will see a form to submit a Pull Request: "[pull requests](http://help.github.com/pull-requests/)". You should be able to adjust the version to which your documentation changes apply before submitting the form. Any changes submitted in a pull request will be sent to the core committers for approval.
|
* After committing you changes, you will see a form to submit a Pull Request: "[pull requests](http://help.github.com/pull-requests/)". You should be able to adjust the version to which your documentation changes apply before submitting the form. Any changes submitted in a pull request will be sent to the core committers for approval.
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
[warning]
|
||||||
You should make your changes in the lowest branch they apply to. For instance, if you fix a spelling issue that you found in the 3.2 documentation, submit your fix to that branch in Github and it'll be copied to the master (4.0) version of the documentation automatically. *Don't submit multiple pull requests*.
|
You should make your changes in the lowest branch they apply to. For instance, if you fix a spelling issue that you found in the 3.2 documentation, submit your fix to that branch in Github and it'll be copied to the master (4.0) version of the documentation automatically. *Don't submit multiple pull requests*.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
## Editing on your computer
|
## Editing on your computer
|
||||||
|
|
||||||
@ -30,9 +33,9 @@ If you prefer to edit content on your local machine, you can "[fork](http://help
|
|||||||
|
|
||||||
The documentation is kept alongside the source code in the `docs/` subfolder of any SilverStripe module, framework or CMS folder.
|
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'>
|
[warning]
|
||||||
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.
|
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>
|
[/warning]
|
||||||
|
|
||||||
## Repositories
|
## Repositories
|
||||||
|
|
||||||
@ -80,37 +83,40 @@ documenting, there shouldn't be any "frequently asked questions" left.
|
|||||||
There are several built-in block styles for highlighting a paragraph of text. Please use these graphical elements
|
There are several built-in block styles for highlighting a paragraph of text. Please use these graphical elements
|
||||||
sparingly.
|
sparingly.
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
[hint]
|
||||||
"Tip box": A tip box is great for adding, deepening or accenting information in the main text. They can be used for background knowledge, or to provide links to further information (ie, a "see also" link).
|
"Tip box": A tip box is great for adding, deepening or accenting information in the main text. They can be used for background knowledge, or to provide links to further information (ie, a "see also" link).
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
Code for a Tip box:
|
Code for a Tip box:
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
```
|
||||||
|
[hint]
|
||||||
...
|
...
|
||||||
</div>
|
[/hint]
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
```
|
||||||
"Notification box": A notification box is good for technical notifications relating to the main text. For example, notifying users about a deprecated feature.
|
"Notification box": A notification box is good for technical notifications relating to the main text. For example, notifying users about a deprecated feature.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
Code for a Notification box:
|
Code for a Notification box:
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
```
|
||||||
|
[notice]
|
||||||
...
|
...
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
```
|
||||||
"Warning box": A warning box is useful for highlighting a severe bug or a technical issue requiring a user's attention. For example, suppose a rare edge case sometimes leads to a variable being overwritten incorrectly. A warning box can be used to alert the user to this case so they can write their own code to handle it.
|
"Warning box": A warning box is useful for highlighting a severe bug or a technical issue requiring a user's attention. For example, suppose a rare edge case sometimes leads to a variable being overwritten incorrectly. A warning box can be used to alert the user to this case so they can write their own code to handle it.
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
Code for a Warning box:
|
Code for a Warning box:
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
```
|
||||||
|
[warning]
|
||||||
...
|
...
|
||||||
</div>
|
[/warning]
|
||||||
|
|
||||||
See [markdown extra documentation](http://michelf.com/projects/php-markdown/extra/#html) for more restrictions
|
```
|
||||||
on placing HTML blocks inside Markdown.
|
on placing HTML blocks inside Markdown.
|
||||||
|
|
||||||
## Translating documentation
|
## Translating documentation
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
title: Translations
|
title: Translations
|
||||||
summary: Translate interface components like button labels into multiple languages.
|
summary: Translate interface components like button labels into multiple languages.
|
||||||
|
icon: globe
|
||||||
|
---
|
||||||
|
|
||||||
# Contributing Translations
|
# Contributing Translations
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
---
|
||||||
title: Implement Internationalisation
|
title: Implement Internationalisation
|
||||||
summary: Implement SilverStripe's internationalisation system in your own modules.
|
summary: Implement SilverStripe's internationalisation system in your own modules.
|
||||||
|
icon: globe
|
||||||
|
---
|
||||||
|
|
||||||
# Implementing Internationalisation
|
# Implementing Internationalisation
|
||||||
|
|
||||||
@ -21,16 +24,6 @@ the web interface, there's a convenient
|
|||||||
[commandline client](http://support.transifex.com/customer/portal/topics/440187-transifex-client/articles) for this
|
[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:
|
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 to import, your project is ready to go - simply point translators to the URL, have them
|
If you don't have existing translations to import, your project is ready to go - simply point translators to the URL, have them
|
||||||
@ -72,16 +65,16 @@ Translations need to be reviewed before being committed, which is a process that
|
|||||||
merging back translations into all supported release branches as well as the `master` branch. The following script
|
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:
|
should be applied to the oldest release branch, and then merged forward into newer branches:
|
||||||
|
|
||||||
:::bash
|
```bash
|
||||||
tx pull
|
tx pull
|
||||||
|
|
||||||
# Manually review changes through git diff, then commit
|
# Manually review changes through git diff, then commit
|
||||||
git add lang/*
|
git add lang/*
|
||||||
git commit -m "Updated translations"
|
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.
|
You can download your work right from Transifex in order to speed up the process for your desired language.
|
||||||
</div>
|
[/notice]
|
||||||
|
|
||||||
## JavaScript Translations
|
## JavaScript Translations
|
||||||
|
|
||||||
@ -89,18 +82,16 @@ SilverStripe also supports translating strings in JavaScript (see [i18n](/develo
|
|||||||
conversion step involved in order to get those translations syncing with Transifex. Our translation files stored in
|
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.
|
`mymodule/javascript/lang/*.js` call `ss.i18n.addDictionary()` to add files.
|
||||||
|
|
||||||
:::js
|
```js
|
||||||
ss.i18n.addDictionary('de', {'MyNamespace.MyKey': 'My Translation'});
|
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.
|
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
|
```ruby
|
||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
host = https://www.transifex.com
|
||||||
|
|
||||||
@ -116,16 +107,17 @@ First of all, you need to create those source files in JSON, and store them in `
|
|||||||
source_lang = en
|
source_lang = en
|
||||||
type = KEYVALUEJSON
|
type = KEYVALUEJSON
|
||||||
|
|
||||||
Then 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
|
files back into the JS files SilverStripe can actually read. This requires an installation of our
|
||||||
[buildtools](https://github.com/silverstripe/silverstripe-buildtools).
|
[buildtools](https://github.com/silverstripe/silverstripe-buildtools).
|
||||||
|
|
||||||
|
```
|
||||||
tx pull
|
tx pull
|
||||||
(cd .. && phing -Dmodule=mymodule translation-generate-javascript-for-module)
|
(cd .. && phing -Dmodule=mymodule translation-generate-javascript-for-module)
|
||||||
git add javascript/lang/*
|
git add javascript/lang/*
|
||||||
git commit -m "Updated javascript translations"
|
git commit -m "Updated javascript translations"
|
||||||
|
|
||||||
# Related
|
```
|
||||||
|
|
||||||
* [i18n](/developer_guides/i18n/): Developer-level documentation of Silverstripe's i18n capabilities
|
* [i18n](/developer_guides/i18n/): Developer-level documentation of Silverstripe's i18n capabilities
|
||||||
* [Contributing Translations](/contributing/translations): Information for translators looking to contribute translations of the SilverStripe UI.
|
* [Contributing Translations](/contributing/translations): Information for translators looking to contribute translations of the SilverStripe UI.
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Core committers
|
||||||
|
summary: The team of contributors that has merge access to our open source repositories
|
||||||
|
icon: users
|
||||||
|
---
|
||||||
|
|
||||||
# Core Committers
|
# 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.
|
The core committers team is reviewed approximately annually, new members are added based on quality contributions to SilverStipe code and outstanding community participation.
|
||||||
|
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Code of conduct
|
||||||
|
summary: How to be a high-performing, helpful member of our community
|
||||||
|
icon: handshake
|
||||||
|
---
|
||||||
|
|
||||||
# SilverStripe community code of conduct
|
# SilverStripe community code of conduct
|
||||||
|
|
||||||
These guidelines aim to be an aspirational ideal for how we should behave when interacting in the SilverStripe developer community and to aid in building great open source software.
|
These guidelines aim to be an aspirational ideal for how we should behave when interacting in the SilverStripe developer community and to aid in building great open source software.
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
---
|
||||||
title: Contributing
|
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.
|
summary: 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.
|
||||||
|
icon: heart
|
||||||
|
---
|
||||||
## House rules for everybody contributing to SilverStripe
|
## House rules for everybody contributing to SilverStripe
|
||||||
* Read over the SilverStripe Community [Code of Conduct](code_of_conduct)
|
* Read over the SilverStripe Community [Code of Conduct](code_of_conduct)
|
||||||
* 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)
|
* 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)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
---
|
||||||
title: SilverStripe Documentation
|
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.
|
summary: 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.
|
||||||
|
---
|
||||||
## Getting Started with SilverStripe
|
## Getting Started with SilverStripe
|
||||||
|
|
||||||
Before you start developing your first web application, you'll need to install the latest version of SilverStripe onto
|
Before you start developing your first web application, you'll need to install the latest version of SilverStripe onto
|
||||||
|
Loading…
Reference in New Issue
Block a user