apostrophePlugin - 1.0.3

CMS featuring in-context editing, version control, custom slots as Symfony modules

You are currently browsing
the website for symfony 1

Visit the Symfony2 website

« Back to the Plugins Home


Forgot your password?
Create an account



advanced search
Information Readme Releases Changelog Contribute
Show source

Before You Begin

Welcome to the 1.0 stable release of Apostrophe! Although this is our first official stable release, our CMS is already in regular use on a number of production sites. This release reflects the fact that our code has been stable and ready for your professional use for quite some time.

We strongly recommend that you follow the instructions in this document which enable you to check out or copy a complete project from svn. Another good option is to download a tarball of our complete sandbox project from apostrophenow.com. We start our own production client sites using the svnforeigncopy tool described here, so you will get maximum support by following that approach.


This document has four major sections:

  • Installation
  • Editor's guide
  • Designer's guide
  • Developer's guide

These are intended to be read consecutively, except for the installation section which is not appropriate if you are not installing Apostrophe yourself. Stop reading when you know what you need to know!

The installation section is intended for developers and system administrators with some knowledge of PHP and command line tasks.

The editor's guide is suitable for end users who will have administrative responsibilities on the site, such as editing and managing content. No programming skills required.

The designer's guide addresses the needs of front-end developers, discussing how to edit and manage stylesheets, page templates and page layouts.

Finally, the developer's guide explores how to extend Apostrophe with new content slots that go beyond our provided rich text and media features and new "engines" that extend the CMS with full-page content that doesn't always fit neatly into the page metaphor. Most readers will never need to refer to this section.

See the end of this document for information about community and professional support, the Apostrophe community and how to participate in further development.

Guiding Philosophy

Apostrophe is a content management system. Apostrophe is open source, and built upon the great work of other open source projects. That's why our apostrophePlugin is a plugin for the Symfony web application framework.

The philosophy of Apostrophe is that editing should be done "in context" as much as possible, keeping confusing modal interfaces to a minimum and always emphasizing good design principles and an intuitive user experience. When we are forced to choose between ease of use and a rarely used feature, we choose ease of use, or make it possible to discover that feature when you truly need it.

Before we decided to write our own CMS, we used sfSimpleCMSPlugin, and although our system is quite different you can see its influence in Apostrophe. We'd like to acknowledge that.

Who Should Use Apostrophe Today?

Right now Apostrophe is best suited to PHP developers who want to make an intuitive content management system available to their clients. Apostrophe is very easy for your clients to edit, administer and maintain once it is set up. Right now, though, Apostrophe installations does call for some command line skills and a willingness to learn about Symfony. We are working to reduce the learning curve.

Front-end developers who do not yet have PHP and Symfony skills but wish to set up an Apostrophe site by themselves should consider tackling the Symfony tutorial to get up to speed. It's not necessary to complete the entire tutorial, but it helps to have at least a passing familiarity with Symfony.

And of course we at P'unk Avenue are available to develop complete Apostrophe sites for those who see the value in a truly intuitive CMS and do not have the development resources in-house to implement it.

Apostrophe Features

Standard features of Apostrophe include version control for all content slots, locking down pages for authenticated users only, and in-context addition, deletion, reorganization and retitling of pages. When a user is logged in with appropriate privileges intuitive editing tools are added to the usual navigation, neatly extending the metaphors already present rather than requiring a second interface solely for editing purposes.

Apostrophe also introduces "areas," or vertical columns, which users with editing privileges are able to create more than one slot. This makes it easy to interleave text with multimedia and other custom slot types without the need to develop a custom PHP template for every page.

Apostrophe includes support for media management, including a built-in media library that allows you to manage locally stored photos and remotely hosted videos. When media are embedded in pages they are automatically sized to cooperate with the page templates created by the designer.

Rich text editing, of course, is standard equipment. And unlike most systems, Apostrophe intelligently filters content pasted from Word and other programs to ensure there are no design-busting markup conflicts.

Supported Browsers

Editing works 100% in Firefox 2+, Safari 4+, Chrome and Internet Explorer 7+. Editing is expressly not supported in Internet Explorer 6. Of course, browsing the site as a user works just fine in Internet Explorer 6. Although IE 6 cannot support our full editing experience, we recognize the need to support legacy browser use by the general public when visiting the site.

System Requirements

apostrophePlugin requires the following. Note that virtually all of the requirements are included in the asandbox project which you can easily check out from svn, copy from svn to your own repository as described below, or just download as a tarball.

