Apostrophe is a Symfony and Doctrine-based CMS that emphasizes in-context editing. All slot types are implemented as Symfony modules, with all the flexibility that implies. Version control is implemented, permitting easy rollback of all edits. jQuery is used to implement AJAX features.

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.

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," vertical columns containing one or more slots. 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.

Apostrophe "engines" allow full Symfony modules to be embedded at one or more points in the CMS and participate in CMS page tree navigation. And custom Apostrophe slots can be written easily as Symfony plugins. Apostrophe's "virtual pages" provide a way to group together reusable slots of content not conceptually anchored to a single page in a conventional page tree.

Symfony's admin interface is fully internationalized, with XLIFF files already available in French, German and Spanish with more on the way. See the Apostrophe documentation for more information about internationalizing Apostrophe.

Obtaining Apostrophe

We recommend obtaining Apostrophe via svn rather than downloading tarballs. It is very easy to check out a stable sandbox project with svn. See the Apostrophe developer documentation for more information.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Join this plugin team

Changelog for release 1.0.5 - 17/02/2010

Moved the manual to trac.apostrophenow.org, reorganized it into multiple pages for easy reading.

Other releases

Release 1.5.1 - 05/02/2011

This version contains fixes made since the release of 1.5.0. It is a stability and quality fix, consisting entirely of bug fixes and a very small number of minor features addressing obvious oversights. This version is a strongly recommended upgrade. Please note that tarball releases are not the best way to get this plugin and keep it up to date with fixes. A better idea is to use svn:externals to bring the 1.5 stable branch of apostrophePlugin into your project's plugins folder and just type svn update regularly.

Fixes in version 1.5.1:

Users with "Add/Delete Pages" can add pages without validation errors

"Add/Delete Pages" and "apply to subpages" work properly again for view and edit permissions

Fixed many issues regarding cropping, strongly recommend an upgrade for those who like the cropping feature

Lots of fixes to apostrophe:migrate-data-from-pkcontextcms and apostrophe:migrate. The former now triggers the latter on success since it is required to complete the job. Please, take the plunge and get off pkContextCMS

File slot now allows filtering by type, and lists only types that are downloadable

New .a-editing and .a-normal classes replace .editing-now and provide a class for the case where the slot is not being edited. Much easier to customize slot styles based on whether the user is editing

Fixed a bug in the button slot where you ccouldn't set the FCK toolbar for the richtext

Workaround for http://trac.symfony-project.org/ticket/8861

Media type detection improvements

Improvements to button.css, notably the .icon-right class

Scoped the audio slot ui to be more specific to avoid css conflicts

Media selection UI fixes

Images can be selected successfully for a file slot (this downloads the original of the image)

Clean off leading zeroes in a cross-browser fashion using a regexp. Fixes a bug with one-digit dates in the date widget

Styles for our "do not edit, this is a staging site" banner are now included

For the oldest formerly-pkcontextcms sites, create the media admin engine page. For everybody, make sure it stays published

Moved jquery.jplayer into the /js/plugins folder, added it to the JS in BaseaTools, made sure to use_helper(a) in the partials

Added jquery.scrollTo for the Cropper. When you are building a slideshow and have numerous thumbnails, often the cropper opens below the fold. Now, the cropper will scroll into view when you engage it, and return you back to you to the top of media when you are finished

Updated the look / feel of the slideshow controls when selecting

aString::limitWords now has a 'characters' option, which can be used to limit by characters rather than words, but otherwise behave exactly like limitWords (notably, all whitespace is converted to single spaces). The benefit is that methods that layer on top of aString::limitWords() can be called with a character limit instead. Set 'characters' to true and the 'word_limit' parameter becomes a character limit

Release 1.5.0 - 18/01/2011

Manual cropping in the media repository. You can now select images that would not have met the constraints for a particular slot previously, as long as you crop them

Big improvements in performance: CSS and JavaScript are minified and compressed to reduce the number of requests and the amount of traffic, speeding page load and helping the server run more smoothly. A new JavaScript integration strategy improves overall performance as well

