Removed deprecated tutorials from docs

This commit is contained in:
Ingo Schommer 2017-12-19 11:44:51 +13:00
parent 58046a3a3f
commit 2fa1114bd3
10 changed files with 3 additions and 2100 deletions

View File

@ -1,13 +1,5 @@
title: Tutorials
introduction: The tutorials below take a step by step look at how to build a SilverStripe application.
## Written Tutorials
<div class="alert" markdown="1">
These tutorials are deprecated, and have been replaced by the new [Lessons](http://silverstripe.org/learn/lessons) section.
</div>
## Video lessons
These include video screencasts, written tutorials and code examples to get you started working with SilverStripe websites.
title: Lessons
introduction: The lessons take a step by step look at how to build a SilverStripe application.
* [How to set up a local development environment in SilverStripe](https://vimeo.com/108861537)
* [Lesson 1: Creating your first theme](http://www.silverstripe.org/learn/lessons/creating-your-first-theme)
@ -32,5 +24,4 @@ These include video screencasts, written tutorials and code examples to get you
## Help: If you get stuck
* [Common Problems](/getting_started/installation/common_problems): Review some existing solutions to common problems.
* [SilverStripe Community Forums](http://www.silverstripe.org/community/forums/): Head over to the forums and ask the community
for help.
* [SilverStripe Community](http://www.silverstripe.org/community/): Join our community chat via Slack, or ask a question on Stack Overflow.

View File

@ -1,429 +0,0 @@
title: Building a basic site
summary: An overview of the SilverStripe installation and an introduction to creating a web page.
<div class="alert" markdown="1">
This tutorial is deprecated, and has been replaced by Lessons 1, 2, 3, and 4 in the [Lessons section](http://www.silverstripe.org/learn/lessons)
</div>
# Tutorial 1 - Building a Basic Site
## Overview
Welcome to the first in this series of tutorials on the SilverStripe Content Management System (CMS).
These tutorials are designed to take you from an absolute beginner to being able to build large, complex websites with SilverStripe. We assume to begin with, that you have some XHTML, CSS and PHP knowledge. This first tutorial provides an absolute
introduction to building a simple website using SilverStripe. It will also teach you how to use the content management system at a basic level.
## What are we working towards?
We are going to create a site in which all the content can be edited in the SilverStripe CMS. It will have a two-level
navigation system, which will be generated on the fly to include all pages you add in the CMS. We will use two different
templates - one for the home page, and one for the rest of the site.
## Installation
You need to [download the SilverStripe software](http://www.silverstripe.org/software/download) and install it to your local machine or to a webserver.
For more information about installing and configuring a webserver read the [Installation instructions and videos](/getting_started/installation).
This tutorial uses the SilverStripe CMS default theme 'Simple' which you will find in the themes folder. We will investigate the existing template files that make up the theme as well as create some new files to build upon the theme.
## Exploring the installation
After installation, open up the folder where you installed SilverStripe.
If you installed on windows with WAMP, it will likely be at *c:\wamp\www*. On Mac OS X, using the built in webserver, it will be in your sites directory */Sites/* (with MAMP, it will likely be at */Applications/MAMP/htdocs/*)
Let's have a look at the folder structure.
| Directory | | Description |
| --------- | | ----------- |
| assets/ | | Contains images and other files uploaded via the SilverStripe CMS. You can also place your own content inside it, and link to it from within the content area of the CMS. |
| cms/ | | Contains all the files that form the CMS area of your site. Its structure is similar to the mysite/ directory, so if you find something interesting, it should be easy enough to look inside and see how it was built. |
| framework/ | | The framework that builds both your own site and the CMS that powers it. Youll 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
### User Interface Basics
![](../_images/tutorial1_cms-basic.jpg)
The CMS is the area in which you can manage your site content. You can access the cms at `http://localhost/your_site_name/admin` (or `http://yourdomain.com/admin` if you are using your own domain name). You
will be presented with a login screen. Login using the details you provided at installation. After logging in you
should see the CMS interface with a list of the pages currently on your website (the site tree). Here you can add, delete and reorganize pages. If you need to delete, publish, or unpublish a page, first check "multi-selection" at the top. You will then be able to perform actions on any checked files using the "Actions" dropdown. Clicking on a page will open it in the page editing interface pictured below (we've entered some test content).
![](../_images/tutorial1_cms-numbered.jpg)
1. This menu allows you to move between different sections of the CMS. There are four core sections - "Pages", "Files", "Users" and "Settings". If you have modules installed, they may have their own sections here. In this tutorial we will be focusing on the "Pages" section.
2. The breadcrumbs on the left will show you a direct path to the page you are currently looking at. You can use this path to navigate up through a page's hierarchy. On the left there are tabs you may use to flick between different aspects of a page. By default, you should be shown three tabs: "Content", "Settings", and "History".
* Content - Allows you to set the title, wysiwyg content, URL and Meta data for your page.
* Settings - Here you set the type of page behavior, parent page, show in search, show in menu, and who can view or edit the page.
* History - This allows you to view previous version of your page, compare, change, and revert to previous version if need be.
3. Within the "Pages" section (provided you are in the "Content" or "Settings" tab) you can quickly move between pages in the CMS using the site tree. To collapse and expand this sidebar, click the arrow at the bottom. If you are in the history tab, you will notice the site tree has been replaced by a list of the alterations to the current page.
![](../_images/tutorial1_cms-numbered-3.jpg)
4. This section allows you to edit the content for the currently selected page, as well as changing other properties of the page such as the page name and URL. The content editor has full [WYSIWYG](http://en.wikipedia.org/wiki/WYSIWYG) capabilities, allowing you to change formatting and insert links, images, and tables.
5. These buttons allow you to save your changes to the draft copy, publish your draft copy, unpublish from the live website, or remove a page from the draft website. The SilverStripe CMS workflow stores two copies of a page, a draft and a published copy. By having separate draft and published copies, we can preview draft changes on the site before publishing them to the live website. You can quickly preview your draft pages without leaving the CMS by clicking the "Preview" button.
![](../_images/tutorial1_cms-numbered-5.jpg)
### Try it
There are three pages already created for you - "Home", "About Us" and "Contact Us". Experiment
with the editor - try different formatting, tables and images. When you are done, click "Save Draft" or "Save
& Publish" to post the content to the live site.
### New pages
To create a new page, click the "Add New" button above the site tree.
When you create a new page, you are given the option of setting the structure of the page ("Top level" or "Under another page") and the page type.
The page type specifies the templates used to render the page, the fields that are able to be edited in the CMS, and page specific behavior. We will explain page types in more depth as we progress; for now, make all pages of the type "Page".
![](../_images/tutorial1_addpage.jpg)
**SilverStripe's friendly URLs**
While you are on the draft or live SilverStripe site, you may notice the URLs point to files that don't exist, e.g.
`http://localhost/contact` or `http://yourdomainname.com/about-us` etc. SilverStripe uses the URL field on the Meta-Data tab of the Edit Page -> Content section to look up the appropriate
page in the database.
Note that if you have sub-pages, changing the Top level URL field for a page will affect the URL for all sub-pages. For example, if we changed the URL field "/about-us/" to "/about-silverstripe/" then the sub-pages URLs would now be "/about-silverstripe/URL-of-subpage/" rather than "/about-us/URL-of-subpage/".
![](../_images/tutorial1_url.jpg)
When you create a new page, SilverStripe automatically creates an appropriate URL for it. For example, *About Us* will
become *about-us*. You are able to change it yourself so that you can make long titles more usable or descriptive. For
example, *Employment Opportunities* could be shortened to *jobs*. The ability to generate easy-to-type, descriptive URLs
for SilverStripe pages improves accessibility for humans and search engines.
You should ensure the URL for the home page is *home*, as that is the page SilverStripe loads by default.
## Templates
All pages on a SilverStripe site are rendered using a template. A template is a file
with a special `*.ss` file extension, containing HTML augmented with some control codes. Through the use of templates, you can have as much control over your sites HTML code as you like. In SilverStripe, the template files and others for controlling your sites appearance, such as the CSS, images, and some javascript, are collectively described as a theme. Themes live in the 'themes' folder of your site.
Every page in your site has a **page type**. We will briefly talk about page types later, and go into much more detail
in tutorial two; right now all our pages will be of the page type *Page*. When rendering a page, SilverStripe will look
for a template file in the *simple/templates* folder, with the name `<PageType>`.ss - in our case *Page.ss*.
Open *themes/simple/templates/Page.ss*. It uses standard HTML apart from these exceptions:
```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
$Title
$SiteConfig.Title
```
These two variables are found within the html `<title>` tag, and are replaced by the "Page Name" and "Settings -> Site Title" fields in the CMS.
```ss
$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
$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
$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
<% %>
```
**Flushing the cache**
Whenever we edit a template file, we need to append *?flush=1* onto the end of the URL, e.g.
`http://localhost/your_site_name/?flush=1`. SilverStripe stores template files in a cache for quicker load times. Whenever there are
changes to the template, we must flush the cache in order for the changes to take effect.
## The Navigation System
We are now going to look at how the navigation system is implemented in the template.
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
<% 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
<ul>
<% loop $Menu(1) %>
<li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>">
<a href="$Link" title="$Title.XML">$MenuTitle.XML</a>
</li>
<% 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:
![](../_images/tutorial1_menu.jpg)
### Highlighting the current page
A useful feature is highlighting the current page the user is looking at. We can do this by using the `is` methods `$isSection` and `$isCurrent`.
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
<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
.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.
The "About Us" section could use some expansion.
Select "Add New" in the Pages section, and create two new pages nested under the page "About Us" called "What we do" and "Our History" with the type "Page".
You can also create the pages elsewhere on the site tree, and drag and drop the pages into place.
Either way, your site tree should now look something like this:
![](../_images/tutorial1_2nd_level-cut.jpg)
Great, we now have a hierarchical site structure! Let's look at how this is created and displayed in our template.
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
<ul>
<% loop $Menu(2) %>
<li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>">
<a href="$Link" title="Go to the $Title.XML page">
<span class="arrow"></span>
<span class="text">$MenuTitle.XML</span>
</a>
</li>
<% 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.
To make sure the menu is not displayed on every page, for example, those that *don't* have any nested pages. We use an **if block**.
Look again in the *Sidebar.ss* file and you will see that the menu is surrounded with an **if block**
like this:
```ss
<% if $Menu(2) %>
...
<ul>
<% loop $Menu(2) %>
<li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>">
<a href="$Link" title="Go to the $Title.XML page">
<span class="arrow"></span>
<span class="text">$MenuTitle.XML</span>
</a>
</li>
<% end_loop %>
</ul>
...
<% 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.
Now that we have two levels of navigation, it would also be useful to include some "breadcrumbs".
Open up */themes/simple/templates/Includes/BreadCrumbs.ss* template and look at the following code:
```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
$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.
![](../_images/tutorial1_menu-two-level.jpg)
Feel free to experiment with the if and loop statements. For example, you could create a drop down style menu from the top navigation using a combination of if statements, loops, and some CSS to style it.
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
<ul>
<% loop $Menu(1) %>
<li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>">
<a href="$Link" title="$Title.XML">$MenuTitle.XML</a>
<% if $Children %>
<ul>
<% loop $Children %>
<li class="<% if $isCurrent %>current<% else_if $isSection %>section<% end_if %>">
<a href="$Link" title="Go to the $Title.XML page">
<span class="arrow"></span>
<span class="text">$MenuTitle.XML</span>
</a>
</li>
<% end_loop %>
</ul>
<% end_if %>
</li>
<% end_loop %>
</ul>
```
## Using a different template for the home page
So far, a single template layout *Layouts/Page.ss* is being used for the entire site. This is useful for the purpose of this
tutorial, but in a finished website we can expect there to be several page layouts.
To illustrate how we do this, we will create a new template for the homepage. This template will have a large graphical
banner to welcome visitors.
### Creating a new page type
Earlier we stated that every page in a SilverStripe site has a **page type**, and that SilverStripe will look for a
template, or template layout, corresponding to the page type. Therefore, the first step when switching the homepage template is to create a new page type.
Each page type is represented by two PHP classes: a *data object* and a *controller*. Don't worry about the details of page
types right now, we will go into much more detail in the [next tutorial](/tutorials/extending_a_basic_site).
Create a new file *HomePage.php* in *mysite/code*. Copy the following code into it:
```php
use Page;
use PageController;
class HomePage extends Page
{
}
class HomePageController extends PageController
{
}
```
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`.
It may take a moment, so be patient. This adds tables and fields needed by your site, and modifies any structures that have changed. It
does this non-destructively - it will never delete your data.
As we have just created a new page type, SilverStripe will add this to the list of page types in the database.
### Changing the page type of the Home page
After building the database, we can change the page type of the homepage in the CMS.
In the CMS, navigate to the "Home" page and switch to the "Settings" tab. Change "Page type" to *Home Page*, and click "Save & Publish".
![](../_images/tutorial1_homepage-type.jpg)
Our homepage is now of the page type *HomePage*. Regardless, it is still
rendered with the *Page* template. SilverStripe does this as our homepage inherits its type from *Page*,
which acts as a fallback if no *HomePage* template can be found.
It always tries to use the most specific template in an inheritance chain.
### Creating a new template
To create a new template layout, create a copy of *Page.ss* (found in *themes/simple/templates/Layout*) and call it *HomePage.ss*. If we flush the cache (*?flush=1*), SilverStripe should now be using *HomePage.ss* for the homepage, and *Page.ss* for the rest of the site. Now let's customise the *HomePage* template.
First, we don't need the breadcrumbs and the secondary menu for the homepage. Let's remove them:
```ss
<% include SideBar %>
```
We'll also replace the title text with an image. Find this line:
```ss
<h1>$Title</h1>
```
and replace it with:
```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:
![](../_images/tutorial1_home-template.jpg)
SilverStripe first searches for a template in the *themes/simple/templates* folder. Since there is no *HomePage.ss*,
it will use the *Page.ss* for both *Page* and *HomePage* page types. When it comes across the *$Layout* tag, it will
then descend into the *themes/simple/templates/Layout* folder, and will use *Page.ss* for the *Page* page type, and
*HomePage.ss* for the *HomePage* page type. So while you could create a HomePage.ss in the *themes/simple/templates/* it is better to reuse the navigation and footer common to both our Home page and the rest of the pages on our website.
![](../_images/tutorial1_subtemplates-diagram.jpg)
## Summary
So far we have taken a look at the different areas and functionality within the pages area of the CMS. We have learnt about template variables, controls and if statements and used these to build a basic, but fully functional, website. We have also briefly covered page types, and looked at how they correspond to templates and sub-templates. Using this knowledge, we have customised our website's homepage design.
In the next tutorial, [Extending a Basic Site](/tutorials/extending_a_basic_site), we will explore page types on a deeper level, and look at customising our own page types to extend the functionality of SilverStripe.
[Next tutorial >>](/tutorials/extending_a_basic_site)

View File

@ -1,584 +0,0 @@
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">
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>
# Tutorial 2 - Extending a basic site
## Overview
In the [first tutorial (Building a basic site)](/tutorials/building_a_basic_site) we learnt how to create a basic site using SilverStripe. This tutorial will build on that, and explore extending SilverStripe by creating our own page types. After doing this we should have a better understanding of how SilverStripe works.
## What are we working towards?
We are going to work on adding two new sections to the site we built in the first tutorial.
The first of these new sections will be *News*, with a recent news listing on the homepage and an RSS feed:
![](../_images/tutorial2_newslist.jpg)
The second will be a *Staff* section, to demonstrate more complex database structures (such as associating an image with each staff member):
![](../_images/tutorial2_einstein.jpg)
## The SilverStripe data model
A large part of designing complex SilverStripe sites is the creation of our own page types. Before we progress any further, it is important to understand what a page type is and how the SilverStripe data model works.
SilverStripe is based on the **"Model-View-Controller"** design pattern. This means that SilverStripe attempts to separate data, logic and presentation as much as possible. Every page has three separate parts which are combined to give you the
final page. Lets look at each one individually:
### Model
All content on our site is stored in a database. Each class that is a child of the [DataObject](api:SilverStripe\ORM\DataObject) class will have its own table in our database.
Every object of such a class will correspond to a row in that table -
this is our "data object", the **"model"** of Model-View-Controller. A page type has a data object that represents all the data for our page. Rather than inheriting
directly from [DataObject](api:SilverStripe\ORM\DataObject), it inherits from [SiteTree](api:SilverStripe\CMS\Model\SiteTree). We generally create a "Page" data object, and subclass this for all other page types. This allows us to define behavior that is consistent across all pages in our site.
### View
The **"view"** is the presentation of our site. As we have already seen, the templates SilverStripe uses to render a page are dependent on the page type. Using templates and css, we are able to have full control over the
presentation of our website.
### Controller
Each page type also has a **"controller"**. The controller contains all the code used to manipulate our data before it is rendered. For example, suppose we were making an auction site, and we only wanted to display the auctions closing in the next ten minutes. We would implement this logic in the controller. The controller for a page should inherit from [ContentController](api:SilverStripe\CMS\Controllers\ContentController). Just as we create a "Page" data object and subclass it for the rest of the site, we also create a "PageController" that is subclassed.
Creating a new page type requires creating each of these three elements. We will then have full control over presentation, the database, and editable CMS fields.
A more in-depth introduction of Model-View-Controller can be found
[here](http://www.slash7.com/articles/2005/02/22/mvc-the-most-vexing-conundrum).
![](../_images/tutorial2_pagetype-inheritance.jpg)
## Creating the news section page types
To create a News section we'll need two new page types. The first one is obvious: we need an *ArticlePage* page type. The second is a little less obvious: we need an *ArticleHolder* page type to contain our article pages.
We'll start with the *ArticlePage* page type. First we create the model, a class called "ArticlePage". We put the *ArticlePage* class into a file called "ArticlePage.php" inside *mysite/code*. All other classes relating to *ArticlePage* should be placed within "ArticlePage.php", this includes our controller (*ArticlePageController*).
**mysite/code/ArticlePage.php**
```php
class ArticlePage extends Page
{
}
```
**mysite/code/ArticlePageController.php**
```php
class ArticlePageController extends PageController
{
}
```
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.
Let's create the *ArticleHolder* page type.
**mysite/code/ArticleHolder.php**
```php
class ArticleHolder extends Page
{
private static $allowed_children = ['ArticlePage'];
}
```
**mysite/code/ArticleHolderController.php**
```php
class ArticleHolderController extends PageController
{
}
```
Here we have done something interesting: the *$allowed_children* field. This is one of a number of static fields we can define to change the properties of a page type. The *$allowed_children* field is an array of page types that are allowed
to be children of the page in the site tree. As we only want **news articles** in the news section, we only want pages of the type *ArticlePage* as children. We can enforce this in the CMS by setting the *$allowed_children* field within this class.
We will be introduced to other fields like this as we progress; there is a full list in the documentation for [SiteTree](api:SilverStripe\CMS\Model\SiteTree).
Now that we have created our page types, we need to let SilverStripe rebuild the database: [http://localhost/your_site_name/dev/build](http://localhost/your_site_name/dev/build). SilverStripe should detect that there are two new page types, and add them to the list of page types in the database.
<div class="hint" markdown="1">
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>
## Adding date and author fields
Now that we have an *ArticlePage* page type, let's make it a little more useful. We can use
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
class ArticlePage extends Page
{
private static $db = [
'Date' => 'Date',
'Author' => 'Text'
];
}
```
Every entry in the array is a *key => value* pair. The **key** is the name of the field, and the **value** is the type. See ["data types and casting"](/developer_guides/model/data_types_and_casting) for a complete list of types.
<div class="hint" markdown="1">
The names chosen for the fields you add must not already be used. Be careful using field names such as Title,
Content etc. as these may already be defined in the page types your new page is extending from.
</div>
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
class ArticlePage extends Page
{
public function getCMSFields()
{
$fields = parent::getCMSFields();
$dateField = new DateField('Date');
$dateField->setConfig('showcalendar', true);
$fields->addFieldToTab('Root.Main', $dateField, 'Content');
$fields->addFieldToTab('Root.Main', new TextField('Author'), 'Content');
return $fields;
}
}
```
Let's walk through this method.
```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 [FieldList](api:SilverStripe\Forms\FieldList) object.
```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 [FormField](api:SilverStripe\Forms\FormField) - see the documentation for more details.
<div class="hint" markdown="1">
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>
We have added two fields: A simple [TextField](api:SilverStripe\Forms\TextField) and a [DateField](api:SilverStripe\Forms\DateField).
There are many more fields available in the default installation, listed in ["form field types"](/developer_guides/forms/field_types/common_subclasses).
```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.
![](../_images/tutorial2_news-cms.jpg)
## Modifying the date field
At the moment, your date field will look just like a text field.
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
class ArticlePage extends Page
{
public function getCMSFields()
{
$fields = parent::getCMSFields();
$dateField = new DateField('Date', 'Article Date (for example: 20/12/2010)');
$dateField->setConfig('showcalendar', true);
$dateField->setConfig('dateformat', 'dd/MM/YYYY');
$fields->addFieldToTab('Root.Main', $dateField, 'Content');
$fields->addFieldToTab('Root.Main', new TextField('Author', 'Author Name'), 'Content');
return $fields;
}
}
```
Let's walk through these changes.
```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
$dateField->setConfig('showcalendar', true);
```
By enabling *showCalendar* you show a calendar overlay when clicking on the field.
```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 [DBDateField](api:SilverStripe\ORM\FieldType\DBDateField) documentation for more configuration options.
```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
Because our new pages inherit their templates from *Page*, we can view anything entered in the content area when navigating to these pages on our site. However, as there is no reference to the date or author fields in the *Page* template this data is not being displayed.
To fix this we will create a template for each of our new page types. We'll put these in *themes/simple/templates/Layout* so we only have to define the page specific parts: SilverStripe will use *themes/simple/templates/Page.ss* for the basic
page layout.
### ArticlePage Template
First, the template for displaying a single article:
**themes/simple/templates/Layout/ArticlePage.ss**
```ss
<% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article>
<h1>$Title</h1>
<div class="news-details">
<p>Posted on $Date.Nice by $Author</p>
</div>
<div class="content">$Content</div>
</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 [SiteTree](api:SilverStripe\CMS\Model\SiteTree) class. *$Date* and *$Author* come from the *ArticlePage* table through your custom Page. *$Content* comes from the *SiteTree* table through the same data object. The data for your page is
spread across several tables in the database matched by id - e.g. *Content* is in the *SiteTree* table, and *Date* and
*Author* are in the *ArticlePage* table. SilverStripe matches this data, and collates it into a single data object.
![](../_images/tutorial2_data-collation.jpg)
Rather than using *$Date* directly, we use *$Date.Nice*. If we look in the [DBDate](api:SilverStripe\ORM\FieldType\DBDate) documentation, we can see
that the *Nice* function returns the date in *dd/mm/yyyy* format, rather than the *yyyy-mm-dd* format stored in the
database.
![](../_images/tutorial2_news.jpg)
###ArticleHolder Template
We'll now create a template for the article holder. We want our news section to show a list of news items, each with a summary and a link to the main article (our Article Page).
**themes/simple/templates/Layout/ArticleHolder.ss**
```ss
<% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article>
<h1>$Title</h1>
$Content
<div class="content">$Content</div>
</article>
<% loop $Children %>
<article>
<h2><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></h2>
<p>$Content.FirstParagraph</p>
<a href="$Link" title="Read more on &quot;{$Title}&quot;">Read more &gt;&gt;</a>
</article>
<% end_loop %>
$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 [DBHTMLText](api:SilverStripe\ORM\FieldType\DBHTMLText) field gives us a nice summary of the article. The function strips all tags from the paragraph extracted.
![](../_images/tutorial2_articleholder.jpg)
### Using include files in templates
We can make our templates more modular and easier to maintain by separating commonly-used components in to *include files*. We are already familiar with the `<% include Sidebar %>` line from looking at the menu in the [first tutorial (Building a basic site)](../tutorials/building_a_basic_site).
We'll separate the display of linked articles as we want to reuse this code later on.
Cut the code between "loop Children" in *ArticleHolder.ss** and replace it with an include statement:
**themes/simple/templates/Layout/ArticleHolder.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
<article>
<h2><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></h2>
<p>$Content.FirstParagraph</p>
<a href="$Link" title="Read more on &quot;{$Title}&quot;">Read more &gt;&gt;</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
private static $icon = "cms/images/treeicons/news-file.gif";
```
And this one to the *HomePage* class:
```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">
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>
## Showing the latest news on the homepage
It would be nice to greet page visitors with a summary of the latest news when they visit the homepage. This requires a little more code though - the news articles are not direct children of the homepage, so we can't use the *Children* control. We can get the data for news articles by implementing our own function in *HomePageController*.
**mysite/code/HomePage.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
<!-- ... -->
<div class="content">$Content</div>
</article>
<% loop $LatestNews %>
<% 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.
![](../_images/tutorial2_homepage-news.jpg)
## Creating a RSS feed
An RSS feed is something that no news section should be without. SilverStripe makes it easy to create RSS feeds by providing an [RSSFeed](api:SilverStripe\Control\RSS\RSSFeed) class to do all the hard work for us. Add the following in the *ArticleHolderController* class:
**mysite/code/ArticleHolder.php**
```php
private static $allowed_actions = [
'rss'
];
public function rss()
{
$rss = new RSSFeed($this->Children(), $this->Link(), "The coolest news around");
return $rss->outputToBrowser();
}
```
Ensure that when you have input the code to implement an RSS feed; flush the webpage afterwards
(add `?flush=1` on the end of your URL). This is because `allowed_actions` has changed.
This function creates an RSS feed of all the news articles, and outputs it to the browser. If we go to [http://localhost/your_site_name/news/rss](http://localhost/your_site_name/news/rss) we should see our RSS feed. When there is more to a URL after a page's base URL, "rss" in this case, SilverStripe will call the function with that name on the controller if it exists.
Depending on your browser, you should see something like the picture below. If your browser doesn't support RSS, you will most likely see the XML output instead. For more on RSS, see [RSSFeed](api:SilverStripe\Control\RSS\RSSFeed)
![](../_images/tutorial2_rss-feed.jpg)
Now all we need is to let the user know that our RSS feed exists. Add this function to *ArticleHolderController*:
**mysite/code/ArticleHolder.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
Now that we have a complete news section, let's take a look at the staff section. We need to create *StaffHolder* and *StaffPage* page types, for an overview on all staff members and a detail-view for a single member. First let's start with the *StaffHolder* page type.
**mysite/code/StaffHolder.php**
```php
class StaffHolder extends Page
{
private static $db = [];
private static $has_one = [];
private static $allowed_children = [StaffPage::class];
}
```
**mysite/code/StaffHolderController.php**
```php
class StaffHolderController extends PageController
{
}
```
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
use SilverStripe\AssetAdmin\Forms\UploadField;
use SilverStripe\Assets\Image;
class StaffPage extends Page
{
private static $db = [];
private static $has_one = [
'Photo' => Image::class
];
public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->addFieldToTab("Root.Images", new UploadField('Photo'));
return $fields;
}
}
```
**mysite/code/StaffPageController.php**
```php
class StaffPageController extends PageController
{
}
```
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 [UploadField](api:SilverStripe\AssetAdmin\Forms\UploadField) in the *getCMSFields* function to the tab "Root.Images". Since this tab doesn't exist,
the *addFieldToTab* function will create it for us. The *UploadField* allows us to select an image or upload a new one in
the CMS.
![](../_images/tutorial2_photo.jpg)
Rebuild the database ([http://localhost/your_site_name/dev/build](http://localhost/your_site_name/dev/build)) and open the CMS. Create
a new *StaffHolder* called "Staff", and create some *StaffPage*s in it.
![](../_images/tutorial2_create-staff.jpg)
### Creating the staff section templates
The staff section templates aren't too difficult to create, thanks to the utility methods provided by the [Image](api:SilverStripe\Assets\Image) class.
**themes/simple/templates/Layout/StaffHolder.ss**
```ss
<% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article>
<h1>$Title</h1>
$Content
<div class="content">$Content</div>
</article>
<% loop $Children %>
<article>
<h2><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></h2>
$Photo.ScaleWidth(150)
<p>$Content.FirstParagraph</p>
<a href="$Link" title="Read more on &quot;{$Title}&quot;">Read more &gt;&gt;</a>
</article>
<% end_loop %>
$Form
</div>
```
This template is very similar to the *ArticleHolder* template. The *ScaleWidth* method of the [Image](api:SilverStripe\Assets\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.
![](../_images/tutorial2_staff-section.jpg)
The *StaffPage* template is also very straight forward.
**themes/simple/templates/Layout/StaffPage.ss**
```ss
<% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article>
<h1>$Title</h1>
<div class="content">
$Photo.ScaleWidth(433)
$Content</div>
</article>
$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)
## Summary
In this tutorial we have explored the concept of page types. In the process of creating and extending page types we have covered many of the concepts required to build a site with SilverStripe.
[Next Tutorial >>](/tutorials/forms)

View File

@ -1,406 +0,0 @@
title: Forms
summary: Capture and store user information through web forms.
# Tutorial 3 - Forms
## Overview
This tutorial is intended to be a continuation of the first two tutorials ([first tutorial](/tutorials/building_a_basic_site), [second tutorial](/tutorials/extending_a_basic_site)). In this tutorial we will build on the site we developed in the earlier tutorials and explore forms in SilverStripe. We will look at custom coded forms: forms which need to be written in PHP.
Instead of using a custom coded form, we could use the [userforms module](http://addons.silverstripe.org/add-ons/silverstripe/userforms). This module allows users to construct forms via the CMS. A form created this way is much quicker to implement, but also lacks the flexibility of a coded form.
## What are we working towards?
We will create a poll on the home page that asks the user their favourite web browser, and displays a bar graph of the results.
![tutorial:tutorial3_pollresults.png](../_images/tutorial3_pollresults.jpg)
## Creating the form
The poll we will be creating on our homepage will ask the user for their name and favourite web browser. It will then collate the results into a bar graph. We create the form in a method on *HomePageController*.
**mysite/code/HomePageController.php**
```php
use PageController;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FormAction;
use SilverStripe\Forms\OptionSetField;
use SilverStripe\Forms\TextField;
class HomePageController extends PageController
{
private static $allowed_actions = ['BrowserPollForm'];
// ...
public function BrowserPollForm()
{
// Create fields
$fields = new FieldList(
new TextField('Name'),
new OptionsetField('Browser', 'Your Favourite Browser', [
'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', [
'Firefox' => 'Firefox',
'Chrome' => 'Chrome',
'Internet Explorer' => 'Internet Explorer',
'Safari' => 'Safari',
'Opera' => 'Opera',
'Lynx' => 'Lynx'
])
);
```
First we create our form fields.
We do this by creating a [FieldList](api:SilverStripe\Forms\FieldList) and passing our fields as arguments.
The first field is a [TextField](api:SilverStripe\Forms\TextField) with the name 'Name'.
There is a second argument when creating a field which specifies the text on the label of the field. If no second
argument is passed, as in this case, it is assumed the label is the same as the name of the field.
The second field we create is an [OptionsetField](api:SilverStripe\Forms\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.
The first argument is the name of the function to call when the button is pressed, and the second is the label of the button.
Here we create a 'Submit' button which calls the 'doBrowserPoll' method, which we will create later.
All the form actions (in this case only one) are collected into a [FieldList](api:SilverStripe\Forms\FieldList) object the same way we did with
the fields.
```php
return new Form($this, 'BrowserPollForm', $fields, $actions);
```
Finally we create the [Form](api:SilverStripe\Forms\Form) object and return it.
The first argument is the controller that contains the form, in most cases '$this'. The second is the name of the method
that returns the form, which is 'BrowserPollForm' in our case. The third and fourth arguments are the
FieldLists containing the fields and form actions respectively.
After creating the form function, we need to add the form to our home page template.
Add the following code to the top of your home page template, just before `<div class="Content">`:
**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,
we need to add some CSS styling.
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 .btn-toolbar {
padding: 5px 0;
}
#BrowserPoll .bar {
background-color: #015581;
}
```
All going according to plan, if you visit [http://localhost/your_site_name/home/?flush=1](http://localhost/your_site_name/home/?flush=1) it should look something like this:
![](../_images/tutorial3_pollform.jpg)
## Processing the form
Great! We now have a browser poll form, but it doesn't actually do anything. In order to make the form work, we have to implement the 'doBrowserPoll()' method that we told it about.
First, we need some way of saving the poll submissions to the database, so we can retrieve the results later. We can do this by creating a new object that extends from [DataObject](api:SilverStripe\ORM\DataObject).
If you recall, in the [second tutorial](/tutorials/extending_a_basic_site) we said that all objects that inherit from DataObject and have their own fields are stored in tables the database. Also recall that all pages extend DataObject indirectly through [SiteTree](api:SilverStripe\CMS\Model\SiteTree). Here instead of extending SiteTree (or [Page](api:SilverStripe\CMS\Model\SiteTree\Page)) to create a page type, we will extend [DataObject](api:SilverStripe\ORM\DataObject) directly:
**mysite/code/BrowserPollSubmission.php**
```php
use SilverStripe\ORM\DataObject;
class BrowserPollSubmission extends DataObject
{
private static $db = [
'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 *HomePageController*:
**mysite/code/HomePageController.php**
```php
use BrowserPollSubmission;
use PageController;
class HomePageController extends PageController
{
// ...
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 [Form](api:SilverStripe\Forms\Form) object.
In our function we create a new *BrowserPollSubmission* object. Since the name of our form fields, and the name of the database fields, are the same we can save the form directly into the data object.
We call the 'write' method to write our data to the database, and '$this->redirectBack()' will redirect the user back to the home page.
## Form validation
SilverStripe forms all have automatic validation on fields where it is logical. For example, all email fields check that they contain a valid email address. You can write your own validation by subclassing the *Validator* class.
SilverStripe provides the *RequiredFields* validator, which ensures that the fields specified are filled in before the form is submitted. To use it we create a new *RequiredFields* object with the name of the fields we wish to be required as the arguments, then pass this as a fifth argument to the Form constructor.
Add a namespace import for `SilverStripe\Forms\RequiredFields`, then change the end of the 'BrowserPollForm' function so it looks like this:
**mysite/code/HomePage.php**
```php
use SilverStripe\Forms\Form;
use SilverStripe\Forms\RequiredFields;
public function BrowserPollForm()
{
// ...
$validator = new RequiredFields('Name', 'Browser');
return new Form($this, 'BrowserPollForm', $fields, $actions, $validator);
}
```
If we then open the homepage and attempt to submit the form without filling in the required fields errors should appear.
![](../_images/tutorial3_validation.jpg)
## Showing the poll results
Now that we have a working form, we need some way of showing the results.
The first thing to do is make it so a user can only vote once per session. If the user hasn't voted, show the form, otherwise show the results.
We can do this using a session variable. The [Session](api:SilverStripe\Control\Session) class handles all session variables in SilverStripe. First modify the 'doBrowserPoll' to set the session variable 'BrowserPollVoted' when a user votes.
**mysite/code/HomePageController.php**
```php
use SilverStripe\Control\Session;
use PageController;
// ...
class HomePageController extends PageController
{
// ...
public function doBrowserPoll($data, $form)
{
$submission = new BrowserPollSubmission();
$form->saveInto($submission);
$submission->write();
$this->getRequest()->getSession()->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
use PageController;
// ...
class HomePageController extends PageController
{
// ...
public function BrowserPollForm()
{
if ($this->getRequest()->getSession()->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,
or clearing your browsing session through your browsers preferences.
Although the form is not shown, you'll still see the 'Browser Poll' heading. We'll leave this for now: after we've built the bar graph of the results, we'll modify the template to show the graph instead of the form if the user has already voted.
Now that we're collecting data, it would be nice to show the results on the website as well. We could simply output every vote, but that's boring. Let's group the results by browser, through the SilverStripe data model.
In the [second tutorial](/tutorials/extending_a_basic_site), we got a collection of news articles for the home page by using the 'ArticleHolder::get()' function, which returns a [DataList](api:SilverStripe\ORM\DataList). We can get all submissions in the same fashion, through `BrowserPollSubmission::get()`. This list will be the starting point for our result aggregation.
Add the appropriate namespace imports, then create the function 'BrowserPollResults' on the *HomePageController* class.
**mysite/code/HomePageController.php**
```php
use SilverStripe\ORM\GroupedList;
use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ArrayData;
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([
'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 [DataList](api:SilverStripe\ORM\DataList). Then we wrap it inside a [GroupedList](api:SilverStripe\ORM\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([
'Browser' => $browserName,
'Percentage' => $percentage
]));
}
```
Now we create an empty [ArrayList](api:SilverStripe\ORM\ArrayList) to hold the data we'll pass to the template. Its similar to [DataList](api:SilverStripe\ORM\DataList), but can hold arbitrary objects rather than just DataObject` instances. Then we iterate over the 'Browser' submissions field.
The `groupBy()` method splits our list by the 'Browser' field passed to it, creating new lists with submissions just for a specific browser. Each of those lists is keyed by the browser name. The aggregated result is then contained in an [ArrayData](api:SilverStripe\View\ArrayData) object, which behaves much like a standard PHP array, but allows us to use it in SilverStripe templates.
The final step is to create the template to display our data. Change the 'BrowserPoll' div to the below.
**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%">&nbsp;</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,
and the poll results need to be displayed.
We use the normal tactic of putting the data into an unordered list and using CSS to style it, except here we use inline styles to display a bar that is sized proportionate to the number of votes the browser has received. You should now have a complete poll.
![](../_images/tutorial3_pollresults.jpg)
## Summary
In this tutorial we have explored custom php forms, and displayed result sets through Grouped Lists. We have briefly covered the different approaches to creating and using forms. Whether you decide to use the [userforms module](http://addons.silverstripe.org/add-ons/silverstripe/userforms) or create a form in PHP depends on the situation and flexibility required.
[Next Tutorial >>](/tutorials/site_search)

View File

@ -1,165 +0,0 @@
title: Site Search
summary: Enable website search. How to handle paged result sets and the SearchForm class.
# Tutorial 4 - Site Search
## Overview
This is a short tutorial demonstrating how to add search functionality to a SilverStripe site. It is recommended that you have completed the earlier tutorials ([Building a basic site](/tutorials/building_a_basic_site), [Extending a basic site](/tutorials/extending_a_basic_site), [Forms](/tutorials/forms)), especially the tutorial on forms, before attempting this tutorial. While this tutorial will add search functionality to the site built in the previous tutorials, it should be straight forward to follow this tutorial on any site of your own.
## What are we working towards?
We are going to add a search box on the top of the page. When a user types something in the box, they are taken to a results page.
![](../_images/tutorial4_search.jpg)
## Creating the search form
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
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`.
In the simple theme, the SearchForm is already added to the header. We will go through the code and explain it.
## Adding the search form
To add the search form, we can add `$SearchForm` anywhere in our templates. In the simple theme, this is in *themes/simple/templates/Includes/Header.ss*
**themes/simple/templates/Includes/Header.ss**
```ss
<% if $SearchForm %>
<span class="search-dropdown-icon">L</span>
<div class="search-bar">
$SearchForm
</div>
<% end_if %>
<% include Navigation %>
```
This displays as:
![](../_images/tutorial4_searchbox.jpg)
## Showing the results
The results function is already included in the `ContentControllerSearchExtension` which
is applied via `FulltextSearchable::enable()`
**cms/code/search/ContentControllerSearchExtension.php**
```php
use SilverStripe\Core\Extension;
class ContentControllerSearchExtension extends Extension
{
public function results($data, $form, $request)
{
$data = [
'Results' => $form->getResults(),
'Query' => $form->getSearchQuery(),
'Title' => _t('SearchForm.SearchResults', 'Search Results')
];
return $this->owner->customise($data)->renderWith(['Page_results', 'Page']);
}
}
```
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
results page template as *Page_results.ss*. Unfortunately this doesn't work when we are using page types that are
children of the *Page* page type. For example, if someone used the search on the homepage, it would be rendered with
*Homepage.ss* rather than *Page_results.ss*. SilverStripe always looks for the template from the most specific page type
first, so in this case it would use the first template it finds in this list:
* HomePage_results.ss
* HomePage.ss
* Page_results.ss
* Page.ss
We can override this list by using the *renderWith* function. The *renderWith* function takes an array of the names of
the templates you wish to render the page with. Here we first add the data to the page by using the 'customise'
function, and then attempt to render it with *Page_results.ss*, falling back to *Page.ss* if there is no
*Page_results.ss*.
## Creating the template
Lastly we need the template for the search page. This template uses all the same techniques used in previous
tutorials. It also uses a number of pagination variables, which are provided by the [PaginatedList](api:SilverStripe\ORM\PaginatedList)
class.
*themes/simple/templates/Layout/Page_results.ss*
```ss
<div id="Content" class="searchResults">
<h1>$Title</h1>
<% if $Query %>
<p class="searchQuery"><strong>You searched for &quot;{$Query}&quot;</strong></p>
<% end_if %>
<% if $Results %>
<ul id="SearchResults">
<% loop $Results %>
<li>
<a class="searchResultHeader" href="$Link">
<% if $MenuTitle %>
$MenuTitle
<% else %>
$Title
<% end_if %>
</a>
<p>$Content.LimitWordCountXML</p>
<a class="readMoreLink" href="$Link"
title="Read more about &quot;{$Title}&quot;"
>Read more about &quot;{$Title}&quot;...</a>
</li>
<% end_loop %>
</ul>
<% else %>
<p>Sorry, your search query did not return any results.</p>
<% end_if %>
<% if $Results.MoreThanOnePage %>
<div id="PageNumbers">
<% if $Results.NotLastPage %>
<a class="next" href="$Results.NextLink" title="View the next page">Next</a>
<% end_if %>
<% if $Results.NotFirstPage %>
<a class="prev" href="$Results.PrevLink" title="View the previous page">Prev</a>
<% end_if %>
<span>
<% loop $Results.Pages %>
<% if $CurrentBool %>
$PageNum
<% else %>
<a href="$Link" title="View page number $PageNum">$PageNum</a>
<% end_if %>
<% end_loop %>
</span>
<p>Page $Results.CurrentPage of $Results.TotalPages</p>
</div>
<% end_if %>
</div>
```
Then finally add ?flush=1 to the URL and you should see the new template.
![](../_images/tutorial4_search.jpg)
## Summary
This tutorial has demonstrated how easy it is to have full text searching on your site. To add search to a SilverStripe site add a search form, a results page, and you're done!
[Next Tutorial >>](/tutorials/dataobject_relationship_management)

View File

@ -1,495 +0,0 @@
title: DataObject Relationship Management
summary: Learn how to create custom DataObjects and how to build interfaces for managing that data.
<div class="alert" markdown="1">
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>
# Tutorial 5 - Dataobject Relationship Management
## Overview
This tutorial explores the relationship and management of [DataObjects](api:SilverStripe\ORM\DataObject). It builds on the [second tutorial](/tutorials/extending_a_basic_site) where we learnt how to define
additional fields on our models, and attach images to them.
## What are we working towards?
To demonstrate relationships between objects,
we are going to use the example of students working on various projects.
Each student has a single project, and each project has one or more
mentors supervising their progress. We'll create these objects,
make them editable in the CMS, and render them on the website.
This table shows some example data we'll be using:
| Project | Student | Mentor |
| ------- | ------- | ------- |
| Developer Toolbar | Jakob,Ofir | Mark,Sean |
| Behaviour Testing | Michal,Wojtek | Ingo, Sean |
| Content Personalization | Yuki | Philipp |
| Module Management | Andrew | Marcus,Sam |
### Has-One and Has-Many Relationships: Project and Student
A student can have only one project, it'll keep them busy enough.
But each project can be done by one or more students.
This is called a **one-to-many** relationship.
Let's create the `Student` and `Project` objects.
**mysite/code/Student.php**
```php
use SilverStripe\ORM\DataObject;
class Student extends DataObject
{
private static $db = [
'Name' => 'Varchar',
'University' => 'Varchar',
];
private static $has_one = [
'Project' => 'Project'
];
}
```
**mysite/code/Project.php**
```php
use Page;
class Project extends Page
{
private static $has_many = [
'Students' => 'Student'
];
}
```
**mysite/code/ProjectController.php**
```php
use PageController;
class ProjectController extends PageController
{
}
```
The relationships are defined through the `$has_one`
and `$has_many` properties on the objects.
The array keys declares the name of the relationship,
the array values contain the class name
(see the ["datamodel"](/developer_guides/model/data_model_and_orm)
topic for more information).
As you can see, only the `Project` model extends `Page`,
while `Student` is a plain `DataObject` subclass.
This allows us to view projects through the standard
theme setup, just like any other page.
It would be possible to render students separately as well,
but for now we'll assume they're just listed as part of their `Project` page.
Since `Project` inherits all properties (e.g. a title) from its parent class,
we don't need to define any additional ones for our purposes.
Now that we have our models defined in PHP code,
we need to tell the database to create the related tables.
Trigger a rebuild through *dev/build* before you
proceed to the next part of this tutorial.
### Organizing pages: ProjectHolder
A `Project` is just a page, so we could create it anywhere in the CMS.
In order to list and organize them, it makes sense to collect them under a common parent page.
We'll create a new page type called `ProjectsHolder` for this purpose,
which is a common pattern in SilverStripe's page types. Holders
are useful for listing their children, and usually restrict these children to a specific class,
in our case pages of type `Project`.
The restriction is enforced through the `$allowed_children` directive.
**mysite/code/ProjectsHolder.php**
```php
use Page;
class ProjectsHolder extends Page
{
private static $allowed_children = [
'Project'
];
}
```
**mysite/code/ProjectsHolderController.php
```php
use PageController;
class ProjectsHolderController extends PageController
{
}
```
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.
Now that we have created our `ProjectsHolder` and `Project` page types, we'll add some content.
Go into the CMS and create a `ProjectsHolder` page named **Projects**.
Then create one `Project` page for each project listed [above](/tutorials/dataobject_relationship_management#what-are-we-working-towards).
### Data Management Interface: GridField
So we have our models, and can create pages of type
`Project` through the standard CMS interface,
and collect those within a `ProjectsHolder`.
But what about creating `Student` records?
Since students are related to a single project, we will
allow editing them right on the CMS interface in the `Project` page type.
We do this through a powerful field called `[GridField](/reference/grid-field)`.
All customization to fields for a page type are managed through a method called
`getCMSFields()`, so let's add it there:
**mysite/code/Project.php**
```php
use Page;
use SilverStripe\Forms\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
class Project extends Page
{
// ...
public function getCMSFields()
{
// Get the fields from the parent implementation
$fields = parent::getCMSFields();
// Create a default configuration for the new GridField, allowing record editing
$config = GridFieldConfig_RelationEditor::create();
// Set the names and data for our gridfield columns
$config
->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDataColumns')
->setDisplayFields([
'Name' => 'Name',
'Project.Title'=> 'Project' // Retrieve from a has-one relationship
]);
// Create a gridfield to hold the student relationship
$studentsField = new GridField(
'Students', // Field name
'Student', // Field title
$this->Students(), // List of all related students
$config
);
// Create a tab named "Students" and add our field to it
$fields->addFieldToTab('Root.Students', $studentsField);
return $fields;
}
}
```
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.
In our case, we want to manage those records, edit their details, and add new ones.
To accomplish this, we have added a specific [GridFieldConfig](api:SilverStripe\Forms\GridField\GridFieldConfig).
While we could've built the config from scratch, there's several
preconfigured instances. The `GridFieldConfig_RecordEditor` default configures
the field to edit records, rather than just viewing them.
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>
![tutorial:tutorial5_project_creation.jpg](../_images/tutorial5_project_creation.jpg)
Select each `Project` page you have created before,
go in the tab panel called "Students", and add all students as required,
by clicking on the link **Add Student** of your *GridField* table.
![tutorial:tutorial5_addNew.jpg](../_images/tutorial5_addNew.jpg)
Once you have added all the students, and selected their projects, it should look a little like this:
![tutorial:tutorial5_students.jpg](../_images/tutorial5_students.jpg)
### Many-many relationships: Mentor
Now we have a fairly good picture of how students relate to their projects.
But students generally have somebody looking them over the shoulder.
In our case, that's the "mentor". Each project can have many of them,
and each mentor can have one or more projects. They're busy guys!
This is called a *many-many* relationship.
The first step is to create the `Mentor` object and set the relation with the `Project` page type.
**mysite/code/Mentor.php**
```php
use SilverStripe\ORM\DataObject;
class Mentor extends DataObject
{
private static $db = [
'Name' => 'Varchar',
];
private static $belongs_many_many = [
'Projects' => 'Project'
];
}
```
**mysite/code/Project.php**
```php
use Page;
class Project extends Page
{
// ...
private static $many_many = [
'Mentors' => 'Mentor'
];
}
```
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()`,
which will allow you to manage the *many_many* relation.
Again, GridField will come in handy here, we just have
to configure it a bit differently.
**mysite/code/Project.php**
```php
use Page;
use SilverStripe\Forms\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
class Project extends Page
{
// ...
public function getCMSFields()
{
$fields = parent::getCMSFields();
// ...
// Same setup, but for mentors
$mentorsField = new GridField(
'Mentors',
'Mentors',
$this->Mentors(),
GridFieldConfig_RelationEditor::create()
);
$fields->addFieldToTab('Root.Mentors', $mentorsField);
return $fields;
}
}
```
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.
In the CMS, open one of your `Project` pages and select the "Mentors" tab.
Add all the mentors listed [above](#what-are-we-working-towards)
by clicking on the **Add Mentor** button.
![tutorial:tutorial5_module_creation.jpg](../_images/tutorial5_module_creation.jpg)
To associate the mentor with a project, select one of the mentors, and click on the projects tab. Add all the projects a mentor is associated with (see the [list](/tutorials/dataobject_relationship_management#What_are_we_working_towards?)), by typing the name in "Find Projects by Page name" and clicking the "Link Existing" button.
You will notice that you are able to select the same `Project` for multiple mentors.
This is the definition of a **many-to-many** relation.
![tutorial:tutorial5_module_selection.jpg](../_images/tutorial5_module_selection.jpg)
## Website Display
Now that we have created all the *Page* and *DataObject* classes necessary and the relational tables to manage the [relations](/developer_guides/model/data_model_and_orm#relations) between them, we would like to see these relations on the website. We will see in this section how to display all these relations,
but also how to create a template for a *DataObject*.
For every kind of *Page* or *DataObject*, you can access to their relations thanks to the **control** loop.
### Projects Overview Template
We'll start by creating a `ProjectsHolder` template,
which lists all projects, and condenses their
student and mentor relationships into a single line.
You'll notice that there's no difference between
accessing a "has-many" and "many-many" relationship
in the template loops: to the template, it's just
a named list of object.
![tutorial:tutorial5_projects_table.jpg](../_images/tutorial5_projects_table.jpg)
**themes/simple/templates/Layout/ProjectsHolder.ss**
```ss
<% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article>
<h1>$Title</h1>
<div class="content">
$Content
<table>
<thead>
<tr>
<th>Project</th>
<th>Students</th>
<th>Mentors</th>
</tr>
</thead>
<tbody>
<% loop $Children %>
<tr>
<td>
<a href="$Link">$Title</a>
</td>
<td>
<% loop $Students %>
$Name ($University)<% if not $Last %>, <% end_if %>
<% end_loop %>
</td>
<td>
<% loop $Mentors %>
$Name<% if not $Last %>, <% end_if %>
<% end_loop %>
</td>
</tr>
<% end_loop %>
</tbody>
</table>
</div>
</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.
To get a list of all projects, we've looped through the "Children" list,
which is a relationship we didn't define explicitly.
It is provided to us by the parent implementation,
since projects are nothing other than children pages in the standard page hierarchy.
### Project Detail Template
Creating the detail view for each `Project` page works in a very similar way.
Given that we're in the context of a single project,
we can access the "Students" and "Mentors" relationships directly in the template.
![tutorial:tutorial5_project.jpg](../_images/tutorial5_project.jpg)
**themes/simple/templates/Layout/Project.ss**
```ss
<% include SideBar %>
<div class="content-container unit size3of4 lastUnit">
<article>
<h1>$Title</h1>
<div class="content">
$Content
<h2>Students</h2>
<% if $Students %>
<ul>
<% loop $Students %>
<li>$Name ($University)</li>
<% end_loop %>
</ul>
<% else %>
<p>No students found</p>
<% end_if %>
<h2>Mentors</h2>
<% if $Mentors %>
<ul>
<% loop $Mentors %>
<li>$Name</li>
<% end_loop %>
</ul>
<% else %>
<p>No mentors found</p>
<% end_if %>
</div>
</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
You might have noticed that we duplicate the same template code
between both views when it comes to displaying the details
on students and mentors. We'll fix this for students,
by introducing a new template for them.
**themes/simple/templates/Includes/StudentInfo.ss**
```ss
$Name ($University)
```
To use this template, we need to add a new method to our student class:
```php
use SilverStripe\ORM\DataObject;
class Student extends DataObject
{
public 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.
## Summary
This tutorial has demonstrated how you can manage data with
different types of relations between in the CMS,
and how you can display this data on your website.
We illustrated how the powerful `Page` class can be useful to structure
your own content, and how we can correlate it to more
lightweight `DataObject` classes. The transition between
the two classes is intentionally fluent in the CMS, you can
manage them depending on your needs.
`DataObject` gives you a no-frills solution to data storage,
but `Page` allows for built-in WYSIWYG editing, versioning,
publication and hierarchical organization.
## Exercises
This is a simplified example, so there's naturally room for improvement.
In order to challenge your knowledge gained in the tutorials so far,
we suggest some excercises to make the solution more flexible:
* Refactor the `Student` and `Mentor` classes to inherit from a common parent class `Person`,
and avoid any duplication between the two subclasses.
* Render mentor details in their own template
* Change the `GridField` to list only five records per page (the default is 20).
This configuration is stored in the [GridFieldPaginator](api:SilverStripe\Forms\GridField\GridFieldPaginator) component

View File

@ -362,8 +362,6 @@ A page will normally contain some content and potentially a form of some kind. F
SilverStripe log-in form. If you are on such a page, the `$Form` variable will contain the HTML content of the form.
Placing it just below `$Content` is a good default.
You can add your own forms by implementing new form instances (see the [Forms tutorial](/tutorials/forms)).
## Related Lessons
* [Adding dynamic content](https://www.silverstripe.org/learn/lessons/v4/adding-dynamic-content-1)

View File

@ -157,4 +157,3 @@ message to the user interface indicating a successful message.
* [GridField Reference](/developer_guides/forms/field_types/gridfield)
* [ModelAdmin: A UI driven by GridField](/developer_guides/customising_the_admin_interface/modeladmin)
* [Tutorial 5: Dataobject Relationship Management](/tutorials/dataobject_relationship_management)

View File

@ -175,8 +175,6 @@ The change is in **$results = $this->getResults($data);**, because you are using
Another thing you cant forget is to check the name of the singleton you are using in your project. the example uses
**MyDataObject**, you need to change it for the one you are using
For more information on how to paginate your results within the template, see [Tutorial: Site Search](/tutorials/4-site-search).
### The Pagination Template
@ -233,7 +231,6 @@ See [SearchFilter](api:SilverStripe\ORM\Filters\SearchFilter) API Documentation
## Related Documentation
* [ModelAdmin](/developer_guides/customising_the_admin_interface/modeladmin)
* [Site Search](/tutorials/site_search)
## API Documentation

View File

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