The following must be installed on your system:

  • PHP 5.2.4 or better, with a PDO driver for MySQL (PHP 5.3.x works but is still rather buggy as of this writing; this refers to PHP's bugs, not ours)
  • MySQL (tested), SQLite (tested), or another relational database supported by PDO and Doctrine (a few nonessential features are MySQL-specific)
  • For the media features: GD support in PHP, or the netpbm utilities. netpbm uses much less memory
  • Optional, for previews of PDFs in PDF slots: ghostscript (you must also have netpbm to use this feature)

A few truly excellent hosting companies already have netpbm and ghostscript in place. If you have a Virtual Private Server (and you should, shared hosting is very insecure), you can most likely install netpbm and ghostscript with a few simple commands like sudo apt-get install netpbm and sudo apt-get install ghostscript.

If you are choosing a Linux distribution, we recommend Ubuntu. Ubuntu includes a sufficiently modern version of PHP right out of the box. If you are using Red Hat Enterprise Linux or CentOS, you will need to upgrade PHP to version 5.2.x on your own. This is unfortunate and Red Hat really ought to get a move on and fix it.

PHP Libraries

** All of these are included in the sandbox project, no need to install them. ** We list them here for developers who want to know what we're relying on.

  • Doctrine
  • The Zend framework, for search
  • Symfony 1.3 or 1.4

Symfony Plugins

** All of these are included in the sandbox project, no need to install them. ** We list them here for developers who want to know what we're relying on.

  • sfSyncContentPlugin
  • sfJqueryReloadedPlugin
  • sfDoctrineGuardPlugin
  • sfWebBrowserPlugin
  • sfFeed2Plugin
  • sfDoctrineActAsTaggablePlugin
  • apostrophePlugin

Note that if you checked out or copied our sandbox project from svn, all of the components "provided with asandbox" are provided as svn externals, which means they update automatically when you type svn update. Wherever possible we've pinned these to stable branches, not development versions.

Optional components of interest: apostrophePlugin is compatible with sfShibbolethPlugin (the 1.2 trunk version) and sfDoctrineApplyPlugin.

Mac users can most easily meet the PHP requirements by installing the latest version of MAMP. Note that MAMP's PHP must be your command line version of PHP, not Apple's default install of PHP. To fix that, add this line to the .profile file in your home directory:

export PATH="/Applications/MAMP/Library/bin:/Applications/MAMP/bin/php5/bin:$PATH"

Of course your production server will ultimately need to meet the same requirements with regard to PHP and PDO.

Apple's default version of PHP for Snow Leopard is theoretically capable of working with Apostrophe, but unfortunately Apple chose to ship a very bleeding-edge version and as of this writing they have not updated it to address the many PHP bugs since discovered. As of this writing we recommend the latest in the PHP 5.2.x series, not PHP 5.3.x, due to numerous hard crashes at the PHP level with PHP 5.3.x.

The use of Microsoft Windows as a hosting environment has not yet been fully validated, however our core features do not depend heavily upon Unix-specific functions and utilities.


There are two ways to get started with Apostrophe. You can start with our sandbox project, which we heartily recommend, or add the apostrophePlugin to your existing Symfony project. The latter only makes sense for experienced Symfony developers whose sites are already well underway. I'll describe both approaches.

Ways to Download the Apostrophe Sandbox Project

Download The Tarball

The easiest way is to just download a tarball of the sandbox project. This tarball is updated nightly from the stable branch of our sandbox. This is a great way to get started, but note that you won't be able to get security and stability fixes just by typing svn update this way.

Check It Out From Subversion

Alternatively you can check it out with subversion:

svn co http://svn.apostrophenow.org/sandboxes/asandbox/branches/1.3 asandbox

That will check out the 1.3 stable branch, which is suitable for use with both Symfony 1.3 and Symfony 1.4.

If you prefer to live dangerously and use our most bleeding-edge code, you can check out the trunk instead:

svn co http://svn.apostrophenow.org/sandboxes/asandbox/trunk asandbox

We love it when you do this, because it results in more feedback for us, but you should definitely consider using the stable branch for your client projects.

Our Favorite: Copying the Sandbox to Your Own Repository

Checking out the sandbox from svn is a nice way to get started, but you'll soon wonder how to save your own changes when the project is part of our own svn repository. That's why we recommend that you instead copy it to your own repository with svnforeigncopy each time you want to start a new site. That's what we do. With svnforeigncopy you get a copy of the asandbox project in your own svn repository, with the svn:ignore and svn:externals properties completely intact. You don't get the project history, but since 99% of the code is in the externally referenced plugins and libraries, that's really not a big deal. The important thing is that you are still connected to the latest bug-fix updates for our plugin.

This will give you all of the necessary plugins and the ability to svn update the whole shebang with one command.

Apache Configuration

Once you have fetched the code by your means of choice, you're ready to configure your testing web server to recognize a new website. Light up the folder asandbox/web as a virtualhost named asandbox via MAMP, httpd.conf or whatever your web hosting environment requires. As with any Symfony project you'll want to allow full Apache overrides in this folder. See config/vhost.sample for tips on virtual host configuration for Apache. Make sure the directives in web/.htaccess will be honored by your server.

(Confused about this MAMP stuff? Wondering how you can test websites on your own computer? You really need to pick up MAMP if you are on a Mac. Windows and Linux users should consider using XAMPP. These are all-in-one packages with PHP, MySQL, Apache and everything else you need to test real websites on your own computer.)

Symfony Configuration

Yes, this is a preconfigured sandbox project, but you do have to adjust a few things to reflect reality on your own computer.

Now create the config/databases.yml file, which must contain database settings appropriate to your system. Copy the file config/databases.yml.sample as a starting point:

cp config/databases.yml.sample config/databases.yml

If you are testing with MAMP the default settings (username root, password root, database name asandbox) may work just fine for you. If you are testing on a staging server you will need to change these credentials.

Also create your properties.ini file:

cp config/properties.ini.sample config/properties.ini

In Symfony properties.ini contains information about hosts that a project can be synced to, in addition to the name of the project. The sample properties.ini file just defines the name of the project. You'll add information there when and if you choose to sync the project to a production server via project:deploy or our enhanced version, apostrophe:deploy. See the Symfony documentation for more information about that technique.

At this point you're ready to use the checkout of Symfony's 1.4.x stable branch that is included in the project. If you want to use a different installation of Symfony, such as a shared install for many sites (note that only 1.3.x and 1.4.x are likely to work), copy config/require-core.php.example to config/require-core.php and edit the paths in that file.

Next, cd to the asandbox folder and run these commands:

./symfony cc
./symfony plugin:publish-assets
./symfony doctrine:build --all
./symfony doctrine:data-load

This will create a sample database from the fixtures files.

If you prefer, you can pull down our full demo site as an alternative to the somewhat bland fixtures site. Replace the doctrine:data-load command with this one (it's OK to do this if you already did the other command):

./symfony apostrophe:demo-fixtures

Note that this task will run for quite a while as media files are included in the download.

Now set the permissions of data folders so that they are writable by the web server. Note that svn does NOT store permissions so you can NOT assume they are already correct:

./symfony project:permissions

Our apostrophePlugin extends project:permissions for you to include the data/writable folder in addition to the standard web/uploads, cache and log folders. Handy, isn't it?

If you prefer you can do this manually:

chmod -R 777 data/a_writable
chmod -R 777 web/uploads
chmod -R 777 cache
chmod -R 777 log

More subtle permissions are possible. However be aware that most "shared hosting" environments are inherently insecure for a variety of reasons. Production Symfony sites should run on a virtual machine of their own, or share a VM only with other sites written by you. So before criticizing the "777 approach," be sure to read this article on shared hosting and Symfony.

Next, build the site's search index for the first time (yes, search is included). It doesn't live in the database so it needs to be done separately. After this, you won't need to run this command again unless you are deploying to a new environment such as a staging or production server and don't plan to sync your content with sfSyncContentPlugin:

./symfony apostrophe:rebuild-search-index --env=dev

(You can specify staging or prod instead to build the search indexes for environments by those names. You'll want that later when working on a production server.)

You can now log in as admin with the password demo to see how the site behaves when you're logged in (if you used the apostrophe:demo-fixtures task, the password will be demo). Start adding subpages, editing slots, adding slots to the multiple-slot content area... have a ball with it!

The Hard Way: Adding apostrophePlugin To An Existing Site

Those who installed the sandbox just now can skip right over this section.

Installing the sandbox is the easy way to install Apostrophe. The notes that follow assume you're doing it the hard way, without starting from the asandbox project.

Begin by installing the following Symfony plugins into your Symfony 1.3/1.4 project:

  • sfJqueryReloadedPlugin
  • sfDoctrineGuardPlugin
  • sfDoctrineActAsTaggablePlugin
  • sfWebBrowserPlugin
  • sfFeed2Plugin
  • sfSyncContentPlugin (recommended but not required)
  • And of course, apostrophePlugin

We strongly encourage you to do so using svn externals. If you are using that approach you will need to be sure to create the necessary symbolic links from your projects web/ folder to to the web/ folders of the plugins that have one. For best results use a relative path:

cd web
ln -s ../plugins/apostrophePlugin/web apostrophePlugin
# Similar for other plugins required

The search features of the plugin rely on Zend Search, so you must also install the Zend framework. The latest version of the minimal Zend framework is sufficient. If you choose to install this system-wide where all PHP code can easily find it with a require statement, great. If you prefer to install it in your Symfony project's lib/vendor folder, you'll need to modify your ProjectConfiguration class to ensure that require statements can easily find files there:

class ProjectConfiguration extends sfProjectConfiguration
  public function setup()
    // We do this here because we chose to put Zend in lib/vendor/Zend.
    // If it is installed system-wide then this isn't necessary to
    // enable Zend Search
      sfConfig::get('sf_lib_dir') .
        '/vendor' . PATH_SEPARATOR . get_include_path());
    // for compatibility / remove and enable only the plugins you want

Create an application in your project. Then create a module folder named a as a home for your page templates and layouts (and possibly other customizations):

mkdir -p apps/frontend/modules/a/templates

The CMS provides convenient login and logout links. By default these are mapped to sfGuardAuth's signin and signout actions. If you are using sfShibbolethPlugin to extend sfDoctrineGuardPlugin, you'll want to change these actions in apps/frontend/config/app.yml:

    domain: duke.edu
    actions_logout: "sfShibbolethAuth/logout"
    actions_login: "sfShibbolethAuth/login"

You can also log in by going directly to /login. If you don't want to display the login link (for instance, because your site is edited only you), just shut that feature off:

    login_link: false

You will also need to enable the a modules in your application's settings.yml file. Of course you may need other modules as well based on your application's needs:

  - a
  - aSync
  - aNavigation
  - aMedia
  - aMediaBackend
  - aRichTextSlot
  - aTextSlot
  - aRawHTMLSlot
  - aSlideshowSlot
  - aVideoSlot
  - aImageSlot
  - aButtonSlot
  - aPDFSlot
  - aFeedSlot
  - sfGuardAuth
  - aUserAdmin
  - aGroupAdmin
  - aPermissionAdmin
  - sfGuardPermission
  - taggableComplete
  - aNavigation
  - default
  - aAdmin

Apostrophe edits rich text content via the FCK editor. A recent version of FCK is included with the plugin. However you'll need to enable FCK in your settings.yml file, as follows:

  rich_text_fck_js_dir:   apostrophePlugin/js/fckeditor

Now, load the fixtures for a basic site. Every site begins with a home page with all other pages being added as descendants of the home page:

./symfony doctrine:build --all
./symfony doctrine:data-load

.htaccess Rules For Media

The media module of Apostrophe uses carefully designed URLs to allow images to be served as static files after they are generated for the first time. This is done at the Apache level to maximize performance: PHP (and therefore Symfony) don't have to get involved at all after the first time an image is rendered at a particular size.

The following special .htaccess rules are required to enable this. These should be copied to your .htaccess file after the RewriteBase / rule, if you are using one, but before any other rules.

###### BEGIN special handling for the media module's cached scaled images
# If it exists, just deliver it
RewriteCond %{REQUEST_URI} ^/uploads/media_items/.+$
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule .* - [L]
# If it doesn't exist, render it via the front end controller
RewriteCond %{REQUEST_URI} ^/uploads/media_items/.+$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
###### END special handling for the media module's cached scaled images

Routing Rules

By default Apostrophe will map CMS pages to URLs beginning with /cms:


And leave all other URLs alone. This is appropriate if the CMS is a minor part of your site. If the CMS is the main purpose of your site, shut off the automatic registration of the route above:

  routes_register: false

And register these as the LAST rules in your application's routing.yml file instead:

# A default rule that gets us to actions outside of the CMS.
# Note that you can't have CMS pages with a slug beginning with /admin
# on a site that uses this particular rule. You could use a 
# different prefix. Or you could provide rules for every
# module, or even for every action. This is just the simplest way.

  url:   /admin/:module/:action/*

# A homepage rule is expected by a and various other plugins,
# so be sure to have one

  url:  /
  param: { module: a, action: show, slug: / }

# Put any routing rules for other modules and actions HERE,
# before the catch-all rule that routes URLs to the
# CMS by default.

# Must be the last rule

  url:   /:slug
  param: { module: a, action: show }
  requirements: { slug: .* }

Thanks to Stephen Ostrow for his help with these rules.

Enhanced Form Controls

To get the benefit of the progressively enhanced form controls featured in our admin tools, you'll need to add apostrophePlugin's aControls.js to your view.yml file:

  ... Other things ...
  javascripts:    [/apostrophePlugin/js/aControls.js]

We're done installing Apostrophe! On to the editor's guide, which briefly explains how to edit content with Apostrophe- briefly because it is so much more fun to simply use it.

Editor's Guide

This section is devoted to users who will participate in editing, maintaining and administering the content of the site. All of these tasks are performed through your web browser. No command line skills are required.

Access your new Apostrophe site's URL to see the home page.

Click "log in" and log in as the admin user (username admin, password demo) to see the editing controls. Notice that editing controls are added to the normal experience of the site. This greatly reduces the learning curve for editors.

Managing Pages

When a user has appropriate privileges on a page, they are able to make the following changes via a simple "breadcrumb trail" that appears at the top of the page:

  • Rename the page by clicking on the page title
  • Add a child page beneath the current page
  • Open the page management settings dialog via the "gear" icon for less frequent changes

Apostrophe emphasizes "unpublishing" pages as the preferred way of "almost" deleting them because it is not permanent. Anonymous users, and users who do not have editing privileges on the page, will see the usual 404 Not Found error. But users with suitable editing privileges will see the page with its title "struck through" and will be able to undelete the page if they desire. This prevents the loss of content.

You can also delete a page permanently via the small X in the lower right corner of the page settings dialog. Most of the time that's a shortsighted thing to do, but it is useful when you create an unnecessary page by accident.

The side navigation column also offers an editing tool: users with editing privileges can change the order of child pages listed there by dragging and dropping them. (If a page has no children, the side navigation displays its peers instead, including itself.) You can do the same thing with the tabs at the top of the page. Also check out the "Reorganize" button, which is discussed in more detail later.

Editing Slots

What about the actual content of the page? The editable content of a page is stored in "slots" (note to developers: not the same thing as Symfony slots).

CMS slots can be of several types:

  • Plaintext slots (single line or multiline)
  • Rich text slots (edited via FCK)
  • Feed slots (RSS or Atom feeds, such as Twitter feeds, inserted into a page)
  • Raw HTML slots (best avoided in good designs, but useful when you must paste raw embed codes)
  • Media slots (image, slideshow, video, PDF, button)
  • Custom slots (of any type, implemented as described in the developer's guide section)

Once you have logged in, you'll note that each editable slot has an "Edit" button or, in the case of the media slots, "Select Image" and similar buttons.

Every slot also offers version control. The arrow-in-a-circle icon accesses a dropdown list of all changes that have been made to that slot, labeled by date, time and author and including a short summary of the change to help you remember what's different about that version. Pick any version to preview it. Click "Save as current version" to permanently revert to that version.

Editing Areas

In addition to single slots, apostrophePlugin also supports "areas." Areas are vertical columns containing more than one slot. Editing users are able to add and remove slots from an area at any time, selecting from a list of slots approved for use in that area. The slots can also be reordered via up and down arrow buttons (used here instead of drag and drop to avoid possible browser bugs when dragging and dropping complex HTML, and because drag and drop is not actually much fun to use when a column spans multiple pages).

The usefulness of areas becomes clear when rich text slots are interleaved with media slots. Media slots provide a robust way to manage photos and videos without wrecking the layout of the site. You can upload photos and select and embed videos freely without worrying about their size and format.

Revising History

Did you make a mistake? Not a problem! Both slots and areas allow you to roll back to any previous edit. Just click "History," preview versions by clicking on them, and click "Save As Current Revision" when you find the version you want.

Editing Media

Click "Add Slot," then "Image," then "Select Image." You will be taken to the media area of the site.

Here you can select any of the images that have already been uploaded to the site, or upload new images. This media repository provides a clean way to keep track of the media you have available.

You can do much the same thing with video. YouTube is tightly integrated with Apostrophe, and you can easily search YouTube from within the media interface. You can also paste embed codes from other sites and add thumbnail images for those videos manually.

You can organize media with categories and tags. Add new categories using the "Manage Categories" button at the left, in the media browser area.

It's possible to add a media page to the public-facing part of the site that displays only media in certain categories. To do that, go to the home page, add a new page and give it a title that relates to a media category. When the new page appears, click the gear icon. When the page settings dialog appears, select "Media" from the "Page Engine" menu.

Once you select Media, a category selector will appear. Pick the appropriate category, then click Save. Note that you can select more than one category.

The page will now refresh and display a media browser that anyone on your site can use to see media in that category, most recent first, with all of the browsing features that are standard in the media area.

Reorganizing the Site

Apostrophe offers drag-and-drop reordering of the children of any page via the navigation links on the left-hand side. You can also reorder the tabs at the top of any page by dragging and dropping.

However, there are times when you want to do something less linear, like moving a page up or down in the page tree.

To do that, click on the "Reorganize" button at the top of any page. This will take you to the reorganize tool, a page where you can drag and drop pages to any position in the page tree for quick and painless reorganization of the site.

This tool is only available to site administrators such as the admin user.

Designer's Guide

This section explains how to go about customizing the appearance and behavior of the site without writing new code (apart from simple calls to insert slots and otherwise use PHP as a templating language). It is intended to be read by front end designers (also known as front end developers).

Some familiarity with Symfony is expected. You will need to edit files like apps/frontend/config/app.yml to adjust settings, and you will be creating .php files although full PHP programming skills are not required.

Title Prefix

By default, the title element of each page will contain the title of that page. In many cases you'll wish to specify a prefix for the title as well.

You can do so by setting app_a_title_prefix in app.yml. This option supports optional internationalization:

    # You can do it this way...
      en: 'Our Company : '
      fr: 'French Prefix : '
    # OR this way for a single-culture site
    title_prefix: 'Our Company'      

Note that all changes to app.yml should be followed by a symfony cc command:

./symfony cc

Creating and Managing Page Templates and Layouts

Where do slots appear in a page? And how do you insert them?

Slots can be inserted in two places: in your site's layout.php file, which decorates all pages, and in page template files, which can be assigned to individual pages.

How to Customize the Layout

By default, the CMS will use the layout.php file bundled with it. If you wish, you can turn this off via app.yml:

    use_bundled_layout: false

CMS pages will then use your application's default layout. One strategy is to copy our layout.php to your application's template folder and customize it there after turning off use_bundled_layout.

How to Customize the Page Templates

The layout is a good place for global elements that should appear on every page. But elements specific to certain types of pages are better kept in page templates. These are standard Symfony template files with a special naming convention.

Page template files live in the templates folder of the a module. We provide these templates "out of the box:"

  • homeTemplate.php
  • defaultTemplate.php

homeTemplate.php is used by our default home page, and defaultTemplate.php is the default template if no other template is chosen.

You can change the template used by a page by using the template dropdown in the breadcrumb trail. This does not delete the slots used by the previous template, so you can switch back without losing your work.

How do you create your own template files? Don't alter the templates folder of the plugin. As always with Symfony modules, you chould instead create your own a/templates folder within your application's modules folder:

mkdir -p apps/frontend/modules/a/templates 

Now you can copy homeTemplate.php and defaultTemplate.php to this folder, or just start over from scratch. You can also copy _login.php if you don't like the way we present the login and logout options. The same applies to _tabs.php and _subnav.php. We do not recommend altering the rest of the templates unless you have a clear understanding of their purpose and function and are willing to make ongoing changes when new releases are made. In general, if you can use CSS to match the behavior of our HTML to your needs, that will be more forwards-compatible with new releases of the CMS.

If you add additional template files, you'll need to adjust the app_a_templates setting in app.yml so that your new templates also appear in the dropdown menu:

        Home Page
        Default Page
        My Template

Custom Navigation in Templates and Layouts

Apostrophe supports three types of navigation components. These can be found in the aNavigation module. Including these navigation elements is as easy as including a component in your template or in layout.php. The three types of navigation elements are:

  • An accordion tree
  • A tabbed or vertical menu of links to child pages
  • A breadcrumb trail from the home page to the current page.

The navigation element, once used, will no longer require any additional SQL queries no matter how many similar navigation elements are included in a template or layout.

Accordion Navigation

Accordion-style navigation can be easily included in your template by doing the following. Accordion navigation is a compromise between showing the entire page tree and showing just the breadcrumb. Accordion navigation includes everything that would be included in a breadcrumb trail, plus the peers of all of those pages. This makes it easy to navigate to related pages at any level without being overwhelmed by a comprehensive list of all pages on the site. These links are rendered as nested ul elements.

<?php include_component('aNavigation', 'accordion', array('root' => '/', 'active' => $page->slug, 'name' => 'normal')) ?>


  • root (required) - This is the slug of the root page that the menu should start from. Often the home page
  • active (required) - The page to build the navigation down to, will also recieve a css class of current
  • name (required) - The unique name of the navigation element for your template. This influences CSS IDs and can be used for styling

Tabbed Navigation

The tabbed navigation provides a navigation element that displays all of the children of a page. Note that depending on your CSS you can render it as a vertical menu. For an example, see the "subnav" area of the asandbox project; see `a/templates/_subnav.php).

<?php include_component('aNavigation', 'tabs', array('root' => '/', 'active' => $page->slug, 'name' => 'tabs')) ?>


  • root (required) - This is the parent slug of the tabs that are displayed.
  • active (required) - This is the page that if in the navigation will recieve a current css class of current
  • name (required) - the unique name of the navigation element for your template.


<?php include_component('aNavigation', 'breadcrumb', array('root' => '/', 'active' => $page->slug, 'name' => 'bread')) ?>


  • root (required) - This is the root slug of the page the breadcrumb should begin with. Usually the home page
  • active (required) - This is the last element that the navigation should descend to. Usually the current page
  • separator (optional) - The separator to use between items, defaults to " > "
  • name (required) - the unique name of the navigation element for your template.

Inserting Slots in Layouts and Templates

Of course, creating layouts and templates does you little good if you can't insert user-edited content into them. This is where the CMS slot helpers come in.

Here's how to insert a slot into a layout or page template:

<?php # Once at the top of the file ?>
<?php use_helper('a') ?>

<?php # Anywhere you want a particular slot ?>
<?php a_slot('body', 'aRichText') ?>

Notice that two arguments are passed to the a_slot helper. The first argument is the name of the slot, which distinguishes it from other slots on the same page. Slot names should contain only characters that are allowed in HTML ID and NAME attributes. We recommend that you use only letters, digits, underscores and dashes in slot names. The slot name will never be seen by the user. It is a useful label such as body or sidebar or subtitle.

The second argument is the type of the slot. "Out of the box," apostrophePlugin offers a useful array of slot types:

  • aText (plaintext)
  • aRichText (allows WYSIWYG formatting)
  • aFeed (brings any RSS or Atom feed into the page)
  • aImage (still images from the media repository)
  • aSlideshow (a series of images from the media repository)
  • aButton (an image from the media repository, and an editor-configurable link)
  • aVideo (video from YouTube and other providers)
  • aPDF (PDF documents from the media repository)
  • aRawHTML (Unfiltered HTML code)

The use of these slots is largely self-explanatory and we encourage you to play with the demo and try them out.

You can add additional slot types of your own and release and distribute them as plugins as explained in the developers' section in this document.

aText Slots

aText slots contain only plaintext (actually, valid HTML text, which can include valid HTML and UTF-8 entities). URLs are automatically rendered as links, email addresses are rendered as obfuscated mailto: links, and newlines are rendered as line breaks.

Note that the special slot name title is reserved for the title of the page and is always of the type aText. While you don't really need to provide an additional editing interface for the title, you might also want to insert it as an h1 somewhere in your page layout or template as a design element:

    <?php a_slot('title', 'aText') ?>

The behavior of most slot types can be influenced by passing options to them from the template or layout. You do this by passing an array of options as a third argument to the helper, like this:

    <?php a_slot('aboutus', 'aText', 'multiline' => true) ?>

The multiline option specifies that a plaintext slot should permit multiple-line text input.

aRichText Slots

Here is a simple example of a rich text slot:

<?php a_slot('ourproducts', 'aRichText') ?>

The rich text slot allows users to edit content using a rich text editor. The HTML they enter is filtered for correctness and to ensure it does not damage your design. Read on for more information about ways to adjust these filters.

The tool option is one of the most useful options for use with rich text slots:

    <?php a_slot('subtitle', 'aRichText',
      array('tool' => 'basic')) ?>

Here we create a subtitle rich text slot on the page which is editable, but only with the limited palette of options provided in FCK's basic toolbar (bold, italic and links).

Note that you can create your own custom toolbars for the FCK rich text editor. Add these to web/js/fckextraconfig.js and they will be found automatically. Here is an example:

FCKConfig.ToolbarSets["Sidebar"] = [
] ;

For more complete examples of what can be included here, see apostrophePlugin/web/js/fckeditor/fckconfig.js. (You do not need to, and should not, duplicate this entire file in fckextraconfig.js. Just add and override things as needed.)

Other notable options to aRichText slots include:

  • allowed-tags is a list of HTML elements to be permitted inside the rich text. By default Apostrophe filters out most HTML elements to prevent pasted content from Microsoft Word and the like from wrecking page layouts. You can pass an array of HTML element names (without angle brackets), or a string like that accepted by the PHP strip_tags function.

This allows us to write a better version of the subtitle slot above that filters the HTML to make sure it's suitable to appear inside an h2 element:

    <?php a_slot('subtitle', 'aRichText',
      array('tool' => 'basic', 'allowed-tags' => '<b><i><strong><em><a>')) ?>

Note that we list both the b tag and the strong tag here. We do this because different browsers submit slightly different rich text.

The default list of allowed tags is:

h3, h4, h5, h6, blockquote, p, a, ul, ol, nl, li, b, i, strong, em, strike, code, hr, br, div, table, thead, caption, tbody, tr, th, td, pre

  • allowed-attributes is a list of HTML attributes to be accepted, on a per-element basis. Unlike strip_tags Apostrophe's robust, high-performance HTML filter, aHtml::simplify, removes all inappropriate HTML attributes. Here is the default list of allowed attributes:

    array( "a" => array("href", "name", "target"), "img" => array("src") );

  • allowed-styles is a list of CSS style names to be permitted, again on a per-element basis. By default, no styles are permitted in rich text editor content. You can alter this to allow more creative table styling, at the cost that a truly persistent user might manage to wreck the page layout. The format is the same as that used above for allowed attributes.

aFeed Slots

The aFeed slot allows editors to insert an RSS feed into the page:

    <?php a_slot('subtitle', 'aFeed') ?>

When the user clicks "Edit," they are invited to paste an RSS feed URL. When they click "Save," the five most recent posts in that feed appear. The title of each feed post links to the original article on the site of origin as specified by the feed.

You can change this behavior with the following options. The defaults are shown as examples:

  • 'links' => true determines whether links to the original posts are provided.
  • 'dateFormat' => 'Y m d' lets you specify your own date and time format (see the PHP date() function). 'Y m d' is not actually our default, as our default is more subtle than can be output with the date function alone (we include the year only when necessary and so on). Our default is very nice, but very American, so feel free to override it.
  • 'markup' => '<strong><em><p><br><ul><li>' changes the list of HTML elements we let through in markup. We apply our usual aHtml::simplify() method, which is smarter than PHP's strip_tags alone.
  • 'interval' => 300 specifies how long we hold on to a feed before we fetch it again. This is important to avoid slowing down your site or getting banned by feed hosts. We rock Symfony's caching classes to implement this. Check out aFeed::getCachedFeed if you're into that sort of thing.
  • 'posts' => 5 determines how many posts are shown.

aImage Slots

Image slots are used to insert single still images from Apostrophe's built-in media repository:

<?php a_slot('landscape', 'aImage') ?>

Options are available to control many aspects of image slots:

  • 'width' => 440 determines the width of the image, in pixels. Images are never rendered larger than actual size as upsampling always looks awful.
  • 'height' => 330 determines the height of the image, in pixels. It is ignored if `flexHeight' => true is also present.
  • 'resizeType' => 's' determines the scaling and cropping style. The s style scales the image down if needed but never changes the aspect ratio, so the image is surrounded with white bars if necessary (see flexHeight for a way to avoid this). The c style crops the largest part of the center of the image that matches the requested aspect ratio.
  • 'flexHeight' => false determines whether the height of the image is scaled along with the requested width to maintain the aspect ratio of the original. Set this option to true to scale the height.
  • 'defaultImage' => false allows you to specify the URL of an image to be displayed if no image has been selected by the editor yet. Otherwise no image is displayed until an editor chooses one.
  • 'title' => false specifies whether the title associated with the image in the media repository should be shown on the page. CSS can be used to display this title in a variety of interesting ways.
  • 'description' => false specifies whether the rich text description associated with the image in the media repository should be shown on the page.
  • 'link' => false specifies a URL that the image should link to when clicked upon. Note that in most cases you probably want to use an aButton slot for this sort of thing.

These options suffice for most purposes. There is one more option, constraints, which takes an array of constraints that can be used to limit which images the user can select from the media repository. By default no constraints are applied. The possible constraints are:

  • 'aspect-width' and 'aspect-height' specify an aspect ratio. The media browser will show only media that match this aspect ratio. Both must be specified. For instance:

    'constraints' => array('aspect-width' => 4, 'aspect-height' => 3)

  • 'minimum-width' and 'minimum-height' specify minimum width and height in pixels. Only media meeting these minimum criteria will be shown. You are not required to use both options although it usually makes sense to specify both.

  • 'width' and 'height' specify that only images of that exact width and/or height should be selectable for this image slot. It is not mandatory to specify both, and if you are using flexHeight you might not want to specify a height constraint.

Here are three examples:

This slot will render the image 400 pixels wide. The original must be at least 400 pixels wide. The height will scale along with the width.

<?php a_slot('landscape', 'aImage', array('width' => 400, 'flexHeight' => true, 'constraints' => array('minimum-width' => 400))) ?>

This slot will be render images at 640x480 pixels. The original must be at least 640 pixels wide. The aspect ratio must be 4x3.

<?php a_slot('landscape', 'aImage', array('width' => 640, 'height' => 480, 'constraints' => array('minimum-width' => 640, 'aspect-width' => 4, 'aspect-height' => 3))) ?>

This slot will crop the largest portion of the center of the selected image with a 400x200 aspect ratio and scale it to 400x200 pixels. The selected image must be at least 400 pixels wide.

<?php a_slot('landscape', 'aImage', array('width' => 400, 'height' => 200, 'resizeType' => 'c', 'constraints' => array('minimum-width' => 400))) ?>

aSlideshow Slots

Slideshow slots allow editors to select one or more images and display them as a slideshow. The exact behavior of the slideshow can be controlled by various options.

Here is a simple example:

<?php a_slot('travel', 'aSlideshow') ?>

By default users can select any images they wish and add them to the slideshow, and the slideshow will advance when they click on an image. Left and right arrows to move back and forward in the slideshow appear above the image unless turned off by the arrows option.

Slideshows with images of varying dimensions can be confusing to look at unless the aspect ratio is the same. Fortunately slideshows support all of the sizing, cropping and constraint options that are available for the aImage slot (see above). We recommend either using resizeType => 'c' to crop to a consistent size (and therefore aspect ratio) or using the aspect-width and aspect-height parameters to the constraints option to ensure a consistent aspect ratio.

In addition to all of the options supported by the aImage slot (see above), slideshow slots support the following additional options:

  • 'random' => false determines whether the slideshow is shown in the order selected by the editor or in a random order. Set this option to true for a random order.

  • 'arrows' => true indicates that arrows to move forward and backward in the slideshow should appear above the image. By default they do appear. You can turn this off by passing false.

  • 'interval' => false sets the interval for automatic advance to the next image in the slideshow. By default the slideshow does not advance automatically. For automatic advance every ten seconds pass 'interval' => 10. Slideshows automatically repeat once they have advanced to the final image. If the user clicks on the slideshow, auto-advance stops, and the user can navigate manually by clicking to advance or using the arrows if present.

  • 'credit' => false determines whether the credit field from the media repository is shown with each image.

aButton Slots

Button slots are similar to image slots. However, they also allow the editor to specify a target URL and a plaintext title. When they click on the button, they are taken to the URL.

Here is an example:

<?php a_slot('logo', 'aButton') ?>

Button slots support all of the options supported by the aImage slot (see above). In particular you might want to specify a default image.

If the title and/or description options are turned on, they are rendered as links to the button's destination URL.

aVideo Slots

The aVideo slot allows video to be embedded in a page.

Apostrophe's media repository does not store video directly. Instead Apostrophe manages videos hosted on external services such as YouTube, Viddler and Vimeo. YouTube is tightly integrated into Apostrophe, so editors can search YouTube directly from the repository in order to easily add videos. Embed codes for other video services can also be added to the media repository. Either way, Apostrophe wrangles the necessary embed codes and ensures that they render at the desired size.

Here is an example:

<?php a_slot('screencast', 'aVideo') ?>

** The video slot supports all of the options supported by the image slot** (described above), with the following differences and exceptions:

  • The width option defaults to 320 pixels.
  • The height option defaults to 240 pixels.
  • The resizeType option is ignored. As a general rule video hosting services do not permit cropping and choose their own approaches to letterboxing. We recommend using the flexHeight option if there is any doubt about the dimensions of the video.
  • The constraints option is supported. The constraints will be applied based on the dimensions of the original video (if from YouTube). For videos from other hosting services (pasted as embed codes in the media repository) constraints are based on the dimensions of the thumbnail manually uploaded by the editor.
  • The link and defaultImage options are not supported.

aPDF Slots

The aPDF slot allows PDF documents to be embedded in a page. If ghostscript and netpbm are installed on the server, thumbnail previews of the first page are automatically displayed in PDF slots, and clicking on them launches the PDF. If they are not available, a clickable PDF icon is still displayed as a way of launching the PDF.

aPDF slots support the following options, which behave as they do for the aImage slot (described above):

  • The width option defaults to 170 pixels.
  • The height option defaults to 220 pixels.
  • The flexHeight option is available to scale the thumbnail based on the page size, although this is only relevant if netpbm and ghostscript are available.
  • The defaultImage option is available. The default image appears when no PDF has been selected yet.
  • The constraints option is available, and can be used to select only PDFs with a certain aspect ratio, such as 8.5x11 (use the aspect-width and aspect-height parameters). This option should not be used when netpbm and ghostscript are not available as the true size of the PDF is not known in this case.

aRawHTML Slots: When You Must Have Raw HTML

Honestly, we're not big fans of raw HTML slots. Editors tend to paste code that breaks page layouts on a fairly regular basis. That's why our rich text slots filter it so carefully.

However there are times when you must have the embed code for a mailing list signup form service or similar third-party website feature.

In these situations the aRawHTML slot is useful.

Here is an example:

<?php a_slot('mailinglist', 'aRawHTML') ?>

This slot displays a multiline text entry form where the editor can paste in HTML code directly. This code is not validated in any way, so you should think carefully before offering this feature to less experienced editors.

There is a way to recover if you paste bad HTML that breaks the page layout. Visit the page with the following appended to the URL:


When this parameter is present in the URL, raw HTML slots will escape their contents so that you see the source code, safely defanged. You can then click "Edit" and make corrections as needed.

That's it for the standard slots included with Apostrophe! Of course there will be more. And you can add more yourself. See the developer's section of this document.

Inserting Areas: Unlimited Slots in a Vertical Column

Slots are great on their own. But when you want to mix paragraphs of text with elements inserted by custom slots, it is necessary to create a separate template file for every page. This is tedious and requires the involvement of an HTML-savvy person on a regular basis.

Fortunately apostrophePlugin also offers "areas." An area is a continuous vertical column containing multiple slots which can be managed on the fly without the need for template changes.

You insert an area by calling a_include_area($name) rather than a_include_slot($name):

<?php a_include_area("sidebar") ?>

When you insert an area you are presented with a slightly different editing interface. At first there are no editable slots in the area. Click "Insert Slot" to add the first one. You can now edit that first slot and save it.

Add more slots and you'll find that you are also able to delete them and reorder them at will.

By default new slots appear at the top of an area. If you don't like this, you can change it for your entire site via app.yml:

    new_slots_top: false

An area has just one version control button for the entire area. This is because creating, deleting, and reordering slots are themselves actions that can be undone through version control.

In a project with many custom slot types, you may find it is inappropriate to use certain slot types in certain areas. You can specify a list of allowed slot types like this:

<?php a_include_area("sidebar",
  array("allowed_types" => array("aText", "myCustomType"))) ?>

Notice that the second argument to a_include_area is an associative array of options. The allowed_types option allows us to specify a list of slot types that are allowed in this particular area.

In addition, you can pass options to the slots of each type, much as you would when inserting a single slot:

  array("allowed_types" => array("aText", "myCustomType"),
    "type_options" => array(
      "aText" => array("multiline" => 1))));

Here the multiline option specifies that all aText slots in the area should have the multiline option set.

Global Slots and Virtual Pages

Most of the time, you want the content of a slot to be specific to a page. After all, if the content was the same on every page, you wouldn't need more than one page.

However, it is sometimes useful to have editable content that appears on more than one page. For instance, an editable page footer or page subtitle might be consistent throughout the site, or at least throughout a portion of the site.

The quickest way to do this is by adding a "global slot" to your page template or layout. Just set the global option to true when inserting the slot:

    <?php a_slot('footer', 'aRichText',
      array('toolbar' => 'basic', 'global' => true)) ?>

The content of the resulting slot is shared by all pages that include it with the global option.

Note that you can use the global flag with areas as well as slots:

<?php a_area('footer', array(
  'allowed_types' => array('aRichText', 'aImage'),
  'global' => true
  )) ?>

By default, global slots can be edited only by users with editing privileges throughout the site. Otherwise users with control only over a subpage could edit a footer displayed on all pages. See below for more information about how to override this rule where appropriate.

Virtual Pages: When Global Slots Are Not Enough

Global slots will do the job for most situations that front end developers will encounter. If you're not a PHP developer looking to use slots and areas to manage dynamic content related to your own code, it's probably safe to skip this section, although it is sometimes useful to apply these techniques if you would otherwise have hundreds of global slots in your design.

Conceptually, all global slots reside together on a virtual page with the slug global. Since there is no leading /, this page can never be navigated to. The global virtual page resides outside of the site's organizational tree and is used only as a storehouse of shared content.

For headers and footers, this works very well. But if you have a larger amount of shared content, the model begins to break down. Fortunately there's a solution: group your content into separate virtual pages.

When you include a slot this way:

  <?php a_slot('biography', 'aRichText', array('toolbar' => 'basic', 'slug' => "bio-$id")) ?>

You are fetching that slot from a separate virtual page with the slug bio-$id, where $id might identify a particular user in the sfGuardUser table.

Apostrophe will automatically create the needed virtual page object in the database the first time the slot is used. This technique can be used for areas as well.

But why is this better than using 'global' => true? Two reasons: performance and access control.

Performance, Global Slots and Virtual Pages

Yes, you could manage dynamic content like biographies with just the global flag, if you chose dynamically generated names for your slots and areas. But since Apostrophe loads all current global slots into memory the first time a global slot is requested on a page, the CMS would be forced to load all of your biographies on just about every page of the site. That's clearly not acceptable. For that reason it's important to use a separate virtual page for each individual's biography, etc.

As a rule of thumb, add database IDs to virtual page slugs, not area or slot names.

Access Control For Global Slots and Virtual Pages

By default, only sitewide admins can edit slots included from other virtual pages. This is fine for headers and footers seen throughout the site, but doesn't work well for biography slots. In other words, users should be able to write their own autobiographies.

You can address this problem by passing 'edit' => true as an option to the slot or area. This overrides the normal slot editing privilege checks, so you should do this only if the user ought to have the privilege according to your own judgment. For instance, you might do something like this:

<?php $myid = sfContext::getInstance()->getUser()->getGuardUser()->id ?>
  <?php a_slot('biography', 'aRichText', array('toolbar' => 'basic', 'slug' => "bio-$id", 'edit' => $id === $myid)) ?>

Once again, you can do this with areas as well.

Including Slots From Other "Normal" Pages

It's possible to use the slug option to include a slot from another normal, navigable page on the site. However, if you do so, keep in mind that you don't want to create a situation where the same slot is included twice on the page itself.

Also keep in mind that normal pages can be moved around on the site, which will break templates that contain explicit slugs pointing at their old locations. You can avoid this by determining the slug option dynamically, or by using a virtual page instead (no leading / on the slug).

CSS: Styling Apostrophe

By default, apostrophePlugin provides two stylesheets which are automatically added to your pages. There's a lot happening there, particularly with regard to the editing interface, and we recommend that you keep these and override them as needed in a separate stylesheet of your own. However, you can turn them off if you wish in app.yml:

    use_bundled_stylesheet: false

If you do keep our stylesheets and further override them, you'll want to specify position: last for the stylesheets that should override them. This is automatically done for the stylesheet web/css/main.css in our sandbox project, and we recommend you make your customizations there.

The Golden Rule: Use Classes, Not IDs

You may want to style individual areas and slots without introducing wrapper divs to your templates. To do that, pay attention to the CSS classes we output on the outermost wrappers of each area. You'll note that standalone slots still have an area wrapper in order to implement their editing controls and make it easier to write consistent CSS:

<div id="a-area-12-body" class="a-area a-area-body">

You may be tempted to use the id attribute. Don't do that. The id attribute contains the page ID, which differs from page to page and should never be used in CSS.

Instead, take advantage of the classes on this div:

  css rules specific to the area or slot named 'body'

Note that if you need different behavior on different pages in a way that can't be achieved by adding different slots and using different variants of each slot, you should use a separate template for each type of page.

Access Control: Who Can Edit What?

By default, an unconfigured Apostrophe site that was not copied from our sandbox follows these security rules:

  • Anyone can view any page without being authenticated.
  • Any authenticated (logged-in) user can edit any page, and add and delete pages.

This is often sufficient for simple sites. But Apostrophe can also handle more complex security needs.

Requiring Login to Access All Pages

To require that the user log in before they view any page in the CMS, use the following setting in app.yml:

    view_login_required: true

Requiring Login to Access Some Pages

To require the user to log in before accessing a particular page, just navigate to that page as a user with editing privileges and click on the "lock" icon.

By default, locked pages are only accessible to logged-in users. Of course, on some sites this is too permissive, especially when users are allowed to create their own accounts without further approval. In such situations you can set up different credentials to access the pages. To require the view_locked credential to view locked pages, use the following app.yml setting:

    view_locked_sufficient_credentials: view_locked

Then grant the view_locked permission to the appropriate sfGuard groups, and you'll be able to distinguish user-created accounts from invited guests.

Requiring Special Credentials to Edit Pages

Editing rights can be controlled in several ways. It may seem a bit confusing, so keep in mind that the default set of permissions, groups and app.yml settings in our sandbox project works well and allows the admin to assign editing and managing privileges anywhere in the CMS page tree as they see fit. Editing privileges allow users to edit a page, while managing privileges allow them to also create and delete pages.

Read on if you believe you may need to override this configuration. You will need to do that if you are not using our sandbox project as a starting point, as the default behavior of the plugin is to allow any logged-in user to edit as they see fit (often quite adequate for small sites without unprivileged user accounts).

Editing and managing privileges are granted as follows:

1) Any user with the cms_admin credential can always carry out any action in the CMS, regardless of all other settings. Note that the sfGuard "superadmin" user always has all credentials.

2) Any user with edit_sufficient_credentials can always edit pages (but not necessarily add or delete them) anywhere on the site. For instance, if you add such users to the executive_editors sfGuardGroup and grant that group the edit permission, then you can give them full editing privileges with these settings:

    edit_sufficient_credentials: ed