Page permissions completely reworked: you can now set ?view? permissions on a per-page, per-individual-or-group basis. Edit permissions can now be set on a per-group-or-individual basis. Inheritance of permissions from parent pages has been discarded in favor of explicit one-time ?cascade to child pages? features

Pages you cannot visit due to a lack of privileges are no longer shown in navigation

?Audio? media type added, allowing you to directly host MP3 files with a nice MP3 player and an audio slot that embeds it

Built-in support for the LESS CSS compiler make stylesheets much easier to maintain

Media types are much more open. For instance there is now an ?Office? type for MS Office documents, plus text files and a few other related things. You can override these types via app.yml

We added a ?File? slot and deprecated the old PDF slot. The ?File? slot can be used for any downloadable file format you have chosen to allow on the site, such as a Word document

Vimeo, Viddler and SlideShare now gets the same ?special treatment? as YouTube when adding video (integrated search, support for pasting just the URL), and there is a simple way to add support for more such embedded services via plugins. We've also improved our support for "unknown" embed codes

You can set up Vimeo, Viddler, SlideShare and YouTube accounts to be automatically synced to your media repository via a cron job and our new ?Linked Accounts? feature. This too can be extended

Videos can be replaced with new videos, even videos from another service, without editing each slot that uses them

A new ?Smart Slideshow? slot brings in images automatically via categories or tags

Categories have been unified throughout the site; there is a single category admin page. This makes Apostrophe more consistent and extensible

A single ?Upload Media? form now accepts all permitted file types, no more separate UIs for images vs. PDFs etc. Uploaded filenames are automatically ?humanized? to become a suggested title, saves a great deal of time if your media is already well-labeled

iPhone and other cameras that save orientation hints in JPEGs are now fully compatible, Apostrophe auto-rotates these images

Pages now have meta tag and description fields; Google doesn?t care about tags, but our internal search feature can leverage them to produce more relevant results

Eliminated confusing distinction between ?template-based? and ?engine? pages (engines still exist "under the hood" for developers). There is one page type menu and configuration has been simplified as well

New import-site task accepts XML files and loads a full-fledged Apostrophe site, including conversion of HTML blocks with embedded
images into a series of rich text and image slots in an area

Batch import of all filetypes supported by the upgraded media repository (Word, Excel, etc. in addition to images, PDFs?)

Button slots can now feature an optional rich text description for more flexibility

Search has been enhanced. You can now search on specific fields (title, slug, tags, categories, body) and appropriate fields are given extra weight in ordinary search results. Example: title:?monkey mittens?

The culture can be part of the page URL for better SEO of internationalized sites

