[shortcode]
@@ -175,12 +154,12 @@ Bad:
### Location
-Element scoped shortcodes have a special ability to move the location they are inserted at to comply with
-HTML lexical rules. Take for example this basic paragraph tag:
+Element scoped shortcodes have a special ability to move the location they are inserted at to comply with HTML lexical
+rules. Take for example this basic paragraph tag:
Head [figure,src="assets/a.jpg",caption="caption"] Tail
-When converted naively would become
+When converted naively would become:
Head caption Tail
@@ -198,17 +177,17 @@ When the location attribute is "leftAlone" or "center" then the DOM is split aro
### Parameter values
Here is a summary of the callback parameter values based on some example shortcodes.
+
+ :::php
+ public function MyCustomShortCode($arguments, $content = null, $parser = null, $tagName) {
+ // ..
+ }
-Short
-
- [my_shortcodes]
-
- $attributes => array()
- $enclosedContent => null
- $parser => ShortcodeParser instance
- $tagName => 'my_shortcode'
-
-Short with attributes
+ [my_shortcode]
+ $attributes => array();
+ $content => null;
+ $parser => ShortcodeParser instance,
+ $tagName => 'myshortcode')
[my_shortcode,attribute="foo",other="bar"]
@@ -217,8 +196,6 @@ Short with attributes
$parser => ShortcodeParser instance
$tagName => 'my_shortcode'
-Long with attributes
-
[my_shortcode,attribute="foo"]content[/my_shortcode]
$attributes => array('attribute' => 'foo')
@@ -237,6 +214,11 @@ example the below code will not work as expected:
The parser will raise an error if it can not find a matching opening tag for any particular closing tag
-## Related
+## Related Documentation
- * [Wordpress implementation](http://codex.wordpress.org/Shortcode_API)
+ * [Wordpress Implementation](http://codex.wordpress.org/Shortcode_API)
+ * [How to Create a Google Maps Shortcode](how_tos/create_a_google_maps_shortcode)
+
+## API Documentation
+
+ * [api:ShortcodeParser]
diff --git a/docs/en/02_Developer_Guides/05_Extending/05_Injector.md b/docs/en/02_Developer_Guides/05_Extending/05_Injector.md
index 42df40321..d56c1e2a7 100644
--- a/docs/en/02_Developer_Guides/05_Extending/05_Injector.md
+++ b/docs/en/02_Developer_Guides/05_Extending/05_Injector.md
@@ -1,14 +1,13 @@
+title: Injector
+summary: Introduction to using Dependency Injection within SilverStripe.
+
# Injector
-## Introduction
+The [api:Injector] class is the central manager of inter-class dependencies in SilverStripe. It offers developers the
+ability to declare the dependencies a class type has, or to change the nature of the dependencies defined by other
+developers.
-The `[api:Injector]` class is the central manager of inter-class dependencies
-in the SilverStripe Framework. In its simplest form it can be considered as
-a replacement for Object::create and singleton() calls, but also offers
-developers the ability to declare the dependencies a class type has, or
-to change the nature of the dependencies defined by other developers.
-
-Some of the goals of dependency injection are
+Some of the goals of dependency injection are:
* Simplified instantiation of objects
* Providing a uniform way of declaring and managing inter-object dependencies
@@ -17,86 +16,111 @@ Some of the goals of dependency injection are
* Improve testability of code
* Promoting abstraction of logic
-A key concept of the injector is whether the object should be managed as
-
-* A pseudo-singleton, in that only one item will be created for a particular
- identifier (but the same class could be used for multiple identifiers)
-* A prototype, where the same configuration is used, but a new object is
- created each time
-* unmanaged, in which case a new object is created and injected, but no
- information about its state is managed.
-
-These concepts will be discussed further below
-
-## Some simple examples
-
-The following sums up the simplest usage of the injector
-
-Assuming no other configuration is specified
+The following sums up the simplest usage of the `Injector` it creates a new object of type `ClassName` through `create`
:::php
- $object = Injector::inst()->create('ClassName');
+ $object = Injector::inst()->create('MyClassName');
-Creates a new object of type ClassName
+The benefit of constructing objects through this syntax is `ClassName` can be swapped out using the
+[Configuration API](../configuration) by developers.
+
+**mysite/_config/app.yml**
+
+ :::yml
+ Injector:
+ MyClassName:
+ class: MyBetterClassName
+
+Repeated calls to `create()` create a new class each time.
:::php
- $object = Injector::inst()->create('ClassName');
- $object2 = Injector::inst()->create('ClassName');
- $object !== $object2;
+ $object = Injector::inst()->create('MyClassName');
+ $object2 = Injector::inst()->create('MyClassName');
-Repeated calls to create() create a new class each time. To create a singleton
-object instead, use **get()**
+ echo $object !== $object2;
+
+ // returns true;
+
+## Singleton Pattern
+
+The `Injector` API can be used for the singleton pattern through `get()`. Subsequent calls to `get` return the same
+object instance as the first call.
:::php
- // sets up ClassName as a singleton
- $object = Injector::inst()->get('ClassName');
- $object2 = Injector::inst()->get('ClassName');
- $object === $object2;
+ // sets up MyClassName as a singleton
+ $object = Injector::inst()->get('MyClassName');
+ $object2 = Injector::inst()->get('MyClassName');
-The subsequent call returns the SAME object as the first call.
+ echo ($object === $object2);
+
+ // returns true;
+
+## Dependencies
+
+The `Injector` API can be used to define the types of `$dependancies` that an object requires.
:::php
+ 'a string value',
'permissions' => '%$PermissionService',
);
}
+When creating a new instance of `MyController` the dependencies on that class will be met.
+
+ :::php
$object = Injector::inst()->get('MyController');
- // results in
- $object->permissions instanceof PermissionService;
- $object->textProperty == 'a string value';
+ echo ($object->permissions instanceof PermissionService);
+ // returns true;
-In this case, on creation of the MyController object, the injector will
-automatically instantiate the PermissionService object and set it as
-the **permissions** property.
+ echo (is_string($object->textProperty));
+ // returns true;
-## Configuring objects managed by the dependency injector
+The [Configuration YAML](../configuration) does the hard work of configuring those `$dependancies` for us.
-The above declarative style of dependency management would cover a large
-portion of usecases, but more complex dependency structures can be defined
-via configuration files.
+**mysite/_config/app.yml**
+
+ :::yml
+ Injector:
+ PermissionService:
+ class: MyCustomPermissionService
+ MyController
+ properties:
+ textProperty: 'My Text Value'
-Configuration can be specified for two areas of dependency management
+Now the dependencies will be replaced with our configuration.
-* Defining dependency overrides for individual classes
-* Injector managed 'services'
+ :::php
+ $object = Injector::inst()->get('MyController');
+
+ echo ($object->permissions instanceof MyCustomPermissionService);
+ // returns true;
-### Factories
+ echo ($object->textProperty == 'My Text Value');
+ // returns true;
+
+## Factories
Some services require non-trivial construction which means they must be created by a factory class. To do this, create
-a factory class which implements the `[api:SilverStripe\Framework\Injector\Factory]` interface. You can then specify
+a factory class which implements the [api:SilverStripe\Framework\Injector\Factory] interface. You can then specify
the `factory` key in the service definition, and the factory service will be used.
An example using the `MyFactory` service to create instances of the `MyService` service is shown below:
+**mysite/_config/app.yml**
+
:::yml
Injector:
MyService:
@@ -104,8 +128,13 @@ An example using the `MyFactory` service to create instances of the `MyService`
MyFactory:
class: MyFactoryImplementation
+**mysite/code/MyFactoryImplementation.php**
+
:::php
+ get('MyService');
-### Dependency overrides
+## Dependency overrides
-To override the **static $dependency;** declaration for a class, you could
-define the following configuration file (module/_config/MyController.yml)
+To override the `$dependency` declaration for a class, define the following configuration file.
+
+**mysite/_config/app.yml**
- name: MyController
- ---
MyController:
dependencies:
textProperty: a string value
permissions: %$PermissionService
-At runtime, the **dependencies** configuration would be read and used in
-place of that declared on the object.
+## Managed objects
-### Managed objects
+Simple dependencies can be specified by the `$dependencies`, but more complex configurations are possible by specifying
+constructor arguments, or by specifying more complex properties such as lists.
-Simple dependencies can be specified by the **dependencies**, but more complex
-configurations are possible by specifying constructor arguments, or by
-specifying more complex properties such as lists.
-
-These more complex configurations are defined in 'Injector' configuration
-blocks and are read by the injector at runtime
+These more complex configurations are defined in `Injector` configuration blocks and are read by the `Injector` at
+runtime.
Assuming a class structure such as
:::php
+ database = $d;
}
@@ -158,50 +185,45 @@ Assuming a class structure such as
}
}
-and the following configuration
+And the following configuration..
+ :::yml
name: MyController
---
MyController:
dependencies:
- permissions: %$PermissionService
- Injector:
- PermissionService:
- class: RestrictivePermissionService
- properties:
- database: %$MySQLDatabase
- MySQLDatabase
- constructor:
- 0: 'dbusername'
- 1: 'dbpassword'
+ permissions: %$PermissionService
+ Injector:
+ PermissionService:
+ class: RestrictivePermissionService
+ properties:
+ database: %$MySQLDatabase
+ MySQLDatabase
+ constructor:
+ 0: 'dbusername'
+ 1: 'dbpassword'
-calling
+Calling..
:::php
// sets up ClassName as a singleton
$controller = Injector::inst()->get('MyController');
-would
+Would setup the following
-* Create an object of type MyController
+* Create an object of type `MyController`
* Look through the **dependencies** and call get('PermissionService')
-* Load the configuration for PermissionService, and create an object of
- type RestrictivePermissionService
-* Look at the properties to be injected and look for the config for
- MySQLDatabase
-* Create a MySQLDatabase class, passing dbusername and dbpassword as the
- parameters to the constructor
+* Load the configuration for PermissionService, and create an object of type `RestrictivePermissionService`
+* Look at the properties to be injected and look for the config for `MySQLDatabase`
+* Create a MySQLDatabase class, passing dbusername and dbpassword as the parameters to the constructor.
-### Testing with Injector in a sandbox environment
-In situations where injector states must be temporarily overridden, it is possible
-to create nested Injector instances which may be later discarded, reverting the
-application to the original state.
+## Testing with Injector
-This is useful when writing test cases, as certain services may be necessary to
-override for a single method call.
+In situations where injector states must be temporarily overridden, it is possible to create nested Injector instances
+which may be later discarded, reverting the application to the original state. This is done through `nest` and `unnest`.
-For instance, a temporary service can be registered and unregistered as below:
+This is useful when writing test cases, as certain services may be necessary to override for a single method call.
:::php
// Setup default service
@@ -209,24 +231,16 @@ For instance, a temporary service can be registered and unregistered as below:
// Test substitute service temporarily
Injector::nest();
+
Injector::inst()->registerService(new TestingService(), 'ServiceName');
$service = Injector::inst()->get('ServiceName');
// ... do something with $service
+
+ // revert changes
Injector::unnest();
- // ... future requests for 'ServiceName' will return the LiveService instance
+## API Documentation
-### What are Services?
-
-Without diving too deep down the rabbit hole, the term 'Service' is commonly
-used to describe a piece of code that acts as an interface between the
-controller layer and model layer of an MVC architecture. Rather than having
-a controller action directly operate on data objects, a service layer provides
-that logic abstraction, stopping controllers from implementing business logic,
-and keeping that logic packaged in a way that is easily reused from other
-classes.
-
-By default, objects are managed like a singleton, in that there is only one
-object instance used for a named service, and all references to that service
-are returned the same object.
\ No newline at end of file
+* [api:Injector]
+* [api:Factory]
\ No newline at end of file
diff --git a/docs/en/02_Developer_Guides/05_Extending/06_Aspects.md b/docs/en/02_Developer_Guides/05_Extending/06_Aspects.md
index 3913552ba..6870bba6e 100644
--- a/docs/en/02_Developer_Guides/05_Extending/06_Aspects.md
+++ b/docs/en/02_Developer_Guides/05_Extending/06_Aspects.md
@@ -3,187 +3,195 @@ summary: Introduction to using aspect-oriented programming with SilverStripe.
# Aspects
-## Introduction
+Aspect oriented programming is the idea that some logic abstractions can be applied across various type hierarchies
+"after the fact", altering the behavior of the system without altering the code structures that are already in place.
-Aspect oriented programming is the idea that some logic abstractions can be applied across various type hierarchies "after the fact", altering the behaviour of the system without altering the code structures that are already in place.
+> In computing, aspect-oriented programming (AOP) is a programming paradigm which isolates secondary or supporting
+> functions from the main program's business logic. It aims to increase modularity by allowing the separation of
+> cross-cutting concerns, forming a basis for aspect-oriented software development.
-> In computing, aspect-oriented programming (AOP) is a programming paradigm
-> which isolates secondary or supporting functions from the main program's
-> business logic. It aims to increase modularity by allowing the separation of
-> cross-cutting concerns, forming a basis for aspect-oriented software
-> development.
+
+[Wikipedia](http://en.wikipedia.org/wiki/Aspect-oriented_programming) provides a much more in-depth explanation.
+
-[The wiki article](http://en.wikipedia.org/wiki/Aspect-oriented_programming)
-provides a much more in-depth explanation!
+In the context of the SilverStripe [Dependency Injector](injector), Aspects are achieved thanks to PHP's `__call` magic
+method combined with the `Proxy` Design Pattern.
+
+Assume an existing service declaration exists called `MyService`. An `AopProxyService` class instance is created, and
+the existing `MyService` object is bound in as a member variable of the `AopProxyService` class.
+
+Objects are added to the `AopProxyService` instance's "beforeCall" and "afterCall" lists; each of these implements
+either the beforeCall or afterCall method.
+
+When client code declares a `$dependency` on MyService, it is actually passed in the `AopProxyService` instance.
+
+Client code calls a method `MyMethod` that it knows exists on `MyService` - this doesn't exist on `AopProxyService`, so
+__call is triggered.
+
+All classes bound to the `beforeCall` list are executed; if any explicitly returns 'false', `myMethod` is not executed.
+Otherwise, `myMethod` is executed.
+
+All classes bound to the `afterCall` list are executed.
+
+To provide some context, imagine a situation where we want to direct all `write` queries made in the system to a
+specific database server, whereas all read queries can be handled by slave servers.
+
+A simplified implementation might look like the following.
+
+
+This doesn't cover all cases used by SilverStripe so is not a complete solution, more just a guide to how it would be
+used.
+
+
+**mysite/code/MySQLWriteDbAspect.php**
+
+ :::php
+
- * @license BSD License http://www.silverstripe.org/bsd-license
- */
-class MySQLWriteDbAspect implements BeforeCallAspect {
- /**
- *
- * @var MySQLDatabase
- */
- public $writeDb;
-
- public $writeQueries = array('insert','update','delete','replace');
-
- public function beforeCall($proxied, $method, $args, &$alternateReturn) {
- if (isset($args[0])) {
- $sql = $args[0];
- $code = isset($args[1]) ? $args[1] : E_USER_ERROR;
- if (in_array(strtolower(substr($sql,0,strpos($sql,' '))), $this->writeQueries)) {
- $alternateReturn = $this->writeDb->query($sql, $code);
- return false;
+ if (in_array(strtolower(substr($sql,0,strpos($sql,' '))), $this->writeQueries)) {
+ $alternateReturn = $this->writeDb->query($sql, $code);
+ return false;
+ }
}
}
}
-}
-
-
-```
To actually make use of this class, a few different objects need to be configured. First up, define the `writeDb`
-object that's made use of above
+object that's made use of above.
-```
- WriteMySQLDatabase:
- class: MySQLDatabase
- constructor:
- - type: MySQLDatabase
- server: write.hostname.db
- username: user
- password: pass
- database: write_database
-```
+**mysite/_config/app.yml**
-This means that whenever something asks the injector for the `WriteMySQLDatabase` object, it'll receive an object of
-type `MySQLDatabase`, configured to point at the 'write' database
+ :::yml
+ WriteMySQLDatabase:
+ class: MySQLDatabase
+ constructor:
+ - type: MySQLDatabase
+ server: write.hostname.db
+ username: user
+ password: pass
+ database: write_database
-Next, this should be bound into an instance of the aspect class
+This means that whenever something asks the [api:Injector] for the `WriteMySQLDatabase` object, it'll receive an object
+of type `MySQLDatabase`, configured to point at the 'write_database'.
-```
- MySQLWriteDbAspect:
- properties:
- writeDb: %$WriteMySQLDatabase
-```
+Next, this should be bound into an instance of the `Aspect` class
+
+**mysite/_config/app.yml**
+
+ :::yml
+ MySQLWriteDbAspect:
+ properties:
+ writeDb: %$WriteMySQLDatabase
Next, we need to define the database connection that will be used for all non-write queries
-```
- ReadMySQLDatabase:
- class: MySQLDatabase
- constructor:
- - type: MySQLDatabase
- server: slavecluster.hostname.db
- username: user
- password: pass
- database: read_database
-```
+**mysite/_config/app.yml**
+
+ :::yml
+ ReadMySQLDatabase:
+ class: MySQLDatabase
+ constructor:
+ - type: MySQLDatabase
+ server: slavecluster.hostname.db
+ username: user
+ password: pass
+ database: read_database
-The final piece that ties everything together is the AopProxyService instance that will be used as the replacement
-object when the framework creates the database connection in DB.php
+The final piece that ties everything together is the [api:AopProxyService] instance that will be used as the replacement
+object when the framework creates the database connection.
-```
- MySQLDatabase:
- class: AopProxyService
- properties:
- proxied: %$ReadMySQLDatabase
- beforeCall:
- query:
- - %$MySQLWriteDbAspect
-```
+**mysite/_config/app.yml**
+
+ :::yml
+ MySQLDatabase:
+ class: AopProxyService
+ properties:
+ proxied: %$ReadMySQLDatabase
+ beforeCall:
+ query:
+ - %$MySQLWriteDbAspect
-The two important parts here are in the `properties` declared for the object
-
-- **proxied** : This is the 'read' database connectino that all queries should be initially directed through
-- **beforeCall** : A hash of method\_name => array containing objects that are to be evaluated _before_ a call to the defined method\_name
+The two important parts here are in the `properties` declared for the object.
+- **proxied** : This is the 'read' database connection that all queries should be initially directed through.
+- **beforeCall** : A hash of method\_name => array containing objects that are to be evaluated _before_ a call to the
+defined method\_name
Overall configuration for this would look as follows
-```
-
-Injector:
- ReadMySQLDatabase:
- class: MySQLDatabase
- constructor:
- - type: MySQLDatabase
- server: slavecluster.hostname.db
- username: user
- password: pass
- database: read_database
- MySQLWriteDbAspect:
- properties:
- writeDb: %$WriteMySQLDatabase
- WriteMySQLDatabase:
- class: MySQLDatabase
- constructor:
- - type: MySQLDatabase
- server: write.hostname.db
- username: user
- password: pass
- database: write_database
- MySQLDatabase:
- class: AopProxyService
- properties:
- proxied: %$ReadMySQLDatabase
- beforeCall:
- query:
- - %$MySQLWriteDbAspect
-
-```
+**mysite/_config/app.yml**
+
+ :::yml
+ Injector:
+ ReadMySQLDatabase:
+ class: MySQLDatabase
+ constructor:
+ - type: MySQLDatabase
+ server: slavecluster.hostname.db
+ username: user
+ password: pass
+ database: read_database
+ MySQLWriteDbAspect:
+ properties:
+ writeDb: %$WriteMySQLDatabase
+ WriteMySQLDatabase:
+ class: MySQLDatabase
+ constructor:
+ - type: MySQLDatabase
+ server: write.hostname.db
+ username: user
+ password: pass
+ database: write_database
+ MySQLDatabase:
+ class: AopProxyService
+ properties:
+ proxied: %$ReadMySQLDatabase
+ beforeCall:
+ query:
+ - %$MySQLWriteDbAspect
## Changing what a method returns
-One major feature of an aspect is the ability to modify what is returned from the client's call to the proxied method.
-As seen in the above example, the `beforeCall` method modifies the byref `&$alternateReturn` variable, and returns
-`false` after doing so.
-
-```
+One major feature of an `Aspect` is the ability to modify what is returned from the client's call to the proxied method.
+As seen in the above example, the `beforeCall` method modifies the `&$alternateReturn` variable, and returns `false`
+after doing so.
+
+ :::php
$alternateReturn = $this->writeDb->query($sql, $code);
+
return false;
-```
-By returning false from the `beforeCall()` method, the wrapping proxy class will _not_ call any additional `beforeCall`
-handlers defined for the called method. Assigning the $alternateReturn variable also indicates to return that value
+By returning `false` from the `beforeCall()` method, the wrapping proxy class will_not_ call any additional `beforeCall`
+handlers defined for the called method. Assigning the `$alternateReturn` variable also indicates to return that value
to the caller of the method.
-
Similarly the `afterCall()` aspect can be used to manipulate the value to be returned to the calling code. All the
`afterCall()` method needs to do is return a non-null value, and that value will be returned to the original calling
code instead of the actual return value of the called method.
+## API Documentation
+
+* [api:AopProxyService]
+* [api:BeforeCallAspect]
+* [api:AfterCallAspect]
+* [api:Injector]
diff --git a/docs/en/02_Developer_Guides/05_Extending/07_Templates.md b/docs/en/02_Developer_Guides/05_Extending/07_Templates.md
new file mode 100644
index 000000000..cc2945978
--- /dev/null
+++ b/docs/en/02_Developer_Guides/05_Extending/07_Templates.md
@@ -0,0 +1,10 @@
+title: Custom Templates
+summary: Override templates from core and modules in your application
+
+# Custom Templates
+
+See [Template Inheritance](../templates).
+
+## Form Templates
+
+See [Form Templates](../forms/form_templates).
\ No newline at end of file
diff --git a/docs/en/02_Developer_Guides/05_Extending/How_Tos/01_Publish_a_Module.md b/docs/en/02_Developer_Guides/05_Extending/How_Tos/01_Publish_a_Module.md
new file mode 100644
index 000000000..358ea0902
--- /dev/null
+++ b/docs/en/02_Developer_Guides/05_Extending/How_Tos/01_Publish_a_Module.md
@@ -0,0 +1,77 @@
+title: How to Publish a SilverStripe module
+
+# How to Publish a SilverStripe module.
+
+If you wish to submit your module to our public directory, you take responsibility for a certain level of code quality,
+adherence to conventions, writing documentation, and releasing updates.
+
+SilverStripe uses [Composer](../../getting_started/composer/) to manage module releases and dependencies between
+modules. If you plan on releasing your module to the public, ensure that you provide a `composer.json` file in the root
+of your module containing the meta-data about your module.
+
+For more information about what your `composer.json` file should include, consult the
+[Composer Documentation](http://getcomposer.org/doc/01-basic-usage.md).
+
+A basic usage of a module for 3.1 that requires the CMS would look similar to
+this:
+
+**mycustommodule/composer.json**
+ :::js
+ {
+ "name": "your-vendor-name/module-name",
+ "description": "One-liner describing your module",
+ "type": "silverstripe-module",
+ "homepage": "http://github.com/your-vendor-name/module-name",
+ "keywords": ["silverstripe", "some-tag", "some-other-tag"],
+ "license": "BSD-3-Clause",
+ "authors": [
+ {"name": "Your Name","email": "your@email.com"}
+ ],
+ "support": {
+ "issues": "http://github.com/your-vendor-name/module-name/issues"
+ },
+ "require": {
+ "silverstripe/cms": "~3.1",
+ "silverstripe/framework": "~3.1"
+ },
+ "extra": {
+ "installer-name": "module-name",
+ "screenshots": [
+ "relative/path/screenshot1.png",
+ "http://myhost.com/screenshot2.png"
+ ]
+ }
+ }
+
+
+Once your module is published online with a service like Github.com or Bitbucket.com, submit the repository to
+[Packagist](https://packagist.org/) to have the module accessible to developers. It'll automatically get picked
+up by [addons.silverstripe.org](http://addons.silverstripe.org/) website.
+
+## Releasing versions
+
+Over time you may have to release new versions of your module to continue to work with newer versions of SilverStripe.
+By using Composer, this is made easy for developers by allowing them to specify what version they want to use. Each
+version of your module should be a separate branch in your version control and each branch should have a `composer.json`
+file explicitly defining what versions of SilverStripe you support.
+
+Say you have a module which supports SilverStripe 3.0. A new release of this module takes advantage of new features
+in SilverStripe 3.1. In this case, you would create a new branch for the 3.0 compatible code base of your module. This
+allows you to continue fixing bugs on this older release branch.
+
+
+As a convention, the `master` branch of your module should always work with the `master` branch of SilverStripe.
+
+
+Other branches should be created on your module as needed if they're required to support specific SilverStripe releases.
+
+You can have an overlap in supported versions, e.g two branches in your module both support SilverStripe 3.1. In this
+case, you should explain the differences in your `README.md` file.
+
+Here's some common values for your `require` section
+(see [getcomposer.org](http://getcomposer.org/doc/01-basic-usage.md#package-versions) for details):
+
+ * `3.0.*`: Version `3.0`, including `3.0.1`, `3.0.2` etc, excluding `3.1`
+ * `~3.0`: Version `3.0` or higher, including `3.0.1` and `3.1` etc, excluding `4.0`
+ * `~3.0,<3.2`: Version `3.0` or higher, up until `3.2`, which is excluded
+ * `~3.0,>3.0.4`: Version `3.0` or higher, starting with `3.0.4`
\ No newline at end of file
diff --git a/docs/en/02_Developer_Guides/05_Extending/How_Tos/02_Create_a_Google_Maps_Shortcode.md b/docs/en/02_Developer_Guides/05_Extending/How_Tos/02_Create_a_Google_Maps_Shortcode.md
new file mode 100644
index 000000000..0bfd5bafd
--- /dev/null
+++ b/docs/en/02_Developer_Guides/05_Extending/How_Tos/02_Create_a_Google_Maps_Shortcode.md
@@ -0,0 +1,33 @@
+title: How to Create a Google Maps Shortcode
+
+# How to Create a Google Maps Shortcode
+
+To demonstrate how easy it is to build custom shortcodes, we'll build one to display a Google Map based on a provided
+address. We want our CMS authors to be able to embed the map using the following code:
+
+ :::php
+ [googlemap,width=500,height=300]97-99 Courtenay Place, Wellington, New Zealand[/googlemap]
+
+So we've got the address as "content" of our new `googlemap` shortcode tags, plus some `width` and `height` arguments.
+We'll add defaults to those in our shortcode parser so they're optional.
+
+**mysite/_config.php**
+
+ :::php
+ ShortcodeParser::get('default')->register('googlemap', function($arguments, $address, $parser, $shortcode) {
+ $iframeUrl = sprintf(
+ 'http://maps.google.com/maps?q=%s&hnear=%s&ie=UTF8&hq=&t=m&z=14&output=embed',
+ urlencode($address),
+ urlencode($address)
+ );
+
+ $width = (isset($arguments['width']) && $arguments['width']) ? $arguments['width'] : 400;
+ $height = (isset($arguments['height']) && $arguments['height']) ? $arguments['height'] : 300;
+
+ return sprintf(
+ '
',
+ $width,
+ $height,
+ $iframeUrl
+ );
+ });
\ No newline at end of file
diff --git a/docs/en/02_Developer_Guides/05_Extending/index.md b/docs/en/02_Developer_Guides/05_Extending/index.md
index 74bfc712c..6cb8ba481 100644
--- a/docs/en/02_Developer_Guides/05_Extending/index.md
+++ b/docs/en/02_Developer_Guides/05_Extending/index.md
@@ -1,8 +1,17 @@
title: Extending SilverStripe
summary: Understand the ways to modify the built-in functionality through Extensions, Subclassing and Dependency Injection.
+introduction: SilverStripe is easily extensible to meet custom application requirements. This guide covers the wide range of API's to modify built-in functionality and make your own code easily extensible.
-[CHILDREN]
+No two applications are ever going to be the same and SilverStripe is built with this in mind. The core framework
+includes common functionality and default behaviors easily complemented with add-ons such as modules, widgets and
+themes.
-## How-to
+SilverStripe includes a myriad of extension API's such as *Extension Hooks* and support for programming patterns
+such Dependency Injection. Allowing developers to tailor the framework to their needs without modifying the core
+framework.
-[CHILDREN Folder=How_To]
\ No newline at end of file
+[CHILDREN Exclude="How_Tos"]
+
+## How to's
+
+[CHILDREN Folder="How_Tos"]
\ No newline at end of file