diff --git a/docs/en/00_Getting_Started/00_Server_Requirements.md b/docs/en/00_Getting_Started/00_Server_Requirements.md index 9f2059cc4..99ca1b4cc 100644 --- a/docs/en/00_Getting_Started/00_Server_Requirements.md +++ b/docs/en/00_Getting_Started/00_Server_Requirements.md @@ -1,3 +1,9 @@ +--- +title: Server Requirements +icon: server +summary: What you will need to run Silverstripe CMS on a web server +--- + # Requirements 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 * 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. - * 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. * Recommended configuration +``` safe_mode = Off magic_quotes_gpc = Off memory_limit = 48M - * See [phpinfo()](http://php.net/manual/en/function.phpinfo.php) for more information about your environment +``` * One of the following databases: * MySQL 5.0+ * PostgreSQL 8.3+ (requires ["postgresql" module](http://silverstripe.org/postgresql-module)) diff --git a/docs/en/00_Getting_Started/01_Installation/01_Linux_Unix.md b/docs/en/00_Getting_Started/01_Installation/01_Linux_Unix.md index fa8978c0c..ec690842a 100644 --- a/docs/en/00_Getting_Started/01_Installation/01_Linux_Unix.md +++ b/docs/en/00_Getting_Started/01_Installation/01_Linux_Unix.md @@ -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 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. diff --git a/docs/en/00_Getting_Started/01_Installation/02_Mac_OSX.md b/docs/en/00_Getting_Started/01_Installation/02_Mac_OSX.md index 76d139a07..88dc13c68 100644 --- a/docs/en/00_Getting_Started/01_Installation/02_Mac_OSX.md +++ b/docs/en/00_Getting_Started/01_Installation/02_Mac_OSX.md @@ -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. diff --git a/docs/en/00_Getting_Started/01_Installation/03_Windows.md b/docs/en/00_Getting_Started/01_Installation/03_Windows.md index ed9bf0b45..a61260911 100644 --- a/docs/en/00_Getting_Started/01_Installation/03_Windows.md +++ b/docs/en/00_Getting_Started/01_Installation/03_Windows.md @@ -1,3 +1,8 @@ +--- +title: Windows +summary: How to install Silverstripe CMS on a Windows environment +icon: windows +--- # Windows with WAMPServer 2.5+ 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). -```bash -$ cd /c/wamp/www -$ composer create-project silverstripe/installer ./silverstripe ``` ### 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: ``` - - # Turn off index.php handling requests to the homepage fixes issue in apache >=2.4 - - DirectoryIndex disabled - -# ------ # - -``` diff --git a/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Mac_OSX_Homebrew.md b/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Mac_OSX_Homebrew.md index 0d6542708..ddc0a4add 100644 --- a/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Mac_OSX_Homebrew.md +++ b/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Mac_OSX_Homebrew.md @@ -14,41 +14,47 @@ which packages up the whole environment into a convenient application. Since we're compiling PHP, some build tooling is required. Run the following command to install Xcode Command Line Tools. +``` xcode-select --install -Now you can install Homebrew itself: +``` +``` 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: +``` brew tap homebrew/dupes brew tap homebrew/php -We're installing PHP 5.5 here, with the required `mcrypt` module: +``` +``` 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) project to install multiple PHP versions in parallel and switch between them easily. ## Install the Database (MariaDB/MySQL) +``` brew install mariadb unset TMPDIR mysql_install_db --user=`whoami` --basedir="$(brew --prefix mariadb)" --datadir=/usr/local/var/mysql --tmpdir=/tmp mysql.server start '/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 -You can also use `mysql.server start` and `mysql.server stop` on demand. +``` ## 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`, and uncomment/add the following lines to activate the required modules: +``` LoadModule rewrite_module libexec/apache2/mod_rewrite.so LoadModule php5_module /usr/local/opt/php55/libexec/apache2/libphp5.so -Change the `DocumentRoot` setting to your user folder (replacing `` with your OSX user name): +``` +``` DocumentRoot "/Users//Sites" -Now find the section starting with `` and change it as follows, +``` again replacing `` with your OSX user name: +``` /Sites"> Options FollowSymLinks Multiviews MultiviewsMatch Any @@ -74,27 +83,31 @@ again replacing `` with your OSX user name: Require all granted -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 from the command line and through the web server. Find and adjust the following options, replacing the `` placeholder: +``` User Group staff -Now start the web server: +``` +``` sudo apachectl start -Every configuration change requires a restart: +``` +``` sudo apachectl restart -You can also load this webserver on boot: +``` +``` 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`. ## SilverStripe Installation diff --git a/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Vagrant_Virtualbox.md b/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Vagrant_Virtualbox.md index 8840115f8..bc8f302fc 100644 --- a/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Vagrant_Virtualbox.md +++ b/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Vagrant_Virtualbox.md @@ -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. 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 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. @@ -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. 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) 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. @@ -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! 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. -```ruby -config.vm.hostname = "virtuallythere.dev" -config.vm.network "privatenetwork", ip: "10.1.2.50" ``` ### 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. @@ -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. Find this line: -```ruby -config.vm.synced_folder "../data", "/vagrant_data" ``` Then change to match this: -```ruby -config.vm.synced_folder ".", "/vagrant" ``` ### 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: -```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. @@ -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. 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: -```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. 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. @@ -136,56 +92,15 @@ Save `setup.sh` in the same folder as your Vagrantfile. ### Setting up Apache 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 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: -```bash -mkdir apache ``` We'll save a `vagrant.conf` file in the newly created apache folder, and inside we’ll define the VirtualHost: -```apache -ServerRoot "/etc/httpd" - - - AllowOverride none - Require all denied - - -DocumentRoot "/vagrant/public" - - - Options Indexes FollowSymLinks - AllowOverride All - Require all granted - - - - ServerName virtuallythere.dev - ServerAlias www.virtuallythere.dev - DocumentRoot /vagrant/public - LogLevel warn - ServerSignature Off - - - Options +FollowSymLinks - Options -ExecCGI -Includes -Indexes - AllowOverride all - Require all granted - - - # SilverStripe specific - - php_flag engine off - - ``` ### Download SilverStripe @@ -194,8 +109,6 @@ As mentioned above, you could install SilverStripe by [Composer](https://getcomp ### We’re ready for launch That’s all! When that’s done, run: -```bash -vagrant up ``` diff --git a/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Windows_IIS7.md b/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Windows_IIS7.md index 6e7d8900f..70f3680a7 100644 --- a/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Windows_IIS7.md +++ b/docs/en/00_Getting_Started/01_Installation/04_Other_installation_Options/Windows_IIS7.md @@ -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 * Go to **Enable or disable an extension** and disable everything, except for these: +``` php_curl.dll php_gd2.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_tidy.dll - * Go to **Configure error reporting** and check **Development machine** +``` * 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) post_max_size = 64M memory_limit = 256M upload_max_filesize = 64M -Tweak the above values as necessary if your requirements differ. +``` ## 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: +``` upload_tmp_dir="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. @@ -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: +``` +``` 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. - +[/warning] ## 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: -``` - - # Turn off index.php handling requests to the homepage fixes issue in apache >=2.4 - - DirectoryIndex disabled - -# ------ # - ``` ## 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). After using the script (and fixing errors afterwards), please remember to remove it again. -```php - $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; - } -} ``` diff --git a/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Lighttpd.md b/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Lighttpd.md index afa9917ac..2a87064a5 100644 --- a/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Lighttpd.md +++ b/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Lighttpd.md @@ -1,9 +1,15 @@ +--- +title: Configure Lighttpd +summary: Write a custom config for Lighttpd +--- + # Lightttpd 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. +``` $HTTP["host"] == "yoursite.com" { 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" } - +``` 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). @@ -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): +``` $HTTP["host"] == "yoursite.com" { url.rewrite-once = ( "(?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 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 diff --git a/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Nginx.md b/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Nginx.md index 69d58ea55..e6695184b 100644 --- a/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Nginx.md +++ b/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Nginx.md @@ -1,3 +1,8 @@ +--- +title: Configure Lighttpd +summary: Write a custom config for nginx +--- + # Nginx 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 that are similar to those shown below. -
+[notice] If you don't fully understand the configuration presented here, consult the [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. -
+[/notice] 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 diff --git a/docs/en/00_Getting_Started/01_Installation/How_To/MySQL_SSL_Support.md b/docs/en/00_Getting_Started/01_Installation/How_To/MySQL_SSL_Support.md index c47e358a6..03bb6b7c4 100644 --- a/docs/en/00_Getting_Started/01_Installation/How_To/MySQL_SSL_Support.md +++ b/docs/en/00_Getting_Started/01_Installation/How_To/MySQL_SSL_Support.md @@ -1,6 +1,7 @@ +--- title: MySQL SSL Support summary: Setting up MySQL SSL certificates to work with Silverstripe - +--- # 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. @@ -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. -
+[notice] This article assumes that you have `MySQL` and `OpenSSL` installed. -
+[/notice] ## 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. -
+[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) -
+[/notice] - :::bash +```bash # Create directory 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 sudo openssl verify -CAfile ca-cert.pem server-cert.pem client-cert.pem - -
+ +``` +[warning] 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. -
+[/warning] - :::bash + +```bash # Set permissions readonly permissions and change owner to root sudo chown root:root *.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 mv *.pem /etc/mysql/ssl - +``` ## Setting up MySQL to use SSL certificates -
+[notice] For Debian/Ubuntu instances, the configuration file is usually in `/etc/mysql/my.cnf`. Refer to your MySQL manual for more information -
+[/notice] We must edit the MySQL configuration to use the newly generated certificates. Edit your MySQL configuration file as follows. +``` [mysqld] ... 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! bind-address=0.0.0.0 -
+``` 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. -
+[/warning] Make sure to restart your MySQL instance to reflect the changes. - :::bash +```bash sudo service mysql restart - +``` ## 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. @@ -119,13 +123,13 @@ First we need to copy the client certificate files to the Silverstripe instance. - `client-cert.pem` - `ca-cert.pem` -
+[warning] Make sure to only copy `client-key.pem`, `client-cert.pem`, and `ca-cert.pem` to avoid leaking your credentials! -
+[/warning] On your Silverstripe instance: - :::bash +```bash # 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 @@ -134,15 +138,15 @@ On your Silverstripe instance: sudo chmod 750 /path/to/secure/folder sudo chmod 400 /path/to/secure/folder/* -### Setting up _ss_environment.php to use SSL certificates +``` -
+[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`. -
+[/notice] Add or edit your `_ss_environment.php` configuration file. (See [Environment Management](/getting_started/environment_management) for more information.) - :::php +```php +[notice] SilverStripe ships with default rewriting rules specific to your web server. Apart from routing requests to the framework, they also prevent access to sensitive files in the webroot, for example YAML configuration files. Please refer to the [secure coding](/developer_guides/security/secure_coding/#filesystem) documentation for details. - +[/notice] diff --git a/docs/en/00_Getting_Started/02_Composer.md b/docs/en/00_Getting_Started/02_Composer.md index 3fae65ed9..70521654c 100644 --- a/docs/en/00_Getting_Started/02_Composer.md +++ b/docs/en/00_Getting_Started/02_Composer.md @@ -1,3 +1,8 @@ +--- +title: Composer +summary: What is composer and how to use it with Silverstripe CMS +--- + # 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. @@ -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: +``` composer help -
+``` 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. -
+[/hint] #### Updating composer If you already have composer installed you can update it by running: +``` 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 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 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`. 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. 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 -When `create-project` is used with a release version like above, +``` it will try to get the code from archives instead of creating git repositories. If you're planning to contribute to SilverStripe, 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 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`), and installs "dev" dependencies like PHPUnit. In case you don't need those dependencies, use the following command instead: +``` composer require --no-update "silverstripe/forum:*" 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: +``` 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). -
+[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`. -
+[/warning] ## 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: +``` 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 @@ -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 +``` assets/* _ss_environment.php 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 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. 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 +``` composer require gdmedia/ss-auto-git-ignore --dev -Edit your composer.json and insert +``` +``` "scripts": { "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 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 +``` { "name": "silverstripe/installer", "description": "The SilverStripe Framework Installer", @@ -166,7 +183,7 @@ Full example of composer.json with the SSAutoGitIgnore installed and enabled "minimum-stability": "dev" } -# Dev Environments for Contributing Code {#contributing} +``` 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: @@ -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. +``` 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)). 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: +``` { "name": "silverstripe/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" } +``` 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: +``` composer update -## Using development versions +``` 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 @@ -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 the latest unstable silverstripe/installer +``` 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 -## 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 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. +``` cd cms 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 @@ -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. +``` { ... "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). diff --git a/docs/en/00_Getting_Started/03_Environment_Management.md b/docs/en/00_Getting_Started/03_Environment_Management.md index d227f060e..fa2dcd830 100644 --- a/docs/en/00_Getting_Started/03_Environment_Management.md +++ b/docs/en/00_Getting_Started/03_Environment_Management.md @@ -1,3 +1,8 @@ +--- +title: Environment Management +summary: How to configure your server environment for Silverstripe CMS +--- + # Environment management 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 "SS_DATABASE_..." and "SS_DEFAULT_ADMIN_..." defines as appropriate. - :::php +```php +[notice] SSL database connections are supported for `MySQLDatabase` and `MySQLPDODatabase` as of the moment. - +[/notice] - :::php +```php UpperCamelCase()` format. +``` Alternatively, `$this->getUpperCamelCase()` will work the same way in templates - you can access both coding styles as `$UpperCamelCase`. Other instance methods should be in `$this->lowerCamelCase()` format: - :::php +```php public function myInstanceMethod() {} -Methods inside classes must always declare their visibility by using one of the private, protected, or public modifiers. +``` ### Variables Static variables should be `self::$lowercase_with_underscores` - :::php +```php self::$my_static_variable = 'foo'; -Member variables should be `$this->lowerCamelCase` +``` - :::php +```php $this->myMemberVariable = 'foo'; -Member variables always declare their visibility by using one of the private, protected, or public modifiers +``` ### Constants All letters used in a constant name must be capitalized, while all words in a constant name must be separated by underscore characters. - :::php +```php const 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. ### 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` - :::php +```php 'firstValue', '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, 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. For readability, the various "=>" assignment operators should be padded such that they align. - :::php +```php $sampleArray = array( 'firstKey' => 'firstValue', 'secondKey' => 'secondValue', ); -### Function and Method Declaration +``` 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. - :::php +```php public function foo($arg1, $arg2) {} // good public function foo ( $arg1, $arg2 ) {} // bad -Keep the opening brace on the same line as the statement. +``` - :::php +```php // good 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. 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 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, $arg4, $arg5, $arg6 ) { // indented code } -Function and method arguments should be separated by a single trailing space after the comma delimiter, +``` apart from the last argument. ### 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. Any content within the braces must be indented using a tab. - :::php +```php if($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, 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, with one space separating the two, at an indentation level equivalent to the opening control statement. - :::php +```php if(($a == $b) && ($b == $c) || (Foo::CONST == $d) @@ -305,12 +311,12 @@ with one space separating the two, at an indentation level equivalent to the ope $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`, the formatting conventions are similar to the `if` construct. The following examples demonstrate proper formatting for `if` statements with `else` and/or `elseif` constructs: - :::php +```php if($a != 2) { $a = 2; } elseif($a == 3) { @@ -319,9 +325,9 @@ The following examples demonstrate proper formatting for `if` statements with `e $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 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(); else doThat(); -#### switch +``` All content within the "switch" statement must be indented using tabs. Content under each "case" statement must be indented using an additional tab. - :::php +```php switch($numPeople) { case 1: break; @@ -344,7 +350,7 @@ Content under each "case" statement must be indented using an additional tab. break; } -The construct `default` should never be omitted from a switch statement. +``` #### 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. - :::php +```php // PHP code public function getTitle() { return "

Bad Example

"; @@ -363,9 +369,9 @@ Try to avoid using PHP's ability to mix HTML into the code. // Template code $Title -Better: Keep HTML in template files: +``` - :::php +```php // PHP code public function getTitle() { return "Better Example"; @@ -374,7 +380,7 @@ Better: Keep HTML in template files: // Template code

$Title

-## Comments +``` 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)). @@ -388,7 +394,7 @@ and [tag overview](http://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumen Example: - :::php +```php /** * My short description for this class. * My longer description with @@ -415,6 +421,7 @@ Example: } +``` ### Class Member Ordering 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, with the column or table name escaped with double quotes as below. - :::php +```php 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. If it's absolutely necessary to use literal values in a query make sure that values are single quoted. - :::php +```php MyClass::get()->where("\"Title\" = 'my title'"); -Use [ANSI SQL](http://en.wikipedia.org/wiki/SQL#Standardization) format where possible. +``` ### Secure Development diff --git a/docs/en/00_Getting_Started/index.md b/docs/en/00_Getting_Started/index.md index 5cb43f81d..184549835 100644 --- a/docs/en/00_Getting_Started/index.md +++ b/docs/en/00_Getting_Started/index.md @@ -1,6 +1,8 @@ +--- 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. - +icon: rocket +--- ## Installing SilverStripe diff --git a/docs/en/01_Tutorials/01_Building_A_Basic_Site.md b/docs/en/01_Tutorials/01_Building_A_Basic_Site.md index d65f0a71e..fa6c5510d 100644 --- a/docs/en/01_Tutorials/01_Building_A_Basic_Site.md +++ b/docs/en/01_Tutorials/01_Building_A_Basic_Site.md @@ -1,9 +1,11 @@ +--- title: Building a basic site summary: An overview of the SilverStripe installation and an introduction to creating a web page. +--- -
+[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) -
+[/alert] # Tutorial 1 - Building a Basic Site ## 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. | | mysite/ | | Contains all your site's code (mainly PHP). | | 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 @@ -111,43 +114,43 @@ for a template file in the *simple/templates* folder, with the name `` Open *themes/simple/templates/Page.ss*. It uses standard HTML apart from these exceptions: - :::ss +```ss <% 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. - :::ss +```ss $Title $SiteConfig.Title -These two variables are found within the html `` tag, and are replaced by the "Page Name" and "Settings -> Site Title" fields in the CMS. +``` - :::ss +```ss $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 -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. - :::ss +```ss $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. These template markers are processed by SilverStripe into HTML before being sent to your browser and are either prefixed with a dollar sign ($) or placed between SilverStripe template tags: - :::ss +```ss <% %> - +``` **Flushing the cache** 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. - :::ss +```ss <% 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). > *$Title* refers to **Page Name** in the CMS, whereas *$MenuTitle* refers to (the often shorter) **Navigation label** - :::ss +```ss <ul> <% loop $Menu(1) %> <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 %> </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. 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. - :::ss +```ss <li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>"> <a href="$Link" title="$Title.XML">$MenuTitle.XML</a> </li> -you will then be able to target a section in css (*simple/css/layout.css*), e.g.: +``` - :::css +```css .section { background:#ccc; } -## A second level of navigation +``` 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. @@ -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: - :::ss +```ss <ul> <% loop $Menu(2) %> <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 %> </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 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. @@ -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** like this: - :::ss +```ss <% if $Menu(2) %> ... <ul> @@ -261,7 +264,7 @@ like this: ... <% 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 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: - :::ss +```ss <% if $Level(2) %> <div id="Breadcrumbs"> $Breadcrumbs </div> <% 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 *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 -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. @@ -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. - :::ss +```ss <ul> <% loop $Menu(1) %> <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 %> </ul> - +``` ## 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: - :::php +```php <?php class HomePage extends Page { } 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. 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. First, we don't need the breadcrumbs and the secondary menu for the homepage. Let's remove them: - :::ss +```ss <% include SideBar %> +``` We'll also replace the title text with an image. Find this line: - :::ss +```ss <h1>$Title</h1> +``` and replace it with: - :::ss +```ss <div id="Banner"> <img src="http://www.silverstripe.org/assets/SilverStripe-200.png" alt="Homepage image" /> </div> - +``` Your Home page should now look like this: diff --git a/docs/en/01_Tutorials/02_Extending_A_Basic_Site.md b/docs/en/01_Tutorials/02_Extending_A_Basic_Site.md index 57585bb72..ae69c420f 100644 --- a/docs/en/01_Tutorials/02_Extending_A_Basic_Site.md +++ b/docs/en/01_Tutorials/02_Extending_A_Basic_Site.md @@ -1,9 +1,11 @@ +--- title: Extending a basic site 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) -</div> +[/alert] # 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** - :::php +```php <?php 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 to specifically create the view for this page type. @@ -83,7 +85,7 @@ Let's create the *ArticleHolder* page type. **mysite/code/ArticleHolder.php** - :::php +```php <?php class ArticleHolder extends Page { private static $allowed_children = array('ArticlePage'); @@ -91,7 +93,7 @@ Let's create the *ArticleHolder* page type. 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 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. -<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 "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". -</div> +[/hint] ## 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 it. Add a *$db* property definition in the *ArticlePage* class: - :::php +```php <?php class ArticlePage extends Page { 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. -<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, 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. 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 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. - :::php +```php $fields = parent::getCMSFields(); - +``` 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. - :::php +```php $fields->addFieldToTab('Root.Main', new TextField('Author'), '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: "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'));` 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]. 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; - +``` 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. @@ -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, the date field will have the date format defined by your locale. - :::php +```php <?php class ArticlePage extends Page { @@ -217,27 +219,27 @@ the date field will have the date format defined by your locale. return $fields; } -Let's walk through these changes. +``` - :::php +```php $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); -By enabling *showCalendar* you show a calendar overlay when clicking on the field. +``` - :::php +```php $dateField->setConfig('dateformat', 'dd/MM/YYYY'); -*dateFormat* allows you to specify how you wish the date to be entered and displayed in the CMS field. See the [api:DateField] documentation for more configuration options. +``` - :::php +```php $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 @@ -253,7 +255,7 @@ First, the template for displaying a single article: **themes/simple/templates/Layout/ArticlePage.ss** - :::ss +```ss <% include SideBar %> <div class="content-container unit size3of4 lastUnit"> <article> @@ -266,7 +268,7 @@ First, the template for displaying a single article: $Form </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. 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** - :::ss +```ss <% include SideBar %> <div class="content-container unit size3of4 lastUnit"> <article> @@ -304,7 +306,7 @@ We'll now create a template for the article holder. We want our news section to $Form </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. ![](../_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** - :::ss +```ss ... <% loop $Children %> <% include ArticleTeaser %> <% end_loop %> ... -Paste the code that was in ArticleHolder into a new include file called ArticleTeaser.ss: +``` **themes/simple/templates/Includes/ArticleTeaser.ss** - :::ss +```ss <article> <h2><a href="$Link" title="Read more on "{$Title}"">$Title</a></h2> <p>$Content.FirstParagraph</p> <a href="$Link" title="Read more on "{$Title}"">Read more >></a> </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. Add the following field to the *ArticleHolder* and *ArticlePage* classes: - :::php +```php private static $icon = "cms/images/treeicons/news-file.gif"; - +``` And this one to the *HomePage* class: - :::php +```php private static $icon = "cms/images/treeicons/home-file.png"; - +``` This will change the icons for the pages in the CMS. ![](../_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. -</div> +[/hint] ## 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** - :::php +```php // ... public function LatestNews($num=5) { $holder = ArticleHolder::get()->First(); 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: **themes/simple/templates/Layout/Homepage.ss** - :::ss +```ss <!-- ... --> <div class="content">$Content</div> </article> @@ -387,7 +389,7 @@ This function simply runs a database query that gets the latest news articles fr <% include ArticleTeaser %> <% 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. 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** - :::php +```php private static $allowed_actions = array( 'rss' ); @@ -412,7 +414,7 @@ An RSS feed is something that no news section should be without. SilverStripe ma return $rss->outputToBrowser(); } - +``` 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. @@ -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** - :::php +```php public function init() { RSSFeed::linkToFeed($this->Link() . "rss"); 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. ## 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** - :::php +```php <?php 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). **mysite/code/StaffPage.php** - :::php +```php <?php class StaffPage extends Page { 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 { } - +``` 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, @@ -501,7 +503,7 @@ The staff section templates aren't too difficult to create, thanks to the utilit **themes/simple/templates/Layout/StaffHolder.ss** - :::ss +```ss <% include SideBar %> <div class="content-container unit size3of4 lastUnit"> <article> @@ -520,7 +522,7 @@ The staff section templates aren't too difficult to create, thanks to the utilit $Form </div> - +``` This template is very similar to the *ArticleHolder* template. The *ScaleWidth* method of the [api:Image] class will resize the image before sending it to the browser. The resized image is cached, so the server doesn't have to resize the image every time the page is viewed. @@ -531,7 +533,7 @@ The *StaffPage* template is also very straight forward. **themes/simple/templates/Layout/StaffPage.ss** - :::ss +```ss <% include SideBar %> <div class="content-container unit size3of4 lastUnit"> <article> @@ -543,7 +545,7 @@ The *StaffPage* template is also very straight forward. $Form </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. ![](../_images/tutorial2_einstein.jpg) diff --git a/docs/en/01_Tutorials/03_Forms.md b/docs/en/01_Tutorials/03_Forms.md index 168829262..e112d04f3 100644 --- a/docs/en/01_Tutorials/03_Forms.md +++ b/docs/en/01_Tutorials/03_Forms.md @@ -1,6 +1,8 @@ +--- title: Forms summary: Capture and store user information through web forms. - +iconBrand: wpforms +--- # Tutorial 3 - Forms ## 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** - ```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. -```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. @@ -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 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. @@ -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 the fields. -```php - return new Form($this, 'BrowserPollForm', $fields, $actions); ``` 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** - ```ss - ... - <div id="BrowserPoll"> - <h2>Browser Poll</h2> - $BrowserPollForm - </div> - <div class="Content"> - ... ``` 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** - ```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** - ```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*: **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. @@ -222,12 +104,6 @@ Change the end of the 'BrowserPollForm' function so it looks like this: **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. @@ -244,32 +120,11 @@ We can do this using a session variable. The [api:Session] class handles all ses **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 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, @@ -285,43 +140,15 @@ Create the function 'BrowserPollResults' on the *HomePage_Controller* class. **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. - ```php - $submissions = new GroupedList(BrowserPollSubmission::get()); ``` First we get all of the `BrowserPollSubmission` records from the database. This returns the submissions as a [api:DataList]. Then we wrap it inside a [api:GroupedList], which adds the ability to group those records. The resulting object will behave just like the original `DataList`, though (with the addition of a `groupBy()` method). -```php - $total = $submissions->Count(); ``` 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. @@ -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** -```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, diff --git a/docs/en/01_Tutorials/04_Site_Search.md b/docs/en/01_Tutorials/04_Site_Search.md index a5db20590..3f7fa4677 100644 --- a/docs/en/01_Tutorials/04_Site_Search.md +++ b/docs/en/01_Tutorials/04_Site_Search.md @@ -1,5 +1,8 @@ +--- title: Site Search summary: Enable website search. How to handle paged result sets and the SearchForm class. +icon: 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. This will enable fulltext search on page content as well as names of all files in the `/assets` folder. - :::php +```php 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`. @@ -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** - :::ss +```ss ... <% if $SearchForm %> <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 %> <% include Navigation %> -This displays as: +``` ![](../_images/tutorial4_searchbox.jpg) @@ -55,7 +58,7 @@ is applied via `FulltextSearchable::enable()` **cms/code/search/ContentControllerSearchExtension.php** - :::php +```php 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. 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* - :::ss +```ss <div id="Content" class="searchResults"> <h1>$Title</h1> @@ -149,7 +152,7 @@ class. <% end_if %> </div> -Then finally add ?flush=1 to the URL and you should see the new template. +``` ![](../_images/tutorial4_search.jpg) diff --git a/docs/en/01_Tutorials/05_Dataobject_Relationship_Management.md b/docs/en/01_Tutorials/05_Dataobject_Relationship_Management.md index 918978f4f..687e89e02 100644 --- a/docs/en/01_Tutorials/05_Dataobject_Relationship_Management.md +++ b/docs/en/01_Tutorials/05_Dataobject_Relationship_Management.md @@ -1,9 +1,12 @@ +--- title: DataObject Relationship Management 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) -</div> +[/alert] # Tutorial 5 - Dataobject Relationship Management @@ -38,7 +41,7 @@ Let's create the `Student` and `Project` objects. **mysite/code/Student.php** - :::php +```php <?php class Student extends DataObject { private static $db = array( @@ -50,9 +53,9 @@ Let's create the `Student` and `Project` objects. ); } -**mysite/code/Project.php** +``` - :::php +```php <?php class Project extends Page { private static $has_many = array( @@ -62,7 +65,7 @@ Let's create the `Student` and `Project` objects. class Project_Controller extends Page_Controller { } -The relationships are defined through the `$has_one` +``` and `$has_many` properties on the objects. The array keys declares the name of the relationship, the array values contain the class name @@ -95,7 +98,7 @@ The restriction is enforced through the `$allowed_children` directive. **mysite/code/ProjectsHolder.php** - :::php +```php <?php class ProjectsHolder extends Page { private static $allowed_children = array( @@ -105,7 +108,7 @@ The restriction is enforced through the `$allowed_children` directive. 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, 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** - :::php +```php <?php 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, 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: We call `setDisplayFields()` directly on the component responsible for their rendering. -<div class="note" markdown="1"> - 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 - with more sophisticated search and management logic, consider - using the [ModelAdmin](/developer_guides/customising_the_admin_interface/modeladmin) - interface instead. -</div> +[note] +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 +with more sophisticated search and management logic, consider +using the [ModelAdmin](/developer_guides/customising_the_admin_interface/modeladmin) +interface instead. +[/note] ![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** - :::php +```php <?php class Mentor extends DataObject { 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 { // ... 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). 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** - :::php +```php class Project extends Page { // ... 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 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** - :::ss +```ss <% include SideBar %> <div class="content-container unit size3of4 lastUnit"> <article> @@ -325,7 +328,7 @@ a named list of object. </article> </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. 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** - :::ss +```ss <% include SideBar %> <div class="content-container unit size3of4 lastUnit"> <article> @@ -375,7 +378,7 @@ we can access the "Students" and "Mentors" relationships directly in the templat </article> </div> -Follow the link to a project detail from from your holder page, +``` or navigate to it through the submenu provided by the theme. ### Student Detail Template @@ -387,19 +390,19 @@ by introducing a new template for them. **themes/simple/templates/Includes/StudentInfo.ss** - :::ss +```ss $Name ($University) -To use this template, we need to add a new method to our student class: +``` - :::php +```php class Student extends DataObject { function getInfo() { return $this->renderWith('StudentInfo'); } } -Replace the student template code in both `Project.ss` +``` and `ProjectHolder.ss` templates with the new placeholder, `$Info`. That's the code enclosed in `<% loop $Students %>` and `<% end_loop %>`. With this pattern, you can increase code reuse across templates. diff --git a/docs/en/01_Tutorials/index.md b/docs/en/01_Tutorials/index.md index 525dd3bcd..b8608ac21 100644 --- a/docs/en/01_Tutorials/index.md +++ b/docs/en/01_Tutorials/index.md @@ -1,10 +1,13 @@ +--- title: Tutorials introduction: The tutorials below take a step by step look at how to build a SilverStripe application. +icon: graduation-cap +--- ## 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. -</div> +[/alert] ## Video lessons These include video screencasts, written tutorials and code examples to get you started working with SilverStripe websites. diff --git a/docs/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md b/docs/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md index 57f9b61a0..db02ddbab 100644 --- a/docs/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md +++ b/docs/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md @@ -1,6 +1,8 @@ +--- title: Introduction to the Data Model and ORM summary: Introduction to creating and querying a database records through the ORM (object-relational model) - +icon: database +--- # Introduction to the Data Model and ORM 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** - :::php +```php <?php 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 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. -<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), 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 automatically set on the `DataObject`. @@ -75,7 +77,7 @@ automatically set on the `DataObject`. **mysite/code/Player.php** - :::php +```php <?php class Player extends DataObject { @@ -88,64 +90,67 @@ automatically set on the `DataObject`. ); } -Generates the following `SQL`. +``` +``` CREATE TABLE `Player` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `ClassName` enum('Player') DEFAULT 'Player', `LastEdited` datetime DEFAULT NULL, `Created` datetime DEFAULT NULL, - `PlayerNumber` int(11) NOT NULL DEFAULT '0', +``` +``` `FirstName` varchar(255) DEFAULT NULL, - `LastName` mediumtext, +``` `Birthday` datetime DEFAULT NULL, PRIMARY KEY (`ID`), +``` KEY `ClassName` (`ClassName`) ); -## Creating Data Records +``` A new instance of a [api:DataObject] can be created using the `new` syntax. - :::php +```php $player = new Player(); -Or, a better way is to use the `create` method. +``` - :::php +```php $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). -</div> +[/notice] Database columns and properties can be set as class properties on the object. The SilverStripe ORM handles the saving of the values through a custom `__set()` method. - :::php +```php $player->FirstName = "Sam"; $player->PlayerNumber = 07; -To save the `DataObject` to the database, use the `write()` method. The first time `write()` is called, an `ID` will be +``` set. - :::php +```php $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(); $id = $player->write(); -## Querying Data +``` With the `Player` class defined we can query our data using the `ORM` or Object-Relational Model. The `ORM` provides shortcuts and methods for fetching, sorting and filtering data from our database. - :::php +```php $players = Player::get(); // 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(); // calls the `Ago` method on the `LastEdited` property. -The `ORM` uses a "fluent" syntax, where you specify a query by chaining together different methods. Two common methods +``` are `filter()` and `sort()`: - :::php +```php $members = Player::get()->filter(array( 'FirstName' => 'Sam' ))->sort('Surname'); // returns a `DataList` containing all the `Player` records that have the `FirstName` of 'Sam' -<div class="info" markdown="1"> +``` Provided `filter` values are automatically escaped and do not require any escaping. -</div> +[/info] ## 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 result set in PHP. In `MySQL` the query generated by the ORM may look something like this - :::php +```php $players = Player::get()->filter(array( '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 // SELECT * FROM Player WHERE FirstName = 'Sam' ORDER BY Surname - +``` This also means that getting the count of a list of objects will be done with a single, efficient query. - :::php +```php $players = Player::get()->filter(array( 'FirstName' => 'Sam' ))->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' echo $players->Count(); +``` ## Looping over a list of objects `get()` returns a `DataList` instance. You can loop over `DataList` instances in both PHP and templates. - :::php +```php $players = Player::get(); foreach($players as $player) { echo $player->FirstName; } -Notice that we can step into the loop safely without having to check if `$players` exists. The `get()` call is robust, and will at worst return an empty `DataList` object. If you do want to check if the query returned any records, you can use the `exists()` method, e.g. +``` - :::php +```php $players = Player::get(); if($players->exists()) { // do something here } -See the [Lists](lists) documentation for more information on dealing with [api:SS_List] instances. +``` ## Returning a single DataObject There are a couple of ways of getting a single DataObject from the ORM. If you know the ID number of the object, you can use `byID($id)`: - :::php +```php $player = Player::get()->byID(5); -`get()` returns a [api:DataList] instance. You can use operations on that to get back a single record. +``` - :::php +```php $players = Player::get(); $first = $players->first(); $last = $players->last(); -## Sorting +``` If would like to sort the list by `FirstName` in a ascending way (from A to Z). - :::php +```php // Sort can either be Ascending (ASC) or Descending (DESC) $players = Player::get()->sort('FirstName', 'ASC'); // Ascending is implied $players = Player::get()->sort('FirstName'); -To reverse the sort +``` - :::php +```php $players = Player::get()->sort('FirstName', 'DESC'); // or.. $players = Player::get()->sort('FirstName', 'ASC')->reverse(); -However you might have several entries with the same `FirstName` and would like to sort them by `FirstName` and +``` `LastName` - :::php +```php $players = Players::get()->sort(array( 'FirstName' => '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(); $players = Player::get()->sort($random) - +``` ## Filtering Results The `filter()` method filters the list of objects that gets returned. - :::php +```php $players = Player::get()->filter(array( 'FirstName' => 'Sam' )); -Each element of the array specifies a filter. You can specify as many filters as you like, and they **all** must be +``` true for the record to be included in the result. The key in the filter corresponds to the field that you want to filter and the value in the filter corresponds to the @@ -291,7 +297,7 @@ value that you want to filter to. So, this would return only those players called "Sam Minnée". - :::php +```php $players = Player::get()->filter(array( 'FirstName' => 'Sam', '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' -There is also a shorthand way of getting Players with the FirstName of Sam. +``` - :::php +```php $players = Player::get()->filter('FirstName', 'Sam'); -Or if you want to find both Sam and Sig. +``` - :::php +```php $players = Player::get()->filter( 'FirstName', array('Sam', 'Sig') ); // SELECT * FROM Player WHERE FirstName IN ('Sam', 'Sig') -You can use [SearchFilters](searchfilters) to add additional behavior to your `filter` command rather than an +``` exact match. - :::php +```php $players = Player::get()->filter(array( 'FirstName:StartsWith' => 'S' 'PlayerNumber:GreaterThan' => '10' )); +``` ### filterAny Use the `filterAny()` method to match multiple criteria non-exclusively (with an "OR" disjunctive), - :::php +```php $players = Player::get()->filterAny(array( 'FirstName' => 'Sam', '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') -You can combine both conjunctive ("AND") and disjunctive ("OR") statements. +``` - :::php +```php $players = Player::get() ->filter(array( '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')) -You can use [SearchFilters](searchfilters) to add additional behavior to your `filterAny` command. +``` - :::php +```php $players = Player::get()->filterAny(array( 'FirstName:StartsWith' => 'S' 'PlayerNumber:GreaterThan' => '10' )); - +``` ### filterByCallback It is also possible to filter by a PHP callback, this will force the data model to fetch all records and loop them in PHP, thus `filter()` or `filterAny()` are to be preferred over `filterByCallback()`. -<div class="notice" markdown="1"> +[notice] Because `filterByCallback()` has to run in PHP, it has a significant performance tradeoff, and should not be used on large recordsets. `filterByCallback()` will always return an `ArrayList`. -</div> +[/notice] The first parameter to the callback is the item, the second parameter is the list itself. The callback will run once for each record, if the callback returns true, this record will be added to the list of returned items. The below example will get all `Players` aged over 10. - :::php +```php $players = Player::get()->filterByCallback(function($item, $list) { return ($item->Age() > 10); }); -### Exclude +``` The `exclude()` method is the opposite to the filter in that it removes entries from a list. - :::php +```php $players = Player::get()->exclude('FirstName', 'Sam'); // SELECT * FROM Player WHERE FirstName != 'Sam' -Remove both Sam and Sig.. +``` - :::php +```php $players = Player::get()->exclude( 'FirstName', array('Sam','Sig') ); -`Exclude` follows the same pattern as filter, so for removing only Sam Minnée from the list: +``` - :::php +```php $players = Player::get()->exclude(array( 'FirstName' => 'Sam', 'Surname' => 'Minnée', @@ -403,16 +410,16 @@ Remove both Sam and Sig.. // 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'); // 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( 'FirstName' => array('Sam', 'Sig'), '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')); -You can use [SearchFilters](searchfilters) to add additional behavior to your `exclude` command. +``` - :::php +```php $players = Player::get()->exclude(array( 'FirstName:EndsWith' => 'S' 'PlayerNumber:LessThanOrEqual' => '10' )); -### Subtract +``` You can subtract entries from a [api:DataList] by passing in another DataList to `subtract()` - :::php +```php $sam = Player::get()->filter('FirstName', 'Sam'); $players = Player::get(); $noSams = $players->subtract($sam); -Though for the above example it would probably be easier to use `filter()` and `exclude()`. A better use case could be +``` when you want to find all the members that does not exist in a Group. - :::php +```php // ... Finding all members that does not belong to $group. $otherMembers = Member::get()->subtract($group->Members()); -### Limit +``` You can limit the amount of records returned in a DataList by using the `limit()` method. - :::php +```php $members = Member::get()->limit(5); -`limit()` accepts two arguments, the first being the amount of results you want returned, with an optional second +``` parameter to specify the offset, which allows you to tell the system where to start getting the results from. The offset, if not provided as an argument, will default to 0. - :::php +```php // Return 10 members with an offset of 4 (starting from the 5th result). $members = Member::get()->sort('Surname')->limit(10, 4); -<div class="alert"> +``` Note that the `limit` argument order is different from a MySQL LIMIT clause. -</div> +[/alert] ### 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: - :::php +```php $members = Member::get()->where("\"FirstName\" = 'Sam'") -#### Joining Tables +``` You can specify a join with the `innerJoin` and `leftJoin` methods. Both of these methods have the same arguments: @@ -491,7 +498,7 @@ You can specify a join with the `innerJoin` and `leftJoin` methods. Both of the * The filter clause for the join. * An optional alias. - :::php +```php // Without an alias $members = Member::get() ->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() ->innerJoin("Group_Members", "\"Rel\".\"MemberID\" = \"Member\".\"ID\"", "Rel"); -<div class="alert" markdown="1"> +``` Passing a *$join* statement to will filter results further by the JOINs performed against the foreign table. It will **not** return the additionally joined data. -</div> +[/alert] ### Default Values Define the default values for all the `$db` fields. This example sets the "Status"-column on Player to "Active" whenever a new object is created. - :::php +```php <?php 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 [Data Types and Casting](/developer_guides/model/data_types_and_casting) for details. -</div> +[/notice] ## Subclasses @@ -533,7 +540,7 @@ time. For example, suppose we have the following set of classes: - :::php +```php <?php 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: - ID: Int - ClassName: Enum('SiteTree', 'Page', 'NewsPage') @@ -561,16 +568,16 @@ The data for the following classes would be stored across the following tables: - ID: Int - Summary: Text -Accessing the data is transparent to the developer. +``` - :::php +```php $news = NewsPage::get(); foreach($news as $article) { 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 special fields. This is called the "base table". In our case, `SiteTree` is the base table. diff --git a/docs/en/02_Developer_Guides/00_Model/02_Relations.md b/docs/en/02_Developer_Guides/00_Model/02_Relations.md index 83ddbbae3..2a705967c 100644 --- a/docs/en/02_Developer_Guides/00_Model/02_Relations.md +++ b/docs/en/02_Developer_Guides/00_Model/02_Relations.md @@ -1,6 +1,8 @@ +--- title: Relations between Records summary: Relate models together using the ORM using has_one, has_many, and many_many. - +icon: link +--- # Relations between Records 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 "TeamID" on the "Player"-table. - :::php +```php <?php 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. 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); $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; // 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 %> <% if $Team %> Plays for $Team.Title <% end_if %> <% end_with %> -## Polymorphic has_one +``` 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. @@ -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'. Ideally, the associated has_many (or belongs_to) should be specified with dot notation. +``` ::php 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 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 be necessary. E.g. Additional parent classes for each respective relationship, or duplication of code. -</div> +[/warning] ## has_many Defines 1-to-many joins. As you can see from the previous example, `$has_many` goes hand in hand with `$has_one`. -<div class="alert" markdown='1'> +[alert] Please specify a $has_one-relationship on the related child-class as well, in order to have the necessary accessors available on both ends. -</div> +[/alert] - :::php +```php <?php 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. - :::php +```php $team = Team::get()->first(); echo $team->Players(); @@ -147,9 +150,9 @@ you will get an instance of [api:HasManyList] rather than the object. 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 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 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: - :::php +```php public function getCMSFields() { $fields = parent::getCMSFields(); @@ -182,7 +185,7 @@ If you're using the default scaffolded form fields with multiple `has_one` relat return $fields; } - +``` ## belongs_to 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. This is not mandatory unless the relationship would be otherwise ambiguous. - :::php +```php <?php class Team extends DataObject { @@ -210,17 +213,17 @@ This is not mandatory unless the relationship would be otherwise ambiguous. ); } - +``` ## many_many Defines many-to-many joins. A new table, (this-class)_(relationship-name), will be created with a pair of ID fields. -<div class="warning" markdown='1'> +[warning] Please specify a $belongs_many_many-relationship on the related class as well, in order to have the necessary accessors available on both ends. -</div> +[/warning] - :::php +```php <?php 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. - :::php +```php $team = Team::get()->byId(1); $supporters = $team->Supporters(); // returns a 'ManyManyList' instance. - +``` The relationship can also be navigated in [templates](../templates). - :::ss +```ss <% with $Supporter %> <% loop $Supports %> Supports $Title <% end_if %> <% end_with %> -To specify multiple $many_manys between the same classes, use the dot notation to distinguish them like below: +``` - :::php +```php <?php 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. @@ -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()` and `remove()` method. - :::php +```php $team = Team::get()->byId(1); // create a new supporter @@ -299,7 +302,7 @@ and `remove()` method. // add the supporter. $team->Supporters()->add($supporter); - +``` ## Custom Relations You can use the ORM to get a filtered result list without writing any SQL. For example, this snippet gets you the @@ -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. - :::php +```php <?php 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 criteria on the added record. -</div> +[/notice] ## Relations on Unsaved Objects diff --git a/docs/en/02_Developer_Guides/00_Model/03_Lists.md b/docs/en/02_Developer_Guides/00_Model/03_Lists.md index 11f830894..7dba78f58 100644 --- a/docs/en/02_Developer_Guides/00_Model/03_Lists.md +++ b/docs/en/02_Developer_Guides/00_Model/03_Lists.md @@ -1,6 +1,8 @@ +--- title: Managing Lists summary: The SS_List interface allows you to iterate through and manipulate a list of objects. - +icon: list +--- # Managing Lists 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. - :::php +```php $members = Member::get(); foreach($members as $member) { echo $member->Name; } -Or in the template engine: +``` - :::ss +```ss <% loop $Members %> <!-- --> <% end_loop %> -## Finding an item by value. +``` - :::php +```php // $list->find($key, $value); // @@ -36,12 +38,12 @@ Or in the template engine: echo $members->find('ID', 4)->FirstName; // returns 'Sam' - +``` ## Maps A map is an array where the array indexes contain data as well as the values. You can build a map from any list - :::php +```php $members = Member::get()->map('ID', 'FirstName'); // $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' // ); +``` 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(); $map = new SS_Map($members, 'ID', 'FirstName'); +``` ## Column - :::php +```php $members = Member::get(); 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' // ); -## ArrayList +``` [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); $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(); // returns '2' - +``` ## API Documentation * [api:SS_List] diff --git a/docs/en/02_Developer_Guides/00_Model/04_Data_Types_and_Casting.md b/docs/en/02_Developer_Guides/00_Model/04_Data_Types_and_Casting.md index 27e74ac59..4ddf842de 100644 --- a/docs/en/02_Developer_Guides/00_Model/04_Data_Types_and_Casting.md +++ b/docs/en/02_Developer_Guides/00_Model/04_Data_Types_and_Casting.md @@ -1,6 +1,8 @@ +--- 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. - +icon: code +--- # Data Types and Casting 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** - :::php +```php <?php 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: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 simple values you can make use of the `$defaults` array. For example: - :::php +```php <?php 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 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 `$db` items. For example: - :::php +```php <?php 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 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** - :::php +```php <?php 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); 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); // returns "Sa.." -## Casting +``` Rather than manually returning objects from your custom functions. You can use the `$casting` property. - :::php +```php <?php 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] type, which has a `FirstSentence()` method to retrieve the first sentence in a longer piece of text. On the most basic level, the class can be used as simple conversion class from one value to another, e.g. to round a number. - :::php +```php DBField::create_field('Double', 1.23456)->Round(2); // results in 1.23 -Of course that's much more verbose than the equivalent PHP call. The power of [api:DBField] comes with its more +``` sophisticated helpers, like showing the time difference to the current date: - :::php +```php DBField::create_field('Date', '1982-01-01')->TimeDiff(); // shows "30 years ago" -## Casting ViewableData +``` Most objects in SilverStripe extend from [api:ViewableData], which means they know how to present themselves in a view context. Through a `$casting` array, arbitrary properties and getters can be casted: - :::php +```php <?php 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')->InPast(); // returns boolean - +``` ## Casting HTML Text 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 "[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 content via `$MyHtmlField.XML`. -</div> +[/hint] ## 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 database column using `dbObject`. - :::php +```php <?php class Player extends DataObject { @@ -216,7 +218,7 @@ database column using `dbObject`. return (!$this->obj("Birthday")->InPast()) ? "Unborn" : $this->dbObject('Status')->Value(); } - +``` ## API Documentation * [api:DataObject] diff --git a/docs/en/02_Developer_Guides/00_Model/05_Extending_DataObjects.md b/docs/en/02_Developer_Guides/00_Model/05_Extending_DataObjects.md index c1519bb8b..61a4c7119 100644 --- a/docs/en/02_Developer_Guides/00_Model/05_Extending_DataObjects.md +++ b/docs/en/02_Developer_Guides/00_Model/05_Extending_DataObjects.md @@ -1,6 +1,7 @@ +--- title: Extending DataObjects summary: Modify the data model without using subclasses. - +--- # Extending DataObjects 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. - :::php +```php <?php 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. 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". - :::php +```php <?php class Player extends DataObject { @@ -76,9 +77,9 @@ member is logged in who belongs to a group containing the permission "PLAYER_DEL } } +``` - -<div class="notice" markdown='1'> +[notice] Note: There are no separate methods for *onBeforeCreate* and *onBeforeUpdate*. Please check `$this->isInDb()` to toggle these two modes, as shown in the example above. -</div> +[/notice] diff --git a/docs/en/02_Developer_Guides/00_Model/06_SearchFilters.md b/docs/en/02_Developer_Guides/00_Model/06_SearchFilters.md index bb7f5784f..c33c817c2 100644 --- a/docs/en/02_Developer_Guides/00_Model/06_SearchFilters.md +++ b/docs/en/02_Developer_Guides/00_Model/06_SearchFilters.md @@ -1,6 +1,8 @@ +--- title: SearchFilter Modifiers summary: Use suffixes on your ORM queries. - +icon: search +--- # SearchFilter Modifiers 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: - :::php +```php // fetch any player that starts with a S $players = Player::get()->filter(array( 'FirstName:StartsWith' => 'S', @@ -29,7 +31,7 @@ An example of a `SearchFilter` in use: 'LastName:PartialMatch' => 'z' )); -Developers can define their own [api:SearchFilter] if needing to extend the ORM filter and exclude behaviors. +``` These suffixes can also take modifiers themselves. The modifiers currently supported are `":not"`, `":nocase"` and `":case"`. These negate the filter, make it case-insensitive and make it case-sensitive, respectively. The default @@ -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: - :::php +```php $players = Player::get()->filter(array( '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' )); -## API Documentation +``` * [api:SearchFilter] diff --git a/docs/en/02_Developer_Guides/00_Model/07_Permissions.md b/docs/en/02_Developer_Guides/00_Model/07_Permissions.md index 49f8d4082..400031f18 100644 --- a/docs/en/02_Developer_Guides/00_Model/07_Permissions.md +++ b/docs/en/02_Developer_Guides/00_Model/07_Permissions.md @@ -1,6 +1,8 @@ +--- title: Model-Level Permissions summary: Reduce risk by securing models. - +icon: lock +--- # Model-Level Permissions 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 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 code. -</div> +[/notice] - :::php +```php <?php 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 checked in the invoking code. The CMS default sections as well as custom interfaces like [api:ModelAdmin] or [api:GridField] already enforce these permissions. -</div> +[/alert] ## API Documentation diff --git a/docs/en/02_Developer_Guides/00_Model/08_SQL_Query.md b/docs/en/02_Developer_Guides/00_Model/08_SQL_Query.md index dd0fe5fe9..887685909 100644 --- a/docs/en/02_Developer_Guides/00_Model/08_SQL_Query.md +++ b/docs/en/02_Developer_Guides/00_Model/08_SQL_Query.md @@ -1,6 +1,8 @@ +--- title: SQL Queries summary: Write and modify direct database queries through SQLExpression subclasses. - +icon: database +--- # SQLSelect ## 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, the following three statements are functionally equivalent: - :::php +```php // Through raw SQL. $count = DB::query('SELECT COUNT(*) FROM "Member"')->value(); @@ -29,7 +31,7 @@ the following three statements are functionally equivalent: // Through the ORM. $count = Member::get()->count(); -If you do use raw SQL, you'll run the risk of breaking +``` various assumptions the ORM and code based on it have: * Custom getters/setters (object property can differ from database column) @@ -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, 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 how to properly prepare user input and variables for use in queries -</div> +[/warning] ## Usage @@ -56,7 +58,7 @@ conditional filters, grouping, limiting, and sorting. E.g. - :::php +```php <?php $sqlQuery = new SQLSelect(); @@ -82,7 +84,7 @@ E.g. 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. ### DELETE @@ -93,7 +95,7 @@ object instead. For example, creating a `SQLDelete` object - :::php +```php <?php $query = SQLDelete::create() @@ -101,9 +103,9 @@ For example, creating a `SQLDelete` object ->setWhere(array('"SiteTree"."ShowInMenus"' => 0)); $query->execute(); -Alternatively, turning an existing `SQLQuery` into a delete +``` - :::php +```php <?php $query = SQLQuery::create() @@ -112,14 +114,14 @@ Alternatively, turning an existing `SQLQuery` into a delete ->toDelete(); $query->execute(); -Directly querying the database +``` - :::php +```php <?php 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. 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. - :::php +```php <?php $update = SQLUpdate::create('"SiteTree"')->addWhere(array('ID' => 3)); @@ -189,13 +191,13 @@ E.g. // Perform the update $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 these are translated internally as multiple single row inserts. For example, - :::php +```php <?php $insert = SQLInsert::create('"SiteTree"'); @@ -217,14 +219,14 @@ For example, $insert->execute(); -### Value Checks +``` Raw SQL is handy for performance-optimized calls, e.g. when you want a single column rather than a full-blown object representation. Example: Get the count from a relationship. - :::php +```php $sqlQuery = new SQLQuery(); $sqlQuery->setFrom('Player'); $sqlQuery->addSelect('COUNT("Player"."ID")'); @@ -232,19 +234,19 @@ Example: Get the count from a relationship. $sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"'); $count = $sqlQuery->execute()->value(); -Note that in the ORM, this call would be executed in an efficient manner as well: +``` - :::php +```php $count = $myTeam->Players()->count(); -### Mapping +``` Creates a map based on the first two columns of the query result. This can be useful for creating dropdowns. Example: Show player names with their birth year, but set their birth dates as values. - :::php +```php $sqlQuery = new SQLSelect(); $sqlQuery->setFrom('Player'); $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(); $field = new DropdownField('Birthdates', 'Birthdates', $map); -Note that going through SQLSelect is just necessary here +``` because of the custom SQL value transformation (`YEAR()`). An alternative approach would be a custom getter in the object definition. - :::php +```php class Player extends DataObject { private static $db = array( 'Name' => 'Varchar', @@ -269,7 +271,7 @@ An alternative approach would be a custom getter in the object definition. $players = Player::get(); $map = $players->map('Name', 'NameWithBirthyear'); -## Related +``` * [Introduction to the Data Model and ORM](data_model_and_orm) diff --git a/docs/en/02_Developer_Guides/00_Model/09_Validation.md b/docs/en/02_Developer_Guides/00_Model/09_Validation.md index 3c668f21b..f91b9422f 100644 --- a/docs/en/02_Developer_Guides/00_Model/09_Validation.md +++ b/docs/en/02_Developer_Guides/00_Model/09_Validation.md @@ -1,6 +1,8 @@ +--- title: Model Validation and Constraints summary: Validate your data at the model level - +icon: check-square +--- # Validation and Constraints 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. - :::php +```php <?php class MyObject extends DataObject { @@ -42,7 +44,7 @@ The return value of `validate()` is a [api:ValidationResult] object. } } -## API Documentation +``` * [api:DataObject] * [api:ValidationResult]; diff --git a/docs/en/02_Developer_Guides/00_Model/10_Versioning.md b/docs/en/02_Developer_Guides/00_Model/10_Versioning.md index 4123f4fb1..fa0971357 100644 --- a/docs/en/02_Developer_Guides/00_Model/10_Versioning.md +++ b/docs/en/02_Developer_Guides/00_Model/10_Versioning.md @@ -1,6 +1,7 @@ +--- title: Versioning summary: Add versioning to your database content through the Versioned extension. - +--- # Versioning Database content in SilverStripe can be "staged" before its publication, as well as track all changes through the @@ -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. **mysite/_config/app.yml** - :::yml +```yml MyRecord: extensions: - Versioned("Stage","Live") -<div class="notice" markdown="1"> +``` The extension is automatically applied to `SiteTree` class. For more information on extensions see [Extending](../extending) and the [Configuration](../configuration) documentation. -</div> +[/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 of `DataObject`. Adding this extension to children of the base class will have unpredictable behaviour. -</div> +[/warning] ## Database Structure Depending on how many stages you configured, two or more new tables will be created for your records. In the above, this will create a new `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 adding a suffix. -</div> +[/notice] * `MyRecord` table: Contains staged 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 explicitly request a certain stage through various getters on the `Versioned` class. - :::php +```php // Fetching multiple records $stageRecords = Versioned::get_by_stage('MyRecord', 'Stage'); $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); $liveRecord = Versioned::get_by_stage('MyRecord', 'Live')->byID(99); -### Historical Versions +``` The above commands will just retrieve the latest version of its respective stage for you, but not older versions stored in the `<class>_versions` tables. - :::php +```php $historicalRecord = Versioned::get_version('MyRecord', <record-id>, <version-id>); -<div class="alert" markdown="1"> +``` The record is retrieved as a `DataObject`, but saving back modifications via `write()` will create a new version, rather than modifying the existing one. -</div> +[/alert] In order to get a list of all versions for a specific record, we need to generate specialized [api:Versioned_Version] objects, which expose the same database information as a `DataObject`, but also include information about when and how a record was published. - :::php +```php $record = MyRecord::get()->byID(99); // stage doesn't matter here $versions = $record->allVersions(); echo $versions->First()->Version; // instance of Versioned_Version -### Writing Versions and Changing Stages +``` The usual call to `DataObject->write()` will write to whatever stage is currently active, as defined by the `Versioned::current_stage()` global setting. Each call will automatically create a new version in the @@ -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 for this: `publish(<from-stage>, <to-stage>)`. - :::php +```php $record = Versioned::get_by_stage('MyRecord', 'Stage')->byID(99); $record->MyField = 'changed'; // 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 $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 // will remove the row from the `MyRecord_Live` table $record->deleteFromStage('Live'); -### Forcing the Current Stage +``` The current stage is stored as global state on the object. It is usually modified by controllers, e.g. when a preview is initialized. But it can also be set and reset temporarily to force a specific operation to run on a certain stage. - :::php +```php $origMode = Versioned::get_reading_mode(); // save current mode $obj = MyRecord::getComplexObjectRetrieval(); // returns 'Live' records Versioned::set_reading_mode('Stage'); // temporarily overwrite mode $obj = MyRecord::getComplexObjectRetrieval(); // returns 'Stage' records Versioned::set_reading_mode($origMode); // reset current mode -### Custom SQL +``` We generally discourage writing `Versioned` queries from scratch, due to the complexities involved through joining multiple tables across an inherited table scheme (see [api:Versioned::augmentSQL()]). If possible, try to stick to @@ -137,20 +138,20 @@ smaller modifications of the generated `DataList` objects. 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'); -### Permissions +``` 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. -<div class="alert" markdown="1"> +[alert] 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 done via user code. This be be achieved either by wrapping `<% if $canView %>` in 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: @@ -160,7 +161,7 @@ Versioned object visibility can be customised in one of the following ways by ed E.g. - :::php +```php class MyObject extends DataObject { private static $extensions = array( '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: * `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. - :::php +```php class MyObjectExtension extends DataExtension { public function canViewNonLive($member = null) { 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. E.g. - :::php +```php class MyObject extends DataObject { private static $extensions = array( 'Versioned' @@ -210,7 +211,7 @@ E.g. 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. ### 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: **mysite/code/MyController.php** - :::php +```php public function init() { parent::init(); Versioned::set_reading_mode('Stage.Stage'); } - +``` ### Controllers The current stage for each request is determined by `VersionedRequestFilter` before any controllers initialize, through @@ -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 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 authenticated to view it. As with any other controller logic, please use `DataObject->canView()` to determine permissions, and avoid exposing unpublished content to your users. -</div> +[/alert] ## API Documentation diff --git a/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md b/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md index ef0f8ddf3..d0bafd664 100644 --- a/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md +++ b/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md @@ -1,6 +1,8 @@ +--- title: Building Model and Search Interfaces around Scaffolding summary: A Model-driven approach to defining your application UI. - +icon: hammer +--- # Scaffolding 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. - :::php +```php <?php 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 public function getCMSFields() { @@ -50,7 +52,7 @@ To fully customise your form fields, start with an empty FieldList. return $fields; } - +``` 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`. @@ -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 system. The default is a set of array values listing the fields. - :::php +```php <?php 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 default search filter assigned (usually an [api:ExactMatchFilter]). To override these defaults, you can specify additional information on `$searchable_fields`: - :::php +```php <?php 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: - :::php +```php <?php 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. - :::php +```php <?php 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 can be used to show a quick overview of the data for a specific [api:DataObject] record. The most common use is their display as table columns, e.g. in the search results of a [api:ModelAdmin] CMS interface. - :::php +```php <?php 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. - :::php +```php <?php 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. - :::php +```php <?php 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) diff --git a/docs/en/02_Developer_Guides/00_Model/12_Indexes.md b/docs/en/02_Developer_Guides/00_Model/12_Indexes.md index 3366d5804..a6c903a37 100644 --- a/docs/en/02_Developer_Guides/00_Model/12_Indexes.md +++ b/docs/en/02_Developer_Guides/00_Model/12_Indexes.md @@ -1,13 +1,15 @@ +--- title: Indexes summary: Add Indexes to your Data Model to optimize database queries. - +icon: database +--- # Indexes It is sometimes desirable to add indexes to your data model, whether to optimize queries or add a uniqueness constraint to a field. This is done through the `DataObject::$indexes` map, which maps index names to descriptor arrays that represent each index. There're several supported notations: - :::php +```php <?php 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 column. The "advanced" notation supports more `<type>` notations. These vary between database drivers, but all of them 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** - :::php +```php <?php 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. ## API Documentation diff --git a/docs/en/02_Developer_Guides/00_Model/How_Tos/Dynamic_Default_Fields.md b/docs/en/02_Developer_Guides/00_Model/How_Tos/Dynamic_Default_Fields.md index 6622fcf1a..2bf8cc7ad 100644 --- a/docs/en/02_Developer_Guides/00_Model/How_Tos/Dynamic_Default_Fields.md +++ b/docs/en/02_Developer_Guides/00_Model/How_Tos/Dynamic_Default_Fields.md @@ -1,3 +1,8 @@ +--- +title: Dynamic Default Fields +summary: Learn how to add default values to your models +--- + # Dynamic Default Values 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: - :::php +```php /** * 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(); } -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: - - :::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(); - } diff --git a/docs/en/02_Developer_Guides/00_Model/How_Tos/Grouping_DataObject_Sets.md b/docs/en/02_Developer_Guides/00_Model/How_Tos/Grouping_DataObject_Sets.md index 85b12ed40..6d7efc99d 100644 --- a/docs/en/02_Developer_Guides/00_Model/How_Tos/Grouping_DataObject_Sets.md +++ b/docs/en/02_Developer_Guides/00_Model/How_Tos/Grouping_DataObject_Sets.md @@ -1,3 +1,8 @@ +--- +title: Grouping DataObject sets +summary: Learn how to split the results of a query into subgroups +--- + # Grouping lists 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: * B +``` * Blog - * C +``` +``` * CMS Workflow * Custom Translations - * D +``` +``` * 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 will be used both for grouping and for the title in the template. - :::php +```php class Module extends DataObject { private static $db = array( '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. - :::php +```php 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. In this case, the `getTitleFirstLetter()` method defined earlier is used to break them up. - :::ss +```ss <%-- Modules list grouped by TitleFirstLetter --%> <h2>Modules</h2> <% loop $GroupedModules.GroupedBy(TitleFirstLetter) %> @@ -83,7 +91,7 @@ In this case, the `getTitleFirstLetter()` method defined earlier is used to brea </ul> <% end_loop %> -## Grouping Sets By Month +``` Grouping a set by month is a very similar process. 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. This will have a method which returns the month it was posted in: - :::php +```php 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: - :::php +```php 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 <h2>Modules</h2> <% loop $GroupedModulesByDate.GroupedBy(MonthCreated) %> @@ -142,7 +150,7 @@ The final step is the render this into the template using the [api:GroupedList:: </ul> <% end_loop %> -## Related +``` * [Howto: "Pagination"](/developer_guides/templates/how_tos/pagination) diff --git a/docs/en/02_Developer_Guides/00_Model/How_Tos/index.md b/docs/en/02_Developer_Guides/00_Model/How_Tos/index.md new file mode 100644 index 000000000..29eb663e5 --- /dev/null +++ b/docs/en/02_Developer_Guides/00_Model/How_Tos/index.md @@ -0,0 +1,6 @@ +--- +title: How To's +--- +# How To's: Model and Databases + +[CHILDREN] \ No newline at end of file diff --git a/docs/en/02_Developer_Guides/00_Model/index.md b/docs/en/02_Developer_Guides/00_Model/index.md index 475503c6b..df9124afd 100644 --- a/docs/en/02_Developer_Guides/00_Model/index.md +++ b/docs/en/02_Developer_Guides/00_Model/index.md @@ -1,7 +1,9 @@ +--- title: Model and Databases summary: Learn how SilverStripe manages database tables, ways to query your database and how to publish data. introduction: This guide will cover how to create and manipulate data within SilverStripe and how to use the ORM (Object Relational Model) to query data. - +icon: database +--- In SilverStripe, application data will be represented by a [api:DataObject] class. A `DataObject` subclass defines the data columns, relationships and properties of a particular data record. For example, [api:Member] is a `DataObject` which stores information about a person, CMS user or mail subscriber. diff --git a/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md b/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md index bd2d472b3..0b751b14b 100644 --- a/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md +++ b/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md @@ -1,6 +1,8 @@ +--- title: Template Syntax summary: A look at the operations, variables and language controls you can use within templates. - +icon: code +--- # Template Syntax 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** - :::ss +```ss <html> <head> <% base_tag %> @@ -40,10 +42,10 @@ An example of a SilverStripe template is below: </body> </html> -<div class="note"> +``` Templates can be used for more than HTML output. You can use them to output your data as JSON, XML, CSV or any other text-based format. -</div> +[/note] ## 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 alphabetic character or underscore, with subsequent characters being alphanumeric or underscore: - :::ss +```ss $Title -This inserts the value of the Title database field of the page being displayed in place of `$Title`. +``` Variables can be chained together, and include arguments. - :::ss +```ss $Foo $Foo(param) $Foo.Bar -These variables will call a method / field on the object and insert the returned value as a string into the template. +``` * `$Foo` will call `$obj->Foo()` (or the field `$obj->Foo`) * `$Foo(param)` will call `$obj->Foo("param")` @@ -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 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 [Formating, Modifying and Casting Variables](casting) -</div> +[/note] Variables can come from your database fields, or custom methods you define on your objects. **mysite/code/Page.php** - :::php +```php public function UsersIpAddress() { return $this->getRequest()->getIP(); } -**mysite/code/Page.ss** +``` - :::html +```html <p>You are coming from $UsersIpAddress.</p> -<div class="node" markdown="1"> +``` +``` Method names that begin with `get` will automatically be resolved when their prefix is excluded. For example, the above method call `$UsersIpAddress` would also invoke a method named `getUsersIpAddress()`. -</div> +``` The 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] @@ -103,46 +106,48 @@ record and any subclasses of those two. **mysite/code/Layout/Page.ss** - :::ss +```ss $Title // returns the page `Title` property $Content // returns the page `Content` property - +``` ## Conditional Logic The simplest conditional block is to check for the presence of a value (does not equal 0, null, false). - :::ss +```ss <% if $CurrentMember %> <p>You are logged in as $CurrentMember.FirstName $CurrentMember.Surname.</p> <% end_if %> +``` A conditional can also check for a value other than falsy. - :::ss +```ss <% if $MyDinner == "kipper" %> Yummy, kipper for tea. <% end_if %> - -<div class="notice" markdown="1"> + +``` +[notice] When inside template tags variables should have a '$' prefix, and literals should have quotes. -</div> +[/notice] Conditionals can also provide the `else` case. - :::ss +```ss <% if $MyDinner == "kipper" %> Yummy, kipper for tea <% else %> I wish I could have kipper :-( <% end_if %> -`else_if` commands can be used to handle multiple `if` statements. +``` - :::ss +```ss <% if $MyDinner == "quiche" %> Real men don't eat quiche <% else_if $MyDinner == $YourDinner %> @@ -151,74 +156,74 @@ Conditionals can also provide the `else` case. Can I have some of your chips? <% end_if %> -### Negation +``` The inverse of `<% if %>` is `<% if not %>`. - :::ss +```ss <% if not $DinnerInOven %> I'm going out for dinner tonight. <% end_if %> -### Boolean Logic +``` Multiple checks can be done using `||`, `or`, `&&` or `and`. If *either* of the conditions is true. - :::ss +```ss <% if $MyDinner == "kipper" || $MyDinner == "salmon" %> yummy, fish for tea <% end_if %> -If *both* of the conditions are true. +``` - :::ss +```ss <% if $MyDinner == "quiche" && $YourDinner == "kipper" %> Lets swap dinners <% end_if %> -### Inequalities +``` You can use inequalities like `<`, `<=`, `>`, `>=` to compare numbers. - :::ss +```ss <% if $Number >= "5" && $Number <= "10" %> Number between 5 and 10 <% end_if %> - +``` ## Includes Within SilverStripe templates we have the ability to include other templates from the `template/Includes` directory using the `<% include %>` tag. - :::ss +```ss <% include SideBar %> -The `include` tag can be particularly helpful for nested functionality and breaking large templates up. In this example, +``` the include only happens if the user is logged in. - :::ss +```ss <% if $CurrentMember %> <% include MembersOnlyInclude %> <% end_if %> -Includes can't directly access the parent scope when the include is included. However you can pass arguments to the +``` include. - :::ss +```ss <% with $CurrentMember %> <% include MemberDetails Top=$Top, Name=$Name %> <% end_with %> - +``` ## Looping Over Lists The `<% loop %>` tag is used to iterate or loop over a collection of items such as [api:DataList] or a [api:ArrayList] collection. - :::ss +```ss <h1>Children of $Title</h1> <ul> @@ -227,16 +232,16 @@ collection. <% end_loop %> </ul> -This snippet loops over the children of a page, and generates an unordered list showing the `Title` property from each +``` page. -<div class="notice" markdown="1"> +[notice] $Title inside the loop refers to the Title property on each object that is looped over, not the current page like the reference of `$Title` outside the loop. -This demonstrates the concept of [Scope](#scope). When inside a <% loop %> the scope of the template has changed to the +This demonstrates the concept of [Scope](#scope). When inside a `<% loop %>` the scope of the template has changed to the object that is being looped over. -</div> +[/notice] ### Altering the list @@ -245,50 +250,50 @@ templates can call [api:DataList] methods. Sorting the list by a given field. - :::ss +```ss <ul> <% loop $Children.Sort(Title, ASC) %> <li>$Title</li> <% end_loop %> </ul> -Limiting the number of items displayed. +``` - :::ss +```ss <ul> <% loop $Children.Limit(10) %> <li>$Title</li> <% end_loop %> </ul> -Reversing the loop. +``` - :::ss +```ss <ul> <% loop $Children.Reverse %> <li>$Title</li> <% end_loop %> </ul> -Filtering the loop. +``` - :::ss +```ss <ul> <% loop $Children.Filter('School', 'College') %> <li>$Title</li> <% end_loop %> </ul> -Methods can also be chained. +``` - :::ss +```ss <ul> <% loop $Children.Filter('School', 'College').Sort(Score, DESC) %> <li>$Title</li> <% end_loop %> </ul> -### Position Indicators +``` Inside the loop scope, there are many variables at your disposal to determine the current position in the list and iteration. @@ -303,7 +308,7 @@ iteration. Last item defaults to 1, but can be passed as a parameter. * `$TotalItems`: Number of items in the list (integer). - :::ss +```ss <ul> <% loop $Children.Reverse %> <% if First %> @@ -314,16 +319,16 @@ iteration. <% end_loop %> </ul> -<div class="info" markdown="1"> +``` A common task is to paginate your lists. See the [Pagination](how_tos/pagination) how to for a tutorial on adding pagination. -</div> +[/info] ### Modulus and MultipleOf $Modulus and $MultipleOf can help to build column and grid layouts. - :::ss +```ss // returns an int $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">, -<div class="hint" markdown="1"> +``` `$Modulus` is useful for floated grid CSS layouts. If you want 3 rows across, put $Modulus(3) as a class and add a `clear: both` to `.column-1`. -</div> +[/hint] $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. - :::ss +```ss <% loop $Children %> <% if $MultipleOf(3) %> <br> <% end_if %> <% end_loop %> -### Escaping +``` Sometimes you will have template tags which need to roll into one another. Use `{}` to contain variables. - :::ss +```ss $Foopx // will returns "" (as it looks for a `Foopx` value) {$Foo}px // returns "3px" (CORRECT) - +``` Or when having a `$` sign in front of the variable such as displaying money. - :::ss +```ss $$Foo // returns "" ${$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 "$Foo" -<div class="hint" markdown="1"> +``` For more information on formatting and casting variables see [Formating, Modifying and Casting Variables](casting) -</div> +[/hint] ## 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 record. In the case of `$Title` the flow looks like +``` $Title --> [Looks up: Current Page_Controller and parent classes] --> [Looks up: Current Page and parent classes] -The list of variables you could use in your template is the total of all the methods in the current scope object, parent +``` classes of the current scope object, and any [api:Extension] instances you have. ### Navigating Scope @@ -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. - :::ss +```ss <h1>Children of '$Title'</h1> <% loop $Children %> @@ -411,39 +417,41 @@ When in a particular scope, `$Up` takes the scope back to the previous level. <% end_loop %> <% end_loop %> -Given the following structure, it will output the text. +``` +``` My Page | +-+ Child 1 - | | +``` | +- Grandchild 1 | +-+ Child 2 +``` Children of 'My Page' Page 'Child 1' is a child of 'My Page' Page 'Grandchild 1' is a grandchild of 'My Page' Page 'Child 2' is a child of 'MyPage' -<div class="notice" markdown="1"> +``` 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> <% loop $Children.Sort('Title').First %> <%-- We have two additional selectors in the loop expression so... --%> <p>Page '$Title' is a child of '$Up.Up.Up.Title'</p> <% 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 page. The previous example could be rewritten to use the following syntax. - :::ss +```ss <h1>Children of '$Title'</h1> <% loop $Children %> @@ -454,21 +462,21 @@ page. The previous example could be rewritten to use the following syntax. <% end_loop %> <% end_loop %> -### With +``` The `<% with %>` tag lets you change into a new scope. Consider the following example: - :::ss +```ss <% with $CurrentMember %> Hello, $FirstName, welcome back. Your current balance is $Balance. <% end_with %> -This is functionalty the same as the following: +``` - :::ss +```ss 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 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. - :::ss +```ss $Me -## Comments +``` Using standard HTML comments is supported. These comments will be included in the published site. - :::ss +```ss $EditForm <!-- Some public comment about the form --> - +``` However you can also use special SilverStripe comments which will be stripped out of the published site. This is useful for adding notes for other developers but for things you don't want published in the public html. - :::ss +```ss $EditForm <%-- Some hidden comment about the form --%> -## Related +``` [CHILDREN] diff --git a/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md b/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md index dc99fe2fc..78101f35a 100644 --- a/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md +++ b/docs/en/02_Developer_Guides/01_Templates/02_Common_Variables.md @@ -1,6 +1,7 @@ +--- title: Common Variables summary: Some of the common variables and methods your templates can use, including Menu, SiteConfig, and more. - +--- # Common Variables The page below describes a few of common variables and methods you'll see in a SilverStripe template. This is not an @@ -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 [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 such as `Page` of the object that's in scope. The methods you can call on that object then are any functions, database properties or relations on the `Page` class, `Page_Controller` class as well as anything from their subclasses **or** extensions. -</div> +[/notice] Outputting these variables is only the start, if you want to format or manipulate them before adding them to the template have a read of the [Formating, Modifying and Casting Variables](casting) documentation. -<div class="alert" markdown="1"> +[alert] Some of the following only apply when you have the `CMS` module installed. If you're using the `Framework` alone, this functionality may not be included. -</div> +[/alert] ## Base Tag - :::ss +```ss <head> <% base_tag %> .. </head> -The `<% base_tag %>` placeholder is replaced with the HTML base element. Relative links within a document (such as <img +``` src="someimage.jpg" />) will become relative to the URI specified in the base tag. This ensures the browser knows where to locate your site’s images and css files. It renders in the template as `<base href="http://www.yoursite.com" /><!--[if lte IE 6]></base><![endif]-->` -<div class="alert" markdown="1"> +[alert] A `<% base_tag %>` is nearly always required or assumed by SilverStripe to exist. -</div> +[/alert] ## CurrentMember Returns the currently logged in [api:Member] instance, if there is one logged in. - :::ss +```ss <% if $CurrentMember %> Welcome Back, $CurrentMember.FirstName <% end_if %> - +``` ## Title and Menu Title - :::ss +```ss $Title $MenuTitle -Most objects within SilverStripe will respond to `$Title` (i.e they should have a `Title` database field or at least a +``` `getTitle()` method). The CMS module in particular provides two fields to label a page: `Title` and `MenuTitle`. `Title` is the title displayed on the web page, while `MenuTitle` can be a shorter version suitable for size-constrained menus. -<div class="notice" markdown="1"> +[notice] If `MenuTitle` is left blank by the CMS author, it'll just default to the value in `Title`. -</div> +[/notice] ## Page Content - :::ss +```ss $Content -It returns the database content of the `Content` property. With the CMS Module, this is the value of the WYSIWYG editor +``` but it is also the standard for any object that has a body of content to output. -<div class="info" markdown="1"> +[info] Please note that this database content can be `versioned`, meaning that draft content edited in the CMS can be different from published content shown to your website visitors. In templates, you don't need to worry about this distinction. The `$Content` variable contains the published content by default,and only preview draft content if explicitly requested (e.g. by the "preview" feature in the CMS) (see the [versioning documentation](/../model/versioning) for more details). -</div> +[/info] ### 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 web pages. You'll need to install it via `composer`. -</div> +[/notice] - :::ss +```ss $SiteConfig.Title -The [SiteConfig](../configuration/siteconfig) object allows content authors to modify global data in the CMS, rather +``` than PHP code. By default, this includes a Website title and a Tagline. `SiteConfig` can be extended to hold other data, for example a logo image which can be uploaded through the CMS or @@ -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 on a per-page basis. -<div class="notice" markdown="1"> +[notice] If you don’t want to include the title tag use `$MetaTags(false)`. -</div> +[/notice] By default `$MetaTags` renders: - :::ss +```ss <title>Title of the Page -`$MetaTags(false)` will render +``` - :::ss +```ss -If using `$MetaTags(false)` we can provide a more custom `title`. +``` - :::ss +```ss $MetaTags(false) $Title - Bob's Fantasy Football -## Links +``` - :::ss +```ss .. -All objects that could be accessible in SilverStripe should define a `Link` method and an `AbsoluteLink` method. Link +``` returns the relative URL for the object and `AbsoluteLink` outputs your full website address along with the relative link. - :::ss +```ss $Link $AbsoluteLink -### Linking Modes +``` - :::ss +```ss $isSection $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. For instance, to only show the menu item linked if it's the current one: - :::ss +```ss <% if $isCurrent %> $Title <% else %> $Title <% end_if %> +``` An example for checking for `current` or `section` is as follows: - :::ss +```ss $MenuTitle - +``` **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. - :::ss +```ss <% if $InSection(about-us) %>

You are viewing the about us section

<% end_if %> - +``` ### URLSegment This returns the part of the URL of the page you're currently on. For example on the `/about-us/offices/` web page the `URLSegment` will be `offices`. `URLSegment` cannot be used to generate a link since it does not output the full path. It can be used within templates to generate anchors or other CSS classes. - :::ss +```ss
-## ClassName +``` Returns the class of the current object in [scope](syntax#scope) such as `Page` or `HomePage`. The `$ClassName` can be handy for a number of uses. A common use case is to add to your `` tag to influence CSS styles and JavaScript behavior based on the page type used: - :::ss +```ss -## Children Loops +``` - :::ss +```ss <% loop $Children %> <% end_loop %> -Will loop over all Children records of the current object context. Children are pages that sit under the current page in +``` the `CMS` or a custom list of data. This originates in the `Versioned` extension's `getChildren` method. -
+[alert] For doing your website navigation most likely you'll want to use `$Menu` since its independent of the page context. -
+[/alert] ### ChildrenOf - :::ss +```ss <% loop $ChildrenOf() %> <% end_loop %> -Will create a list of the children of the given page, as identified by its `URLSegment` value. This can come in handy +``` because it's not dependent on the context of the current page. For example, it would allow you to list all staff member pages underneath a "staff" holder on any page, regardless if its on the top level or elsewhere. @@ -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 preference, `AllChildren` does not filter by `ShowInMenus`. - :::ss +```ss <% loop $AllChildren %> ... <% end_loop %> - +``` ### Menu Loops - :::ss +```ss <% loop $Menu(1) %> ... <% end_loop %> -`$Menu(1)` returns the top-level menu of the website. You can also create a sub-menu using `$Menu(2)`, and so forth. +``` -
+[notice] Pages with the `ShowInMenus` property set to `false` will be filtered out. -
+[/notice] ## Access to a specific Page - :::ss +```ss <% with $Page(my-page) %> $Title <% end_with %> -Page will return a single page from site, looking it up by URL. +``` ## Access to Parent and Level Pages ### Level - :::ss +```ss <% with $Level(1) %> $Title <% end_with %> -Will return a page in the current path, at the level specified by the numbers. It is based on the current page context, +``` looking back through its parent pages. `Level(1)` being the top most level. For example, imagine you're on the "bob marley" page, which is three levels in: "about us > staff > bob marley". @@ -292,7 +294,7 @@ For example, imagine you're on the "bob marley" page, which is three levels in: ### Parent - :::ss +```ss $Parent.Title @@ -301,7 +303,7 @@ For example, imagine you're on the "bob marley" page, which is three levels in: $Parent.Parent.Title - +``` ## Navigating Scope See [scope](syntax#scope). @@ -314,29 +316,29 @@ for website users. While you can achieve breadcrumbs through the `$Level()` control manually, there's a nicer shortcut: The `$Breadcrumbs` variable. - :::ss +```ss $Breadcrumbs -By default, it uses the template defined in `cms/templates/BreadcrumbsTemplate.ss` +``` - :::ss +```ss <% if $Pages %> <% loop $Pages %> <% if $Last %>$Title.XML<% else %>$MenuTitle.XML »<% end_if %> <% end_loop %> <% end_if %> -
+``` 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. -
+[/info] ## Forms - :::ss +```ss $Form -A page will normally contain some content and potentially a form of some kind. For example, the log-in page has a the +``` SilverStripe log-in form. If you are on such a page, the `$Form` variable will contain the HTML content of the form. Placing it just below `$Content` is a good default. diff --git a/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md b/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md index 3b37ddb26..f07cceaef 100644 --- a/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md +++ b/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md @@ -1,6 +1,8 @@ +--- title: Requirements summary: How to include and require other assets in your templates such as javascript and CSS files. - +iconBrand: js +--- # Requirements 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 `` tag of your template, as it enables a more **mysite/templates/Page.ss** -``` -<% require css("cms/css/TreeSelector.css") %> -<% require themedCSS("TreeSelector") %> -<% require javascript("cms/javascript/LeftAndMain.js") %> ``` -
+[alert] Requiring assets from the template is restricted compared to the PHP API. -
+[/alert] ## PHP Requirements API It is common practice to include most Requirements either in the *init()*-method of your [controller](../controllers/), or as close to rendering as possible (e.g. in [api:FormField]). -```php -` element, so you can define 'screen' or 'print' for example. -```php -Requirements::css("cms/css/TreeSelector.css", "screen,projection"); ``` ### Javascript Files -```php -Requirements::javascript($path); ``` A variant on the inclusion of custom javascript is the inclusion of *templated* javascript. Here, you keep your JavaScript in a separate file and instead load, via search and replace, several PHP-generated variables into that code. -```php -$vars = array( - "EditorCSS" => "cms/css/editor.css", -); - -Requirements::javascriptTemplate("cms/javascript/editor.template.js", $vars); ``` In this example, `editor.template.js` is expected to contain a replaceable variable expressed as `$EditorCSS`. @@ -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 language. -```php -Requirements::customScript(<< +[alert] To make debugging easier in your local environment, combined files is disabled when running your application in `dev` mode. - +[/alert] By default it stores the generated file in the assets/ folder, but you can configure this by pointing the `Requirements.combined_files_folder` configuration setting to a specific folder. **mysite/_config/app.yml** -```yaml -Requirements: - combined_files_folder: '_combined' ``` -
+[info] 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. -
+[/info] You can also combine CSS files into a media-specific stylesheets as you would with the `Requirements::css` call - use the third paramter of the `combine_files` function: -```php -$printStylesheets = array( - "$themeDir/css/print_HomePage.css", - "$themeDir/css/print_Page.css", -); - -Requirements::combine_files('print.css', $printStylesheets, 'print'); ``` 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 -```php -Requirements::clear(); ``` Clears all defined requirements. You can also clear specific requirements. -```php -Requirements::clear(THIRDPARTY_DIR.'/prototype.js'); ``` -
+[alert] Depending on where you call this command, a Requirement might be *re-included* afterwards. -
+[/alert] ## 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 version in a custom location. This assumes you have tested your application with the newer version. -```php -Requirements::block(THIRDPARTY_DIR . '/jquery/jquery.js'); ``` -
+[alert] 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. -
+[/alert] ## Inclusion Order Requirements acts like a stack, where everything is rendered sequentially in the order it was included. There is no way to change inclusion-order, other than using *Requirements::clear* and rebuilding the whole set of requirements. -
+[alert] 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. -
+[/alert] ## Javascript placement By default, SilverStripe includes all Javascript files at the bottom of the page body, unless there's another script already loaded, then, it's inserted before the first `