Apostrophe sites can be a subdirectory of an existing site (although we strongly recommend a holistic rethink of your site if you're considering this in most cases)

Better pagination

?This Page? button eliminated in favor of separate ?Page Settings? and ?Add Page? buttons. You can now set all of the properties of a new page at creation time. Managing pages is just plain pleasant

Many other usability improvements

Full names and email addresses are now part of the user management system, not just usernames

Release 1.4.2 - 04/09/2010

The most important fixes here are stability-related. This is a strongly recommended upgrade for all users of the 1.x series of Apostrophe or earlier

Admin: fixed a bug with the cascade page settings form from not displaying when all child pages are unpublished

Button slot: Fixed a few bugs with the button slot. If you set a title and a URL it will output a simple text link. It will also by default NOT output the image's full description, because that's the majority use case. If you want the image's full description along with the button, it can be enabled as an option

Deployment: the apostrophe:deploy task now instructs rsync to checksum files rather than relying on modification times. This is critical when deployment happens from multiple development workstations. As a result we no longer need to clear the APC cache on every deployment, so that feature has been removed from the aSync module. rsync checksum is supposedly slow, but on modern systems performance is quite reasonable

Deployment: new apostrophe:fix-remote-permissions task asks a remote production or staging server to chmod the Symfony-writable folders recursively to address the fact that umask() settings often prevent files created by Apache from being touched by command line tasks that need to do things like rebuilding the search index or syncing media content. Use this task to fix permissions "as Apache" without root access

Documentation: README updated

Documentation: package.xml.tmpl updates

Engine page routing fixes

JavaScript: removed an addJavascript call to jquery.hotkey because we do not use it and it was creating a 404 error

Media: don't try to calculate dimensions if PDF preview is turned off. With netpbm turned off we can't do PDF preview and shouldn't try to fetch the dimensions, which are unknown

Media: never return attributes for logged-out users in the media repository. We might have to revisit this if we decide to offer public filters of some sort that are attribute-based, but right now our attributes are designed for the image selection/management experience and should never be active after you log out. This was not a security hole, just a source of confusion

Search: fixed bug with clear search button

Search: new apostrophe:optimize-search-index task should be run nightly to reoptimize the Zend Search index

Security: the app_aMedia_admin_credential and app_aMedia_upload_credential options were hardcoded to media_admin and media_upload in a bunch of places. All of these cases have been fixed to respect app.yml so you can change the media credentials if you wish

Security: fixed security of aSync module, since it has its own password system it doesn't make sense to lock it with security.yml (also it is disabled by default)

Stability: wrapped tree lock calls around page creation and deletion to address the fact that Doctrine doesn't seem to have concurrency locks for nested set operations. We had previously locked reorganize operations for similar reasons but did not realize that the fundamental insert and delete operations did not have locks either (transactions do not address the same issue)

Stability: the repair-tree task has been overhauled. The task now uses PDO to avoid memory limitations of Doctrine, and is very fast now. There is now a method option which can be set to list or slug. The list option (well-tested) doesn't reorganize a messed-up page tree, but it does correct any errors in lft and rgt values such that it is now safe to manually reorganize it. The slug option infers the page tree from page slugs; this discards order of pages at the same level and doesn't work well if you heavily edit your slugs. A third approach is to specify the csv option, which should be set to a file containing a CSV dump with id, lft, rgt and level values (in that order, with no header) from a known-good database. Pages that did not exist in that good database become archived children of the homepage for easy cleanup in 'reorganize.' See the verbose help for the task for an example of how to create such a CSV file from a known-good backup

Navigation: getAncestorsInfo now has an optional $livingOnly flag to return only ancestors that are not archived

Performance: new app_a_search_hard_limit option prevents out of memory errors when searching very large sites. If you have 1,000's of pages see the documentation for more information about how to set this

Refactoring: $aPageTable->checkUserPrivilegesBody() is the core privilege checker method; extend this, calling the base version and adding your own checks, and you won't have to worry about caching or rewriting privilege names, both of which are taken care of by the checkUserPrivileges method first

Refactoring: Fixed missing parent::configure() calls in the media subtype forms. Now you can modify the behavior of all media subtype forms by editing the configure() method of the aMediaItemForm class at project level

Refactoring: aPageTable::checkPrivileges is now a wrapper around $aPageTable->checkUserPrivileges(), which is easier to extend without static method inheritance problems

Refactoring: all form classes and many other classes, such as aTools, now extend a Basea* class so you can override them and extend the base to avoid duplicating code

Refactoring: the privileges portion of the page settings form is broken out to a single allPrivileges partial so that you can easily override that to add or remove parts of it when templating out the page settings form

Samples: removed twoColumnTemplate from app.yml sample

Search: Zend Lucene can throw exceptions if it doesn't like search syntax. Catch the exceptions and report no results

Security: custom secureSuccess message in sandbox project when you are logged in with insufficient permissions, behaves like a 404 by default

Security: don't show the heading for the page permissions area if both privilege widgets are disabled for this user

Security: explicit permissions are not checked for virtual pages (this introduced DQL bugs granting everyone edit permissions to them, you should be using the 'edit' flag to a_area or a_slot to programmatically determine who has rights to a virtual page)

Security: improved privilege cache

Signin: removed remember me button from the signin form partial since its default behavior in sfDoctrineGuardPlugin is not what users expect (i.e. it doesn't work)

Slots: Added an aUI call after slot is saved to reactivate buttons etc.

Slots: app_a_new_slots_top option now works properly. Thanks to martin79

Slots: editing-now class now removed from slots properly after save

Slots: slots can now be nested more deeply, often needed in the blog plugin

Slugs: Add checks and fixes when renaming a page creates a slug conflict

Slugs: Fixed bug that caused engines to not work properly with utf-8 slugs

Slugs: leading slash required when editing slugs

Slugs: new require_leading_slash option to aValidatorSlug

Testing: minor tweaks to the functional testing methods

Variants: The new app_a_allowed_slot_variants setting determines which slot variants are permitted by default. You can always override it with an explicit allowed_variants option in an a_slot or a_area call

Release 1.4.0 - 25/05/2010

Hooray, you can link directly to an engine page just by passing an engine-slug parameter as one of your route parameters in link_to and url_for! No more aRouteTools::pushTargetEnginePage() (although that is still occasionally useful and fully supported).

No more hardcoded routing.yml in apostrophePlugin to break people's backend apps. Instead we have a second event which registers when app_a_admin_routes_register is set (that defaults to true), and registers routes for the various admin modules only if they are actually enabled. Much more conservative.

Added the ability to now specify a slideshowItemPartial.php using the slot options. This adds a boatload of flexibility for not overriding the plugin at the project level, but instead enhancing it. It also integrates well with Variants. I switched the compact slideshow to use this technique and it works great.

a_get_option($options, 'height', 500) replaces isset($optionsheight) ? $optionsheight : 500. The former is much less bug-prone and friendlier in templates, which is important because the defaults for a presentation-related option are a presentation decision and therefore should be in the view layer (the template), but view code shouldn't be complicated to understand.

Refactored permissions checks from aPage to aPageTable

Improved handling of virtual pages in search results

No more 404 errors if you click upload without selecting any images, you get a reasonable validation error message instead

new apostrophe:import-files task pulls in JPEGs, GIFs, PNGs and PDFs in the specified folder, which defaults to web/uploads/media_import. Then it REMOVES those files from that folder.

Refactored SQL migration conveniences to aMigrate where they can be used by migration hooks in other plugins that listen to the apostrophe:migrate event

The media repository now behaves reasonably when PDFs are uploaded to a server that can't render previews of PDFs. An icon is substituted, rather than a fake rendering of the PDF, and the format field is set properly. Much better. Hit this with Jake and John this morning. The preview icon is chosen based on the format and is not hardcoded to PDF. You can reliably check for a nonrenderable media item by checking whether $mediaItem->getWidth() is null.

If we got valid image info, the image size is less than 1024x768, gd is enabled, and gd supports the image type, always use gd.

edit = false for slot options is no longer ignored when logged in as an admin

The various get*Info methods that return information about related pages now include the page template. This is handy when you want to link to an ancestor page in a special way if it is a landing page.

New aImageConverter::supportsInput($extension) method allows you to check whether aImageConverter can import a particular image format on this system. Mainly used to check for pdf support.

New default behavior of navigation components is appropriate for use on large sites with many pages. On small sites it may be slightly slower, in which case you can set app_a_many_pages to false to get the old "fetch the entire page tree and reuse it in each navigation component as needed" behavior back.

Use aTools::isPotentialEditor to determine when to include history browser div.

Pretty signficant changes to the history browser. The history browser now has its own close button instead of piggy-backing on the area cancel button. The area cancel button has now gone away completely because we use the nifty dropdown for Add Slot. And History has it's own set of controls.

fixed bug that caused the icons to disappear from the addSlot dropdown

changed the aAdmin assets file to look for our jQ UI that comes bundled with apostrophe rather than the Lightness UI bundled with jQuery reloaded

Warning comments before various API methods in aPage explaining the need to get the page properly with retrievePageBySlugWithSlots() first. TODO: document this entire subject in the manual.

One can now specify whether the slot should go to the top or bottom of the area when adding a slot with newAreaVersion (TODO: document this method generally and this option particularly)

Removed some obsolete, noisy logging calls

the admin generator form markup was outdated and did not reflect the form markup we use via the formFormatter so we updated it to be that way.

updated layout to allow for main navigation in non-cms pages

made normal and alt page settings button icons

revised page settings icons, smaller and brighter

changed delete and history buttons to be flagging buttons with white backgrounds, big visual clutter improvement

cleaned up history icon, still should probably be redone

adjusted styling on button flag-left and flag-right

new page settings icons

added icons for the reorganize tree to display which engines pages are using

created a brand new awesome a-btn.mini button (its rad)

Release 1.0.12 - 09/04/2010

Support classes for functional tests of Apostrophe. Our functional test parent classes permit fast reloading of the database between functional test classes via mysqldump. Support hooks for this are documented, we'll release that portion of our internal commit tool at some point.

Language switcher no longer uses a-controls class, I created a separate CSS class for it to avoid breaking functional tests that check whether the user sees controls they shouldn't.

changed background position for buttons to be compatible with the gradients, fixed incorrect width on slideshow arrows

Merged further improvements to functional testing from trunk

merged new add slot user interface into the 1.3 branch

removed the old tabs component in favor of the new aNavigation component we want people to use

"Home" no longer appears in French when it should be English

Related: aPageTable::retrieveBySlug() now fetches the page complete with its current slots for the proper culture. Without this there were too many situations where Doctrine's standard policy of refreshing a particular object if it is requested more than once led to unexpected behavior, such as missing slots or the wrong versions or cultures of slots.

missing alt images

restored button positioning for singleton slots

CSS improvements

fixed icon images for trash and cancel

Warning caused by obsolete argument in removeAttributes call fixed

When a-area slot is in an add-slot-now state, the z-index is increased to 999 so that the menu sits on top of everything else. This helps with stacking and overlap for neighboring elements

If you click anywhere outside of the addslot dropdown the dropdown goes away

There is no Form helper in Symfony 1.4 (this affected the aSubCrud functionality which is fairly obscure)

Submit and cancel butotn look and feel fixes

updated slideshow controls in template and css

No text shadow on new add-slot dropdown lis, up & down slot arrows were the wrong width.

Fixed double-escaping in rename page form

changed padding on flagged button, was set for previous button dimensions

Fixed an ugly bug with History Previews in 1.3

fixed slideshow arrow background position

styled blog error messages

little styling change on the blog errors

HTML "button" elements eradicated

solved the annoying cancel button issue finally, removed the stray lines from aUI that didn't need to be there anymore, and renamed some special button classes to avoid conflicts and clarify

Took the class off of the slideshow arrows because they're not buttons

Our admin generator theme no longer mandates that i18n be turned on in your settings.yml (although it CAN be)

added a unique class name to aNavigation for tabs and accordions

added a unique class name to aNavigation for breadcrumbs too

Release 1.0.11 - 25/03/2010

Release 1.0.9 - 24/03/2010

A computer abandoned by an admin who has logged out can no longer be used to edit slots the admin previously edited using cleverly constructed URLs (only an issue on the same computer and if the PHP session has not ended). Note that you must upgrade your myUser class in apps/frontend/lib to extend aSecurityUser rather than sfGuardSecurityUser to get this fix (aSecurityUser is a subclass of the latter)

Global or virtual-page media slots can be edited successfully on Symfony pages that are not CMS pages

Release 1.0.8 - 25/02/2010

Release 1.0.7 - 25/02/2010

Removed obsolete default layout for media repository; those not using the sandbox no longer have to explicitly override use_bundled_layout. Removed obsolete CSS files not used since the pk days. Media library cancel button is easier to see. Slideshows are saved in a way that doesn't crush additional data application-level overrides might be saving. All components and actions classes now overridable and inheritable at the app level. "Download original" and PDf viewing links now work properly.

Release 1.0.6 - 19/02/2010

Release 1.0.5 - 17/02/2010

Moved the manual to trac.apostrophenow.org, reorganized it into multiple pages for easy reading.

Release 1.0.4 - 17/02/2010

Documented slot variants. Fixed bugs in slot variants. Variants no longer have to rigorously contradict each other, they always start from the slot's options. Added the allowed_variants option for slots and areas, which allows them to be restricted to those that are suitable to a particular context, and also reordered, changing the default if desired (the first one allowed is the default). Removed 'mkdir -p' call that made generate-slot-type unusable on Windows. Various CSS fixes.

Release 1.0.2 - 12/02/2010

Apostrophe

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.

For complete and extensive documentation of Apostrophe please visit
trac.apostrophenow.org. There you'll find a complete manual organized much more conveniently than is possible with a README file.