To participate you must create an account on apostrophenow.org. If you have already done so, click Login.

Changeset 145

Show
Ignore:
Timestamp:
02/03/10 10:33:47 (2 years ago)
Author:
tboutell
Message:

Enable all of the slots by default at the app.yml/settings.yml level (that doesn't mean they are in templates that don't want them). Replaced stale copy of plugin README with directions to go read the real thing. Autogenerated stuff

Location:
sandboxes/asandbox/trunk
Files:
8 modified

Legend:

Unmodified
Added
Removed
  • sandboxes/asandbox/trunk/README

    r8 r145  
    1 ## READ THIS FIRST ## 
     1Please see: 
    22 
    3 Did you download this plugin from the Symfony plugins site? Depending on your needs, you're probably doing it the hard way. Please read the "Installation" section! 
     3plugins/apostrophePlugin/README 
    44 
    5 ## Before You Begin ## 
    6  
    7 This is a beta release of a. Although the 
    8 CMS already works quite well and we have released production sites 
    9 based on it, certain aspects are still maturing.  
    10  
    11 We strongly recommend that you follow the instructions in this 
    12 document which permit you to check out or copy a complete project from svn  
    13 rather than wrestling with the pear package dependencies yourself. You can check 
    14 out our cmstest project directly as a sandbox, or copy it with our 
    15 svnforeigncopy script to start your own version-controlled project. 
    16 *This is how we start new sites of our own*, so you will get maximum 
    17 support following this approach. 
    18  
    19 ## Overview ## 
    20  
    21 apostrophePlugin is the core of a suite of plugins that make up the [Apostrophe Content Management System](http://www.apostrophenow.com/). The philosophy of Apostrophe is that editing should be done "in context" whenever possible. 
    22  
    23 apostrophePlugin is heavily inspired by and borrows a little code and numerous ideas from sfSimpleCMSPlugin. The changes here are dramatic enough that a separate plugin makes more sense, but we felt it important to acknowledge this up front. sfSimpleCMSPlugin allowed users to double-click text frames and edit them in place. We have expanded this idea to allow for a number of content slots to be edited without leaving the page. 
    24  
    25 Standard features of apostrophePlugin include version control for all content slots, locking pages for authenticated users only, and in-context addition/deletion/reordering/retitling of pages. When a user is logged in with appropriate privileges the breadcrumb trail and sub-navigation areas become editing tools, neatly extending the metaphors they already implement rather than requiring a second interface solely for editing purposes. 
    26  
    27 apostrophePlugin 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. 
    28  
    29 ## Supported Browsers ## 
    30  
    31 Editing works 100% in Firefox, Safari and Internet Explorer 7+.  
    32 Editing is expressly not supported in Internet Explorer 6. Of course, 
    33 browsing the site as a user works just fine in Internet Explorer 6. 
    34  
    35 ## Requirements ## 
    36  
    37 apostrophePlugin requires the following. Note that virtually all of the 
    38 requirements are included in the cmstest project which you can easily 
    39 check out or copy from svn as described below. We *strongly recommend* 
    40 starting out in this way. 
    41  
    42 ### System Requirements ### 
    43  
    44 The following must be installed on your system: 
    45  
    46 * PHP 5.2.4 or better, with a PDO driver for MySQL 
    47 * MySQL (tested), SQLite (tested), or another relational database supported by PDO and Doctrine 
    48 * For the media features: netpbm (recommended), or GD support in PHP 
    49 * Optional, for PDF slots: ghostscript 
    50  
    51 See apostrophePlugin/README and apostrophePlugin/README for more 
    52 information about installing netpbm and ghostscript. A few truly excellent hosting  
    53 companies may already have these in place. If you have a Virtual Private Server 
    54 (and you should, shared hosting is very insecure), you can most likely install 
    55 netpbm and ghostscript with a few simple commands like `sudo apt-get install netpbm` 
    56 and `sudo apt-get install ghotscript`. 
    57  
    58 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. 
    59  
    60 ### PHP Libraries ### 
    61  
    62 * Symfony 1.2, 1.3 or 1.4 (provided with cmstest) 
    63 * sfJqueryReloadedPlugin (provided with cmstest) 
    64 * sfDoctrineGuardPlugin (provided with cmstest) 
    65 * apostrophePlugin (provided with cmstest) 
    66 * Doctrine (standard with Symfony 1.2) 
    67 * The Zend framework, for search (provided with cmstest) 
    68  
    69 Note that all of the components "provided with cmstest" 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. 
    70  
    71 Most users will also want apostrophePlugin and apostrophePlugin, which are also included in cmstest. 
    72  
    73 Optional: apostrophePlugin is compatible with sfShibbolethPlugin (the 1.2 trunk version) and sfDoctrineApplyPlugin. 
    74  
    75 Mac users can most easily meet the PHP requirements by installing 
    76 the latest version of [MAMP](http://www.mamp.info/). Note that MAMP's PHP  
    77 must be your command line version of PHP, not Apple's default install of PHP.  
    78 To fix that, add this line to the `.profile` file in your home directory: 
    79  
    80     export PATH="/Applications/MAMP/Library/bin:/Applications/MAMP/bin/php5/bin:$PATH" 
    81  
    82 Of course your production server will ultimately need to meet the same requirements with regard to PHP and PDO. 
    83  
    84 The use of Microsoft Windows as a hosting environment has not been tested but may work reasonably well now that netpbm is no longer mandatory for media slots. 
    85  
    86 ## Installation ## 
    87  
    88 For the time being, we recommend that you check out our 
    89 sample project via Subversion. You don't need to be an svn expert 
    90 to use this command: 
    91  
    92 FOR SYMFONY 1.4 (recommended for new projects): 
    93  
    94                 svn co http://svn.symfony-project.com/plugins/apostrophePlugin/sandbox/trunk cmstest 
    95  
    96 FOR SYMFONY 1.3 (recommended only if you are stuck with other 1.2 code that isn't fully 1.4-ready yet): 
    97  
    98                 svn co http://svn.symfony-project.com/plugins/apostrophePlugin/sandbox/branches/1.3 cmstest 
    99  
    100 FOR SYMFONY 1.2 (*not recommended for new projects*): 
    101  
    102     svn co http://svn.symfony-project.com/plugins/apostrophePlugin/cmstest cmstest 
    103  
    104 Better yet, copy it to your own repository with [svnforeigncopy](https://sourceforge.net/projects/svnforeigncopy/) each time you want to start a new site. That's what we do. With `svnforeigncopy` you get a copy of the cmstest 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. 
    105  
    106 This will give you all of the necessary plugins and the ability to 
    107 `svn update` the whole shebang with one command. 
    108  
    109 Next light up the folder `cmstest/web` as a virtualhost named `cmstest` via MAMP, `httpd.conf` or whatever your testing 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. 
    110  
    111 Now create the `config/databases.yml` file, which must contain 
    112 database settings appropriate to *your* system. Copy the file 
    113 `config/databases.yml.sample` as a starting point: 
    114  
    115     cp config/databases.yml.sample config/databases.yml 
    116  
    117 If you are testing with MAMP the default settings 
    118 (username root, password root, database name cmstest) may work 
    119 just fine for you. If you are testing on a staging server you will 
    120 need to change these credentials. 
    121  
    122 Also create your `properties.ini` file: 
    123  
    124     cp config/properties.ini.sample config/properties.ini 
    125  
    126 In Symfony `properties.ini` contains information about hosts that a project can be synced to, in addition 
    127 to the name of the project. The sample properties.ini file just defines the name of the project. You'll 
    128 add information there when and if you choose to sync the project to a production server via project:deploy. 
    129 See the Symfony documentation for more information about that technique. 
    130  
    131 At this point you're ready to use the checkout of Symfony's 1.2.x branch 
    132 that is included in the project. If you want to use a different installation 
    133 of Symfony, such as a shared install for many sites (note that only 1.2.x is likely to work), copy  
    134 config/require-core.php.example to config/require-core.php and edit the  
    135 paths in that file.  
    136  
    137 Next, cd to the `cmstest` folder and run these commands: 
    138  
    139 (For Symfony 1.3 and 1.4) 
    140  
    141                 ./symfony doctrine:build --all 
    142                 ./symfony doctrine:data-load 
    143          
    144 (For Symfony 1.2) 
    145  
    146     ./symfony doctrine:build-all-reload 
    147  
    148 This will create a sample database from the fixtures files. 
    149  
    150 Now set the permissions of data folders so that they are writable by the web  
    151 server. Note that svn does NOT store permissions so you can NOT assume they are 
    152 already correct: 
    153  
    154 ./symfony project:permissions 
    155  
    156 Our apostrophePlugin extends project:permissions for you to include the 
    157 data/writable folder in addition to the standard web/uploads, cache and log folders. Handy, isn't it? 
    158  
    159 If you prefer you can do this manually: 
    160  
    161                 chmod -R 777 data/a_writable 
    162                 chmod -R 777 web/uploads 
    163                 chmod -R 777 cache 
    164                 chmod -R 777 log 
    165  
    166 More subtle permissions are possible. However be aware that most 
    167 "shared hosting" environments are inherently insecure for a variety 
    168 of reasons. Before criticizing the "777 approach," be sure 
    169 to [read this article on shared hosting and Symfony](http://trac.symfony-project.org/wiki/SharedHostingNotSecure). 
    170  
    171 Next, build the site's search index for the first time (yes, search is included). It doesn't live in 
    172 the database so it needs to be done separately. After this, you won't 
    173 need to run this command again unless you are deploying to a new 
    174 environment such as a staging or production server: 
    175  
    176 ./symfony aToolkit:rebuild-search-index --env=dev 
    177  
    178 (You can specify staging or prod instead to build the search indexes 
    179 for environments by those names.) 
    180  
    181 You can now log in as `admin` with the password `admin` to see how the 
    182 site behaves when you're logged in. Start adding subpages, editing 
    183 slots, adding slots to the multiple-slot content area... have a ball with it. 
    184  
    185 OPTIONAL: by default pages are reindexed for search purposes at the time 
    186 edits are made. For performance reasons you might be happier deferring 
    187 this to a cron job that runs every few minutes. If you want to take this 
    188 approach, set up a cron job like this: 
    189  
    190 0,10,20,30,40,50 * * * * /path/to/your/project/symfony a:update-lucene --env=env 
    191  
    192 Note the `--env` option. There is a separate index for each 
    193 environment. On a development workstation, specify `--env=env`. In 
    194 a production environment you might specify `--env=prod`. 
    195  
    196 Then turn on the feature in app.yml: 
    197  
    198 all: 
    199   a: 
    200     defer_search_updates: true 
    201  
    202 This speeds up editing a bit. But if you don't like cron, you don't have to enable it. 
    203  
    204 You can also change the word count of search summaries: 
    205  
    206                 all: 
    207                   a: 
    208                     search_summary_wordcount: 50 
    209  
    210 That's the easy way to configure the CMS. The notes that follow assume you're doing it the hard way, without starting from the cmstest project. 
    211  
    212 * * * 
    213  
    214 Install the above plug-ins in your Symfony 1.2 project. 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 
    215 a relative path: 
    216  
    217     cd web 
    218     ln -s ../plugins/apostrophePlugin/web apostrophePlugin 
    219     # Similar for other plugins required 
    220  
    221 The search features of the plugin rely on Zend Search, so you must 
    222 install the Zend framework.  
    223 [The latest version of the minimal Zend framework is sufficient.](http://framework.zend.com/download/latest) If you choose to install this system-wide 
    224 where all PHP code can easily find it with a `require` statement, great. 
    225 If you prefer to install it in your Symfony project's 
    226 `lib/vendor` folder, you'll need to modify your `ProjectConfiguration` class 
    227 to ensure that `require` statements can easily find files there: 
    228  
    229     class ProjectConfiguration extends sfProjectConfiguration 
    230     { 
    231       public function setup() 
    232       { 
    233         // We do this here because we chose to put Zend in lib/vendor/Zend. 
    234         // If it is installed system-wide then this isn't necessary to 
    235         // enable Zend Search 
    236         set_include_path( 
    237           sfConfig::get('sf_lib_dir') . 
    238             '/vendor' . PATH_SEPARATOR . get_include_path()); 
    239         // for compatibility / remove and enable only the plugins you want 
    240         $this->enableAllPluginsExcept(array('sfPropelPlugin', 'sfCompat10Plugin')); 
    241       } 
    242     } 
    243  
    244 Create an application in your project. Then create a 
    245 module folder named a as a home for your page templates 
    246 and layouts (and possibly other customizations): 
    247  
    248     mkdir -p apps/frontend/modules/a/templates 
    249  
    250 The CMS provides convenient login and logout links. By default these 
    251 are mapped to sfGuardAuth's signin and signout actions. If you are 
    252 using sfShibbolethPlugin to extend sfDoctrineGuardPlugin, you'll 
    253 want to change these actions in apps/frontend/config/app.yml: 
    254  
    255     all: 
    256       sfShibboleth: 
    257         domain: duke.edu 
    258       a: 
    259         actions_logout: "sfShibbolethAuth/logout" 
    260         actions_login: "sfShibbolethAuth/login" 
    261  
    262 You can also log in by going directly to `/login`. If you don't want to display the login link 
    263 (for instance, because your site is edited only you), just shut that feature off: 
    264  
    265     all: 
    266       a: 
    267         login_link: false 
    268  
    269 You will also need to enable the a modules in  
    270 your application's `settings.yml` file. Of course you may need 
    271 other modules as well based on your application's needs: 
    272  
    273     enabled_modules:        [a, aText, aRichText, sfGuardAuth] 
    274  
    275 a edits rich text content via the FCK editor. A recent version of FCK is 
    276 included in downloads of apostrophePlugin, which is among the requirements already stated 
    277 above. However you'll need to enable FCK in your settings.yml file, as follows: 
    278  
    279     all: 
    280       rich_text_fck_js_dir:   apostrophePlugin/js/fckeditor 
    281  
    282 Load the fixtures for the "stub" site. Every site begins with a home 
    283 page with all other pages being added as descendants of the home page: 
    284  
    285     ./symfony doctrine:build-all-load 
    286  
    287 By default a will establish routes which map your modules and  
    288 actions to: 
    289  
    290     /cms/modulename/actionname 
    291  
    292 And check all other URLs to see whether they match a slug (i.e. a path) 
    293 in the CMS, offering up that page via the a/show action if so. 
    294  
    295 If this is not what you want, you will need to shut off the built-in 
    296 routes via app.yml and write your own: 
    297  
    298     a_routes_register: false 
    299  
    300 In any case, it is up to you to provide a route for the homepage of 
    301 your site. If you want the homepage to be the root page of the CMS, 
    302 just use this route in your routing.yml file: 
    303  
    304     homepage: 
    305       url:   / 
    306         param: { module: a, action: show, slug: / } 
    307  
    308 ### Enhanced Form Controls ### 
    309  
    310 To get the benefit of the progressively enhanced form controls 
    311 featured in our admin tools, you'll need to add apostrophePlugin's 
    312 aControls.js to your view.yml file: 
    313  
    314     default: 
    315       ... Other things ... 
    316       javascripts:    [/apostrophePlugin/js/aControls.js] 
    317  
    318 ### Title Prefix ### 
    319  
    320 By default, the title element of each page will contain the title of that page. 
    321 In many cases you'll wish to specify a prefix for the title as well. 
    322  
    323 You can do so by setting `app_a_title_prefix` in `app.yml`. This option supports 
    324 optional internationalization: 
    325  
    326     all: 
    327       a: 
    328         # You can do it this way... 
    329         title_prefix: 
    330           en: 'Our Company : ' 
    331           fr: 'French Prefix : ' 
    332         # OR this way for a single-culture site 
    333         title_prefix: 'Our Company'       
    334  
    335 ## Routing Rules ## 
    336  
    337 For compatibility reasons, by default your CMS pages will appear in the /cms "folder" 
    338 of your site. That is, the "home page" is `http://yoursite.com/cms`. Administrative 
    339 actions of the CMS can then use the default Symfony routing rules. The home page URL ("/") is not overridden 
    340 by default and still goes wherever you'd like it to go in your application. 
    341  
    342 Of course, this probably isn't what most people want in practice. You can easily 
    343 customize it via your `routing.yml` file so that URLs are presumed to be CMS pages if they do  
    344 not expressly match a routing rule. And it's not hard to create a catch-all "folder" to contain URLs mapped to Symfony actions rather than CMS pages. 
    345  
    346 These rules are used by our `cmstest` project. Note that the routing rule for pages must be named `a_page` so that the plugin can use it explicitly to generate short paths to pages, even in the presence of the default rule which would otherwise match first: 
    347  
    348     # This default routing rule handles all non-CMS actions that are not 
    349     # explicitly routed by another rule 
    350  
    351     default: 
    352       url:   /admin/:module/:action/* 
    353  
    354     # A homepage rule is expected by a and various other plugins, 
    355     # so be sure to have one 
    356  
    357     homepage: 
    358       url:  / 
    359       param: { module: a, action: show, slug: / } 
    360  
    361     # Put any additional routing rules for other modules and actions HERE, 
    362     # before the catch-all rule that routes URLs to the 
    363     # CMS by default. 
    364  
    365     # Must be the last rule, and must have this name 
    366  
    367     a_page: 
    368       url:   /:slug 
    369       param: { module: a, action: show } 
    370       requirements: { slug: .* } 
    371  
    372 When using this technique you will also need to turn off the default routing rules of the CMS 
    373 as we've done in the `cmstest` project: 
    374  
    375     all: 
    376       a: 
    377         routes_register: false 
    378  
    379 Thanks to Stephen Ostrow for his analysis of this issue. 
    380  
    381 ## Customizing Your CMS Site ## 
    382  
    383 You now have a CMS site. Access your Symfony site's URL to see the 
    384 home page.  
    385  
    386 Click "log in" and log in as the sfDoctrineGuard superuser  
    387 (admin/admin) to see 
    388 the editing controls. 
    389  
    390 ### Managing Pages ### 
    391  
    392 When a user has appropriate privileges on a page, they are able to make 
    393 the following changes via the breadcrumb trail, which becomes an 
    394 interactive site management tool when logged in: 
    395  
    396 * Rename the page by clicking on the page title 
    397 * Add a child page beneath the current page 
    398 * Open the page management settings dialog via the "gear" icon 
    399 for less frequent changes 
    400  
    401 Note that the breadcrumb trail appears on the home page only when 
    402 logged in with editing privileges. On sub-pages the breadcrumb 
    403 trail always appears. 
    404  
    405 a emphasizes "turning off" pages as the preferred way 
    406 of "almost" deleting them because it is not permanent. 
    407 Anonymous users, and users who do not 
    408 have editing privileges on the page, will see the usual 404 Not Found error. 
    409 But users with editing privileges will see the page with its title 
    410 "struck through" and will be able to undelete the page if they desire. 
    411 You can also delete a page permanently via the small X in the lower right 
    412 corner of the management dialog. Most of the time that's a shortsighted 
    413 thing to do but it is useful when you create an unnecessary 
    414 page by accident. 
    415  
    416 The side navigation column also offers an editing tool: users with 
    417 editing privileges can change the order of child pages listed there 
    418 by dragging and dropping them. (If a page has no children, the side 
    419 navigation displays its peers instead, including itself.) 
    420  
    421 ### Editing Content: Editing Slots ### 
    422  
    423 What about the actual content of the page? The editable content 
    424 of a page is stored in "CMS slots" (not the same thing as Symfony slots). 
    425 CMS slots can be of several types: 
    426  
    427 * Plaintext slots (single line or multiline) 
    428 * Rich text slots (edited via FCK) 
    429 * Custom slots (of any type, implemented as described later) 
    430  
    431 Once you have logged in, you'll see each editable slot outlined 
    432 with a box. If the slot is currently empty, there will also be a  
    433 hint to double-click the slot to begin editing it. Double-click it to 
    434 edit it with the appropriate editor (an input, textarea or rich 
    435 text edit control). Click "Save" to save your changes. 
    436  
    437 Every slot also offers version control. The arrow-in-a-circle icon 
    438 accesses a dropdown list of all changes that have been made to that slot, 
    439 labeled by date, time and author and including a short summary of the change 
    440 to help you remember what's different about that version. Pick any version and  
    441 click "Preview" to redisplay that version of the slot. To revert to an old version, 
    442 copying it to create a new version of the slot, click "Revert." Click 
    443 "Cancel" if you decide not to switch versions. 
    444  
    445 ### Editing Content: Editing Areas ### 
    446  
    447 In addition to single slots, apostrophePlugin also supports 
    448 "areas." Areas are vertical columns containing more than one slot. 
    449 Editing users are able to add and remove slots from an area at any time,  
    450 selecting from a list of slots approved for use in that area. The slots 
    451 can also be reordered via up and down arrow buttons (used here instead 
    452 of drag and drop to avoid possible browser bugs when dragging and dropping  
    453 complex HTML, and because drag and drop is not actually much fun to use 
    454 when a column spans multiple pages). 
    455  
    456 The usefulness of areas may not be entirely clear when only  
    457 plaintext and rich text slots are in use on a site. Their usefulness 
    458 can be better understood when custom slot types that implement 
    459 multimedia or connect to your project-specific resources come into play. 
    460 You want to be able to interleave these with blocks of text without 
    461 creating a new custom page template for each one. Areas give you that 
    462 capability. 
    463  
    464 ## Creating and Managing Page Templates and Layouts ## 
    465  
    466 Where do slots appear in a page? And how do you insert them?  
    467  
    468 Slots can be inserted in two places: in your site's <tt>layout.php</tt> 
    469 file, which decorates all pages, and in page template files, which can 
    470 be assigned to individual pages.  
    471  
    472 ### How to Customize the Layout ### 
    473  
    474 By default, the CMS will use the 
    475 <tt>layout.php</tt> file bundled with it. If you wish, you can turn this 
    476 off via app.yml: 
    477  
    478     all: 
    479       a: 
    480         use_bundled_layout: false 
    481  
    482 CMS pages will then use your application's default layout. One strategy 
    483 is to copy our <tt>layout.php</tt> to your application's template folder 
    484 and customize it there after turning off use_bundled_layout. 
    485  
    486 ### Turning Off the Home Page Tab ### 
    487  
    488 By default a generates navigation tabs for the direct 
    489 children of the home page, and for the home page itself. If you don't 
    490 want a tab for the home page itself, just change this setting 
    491 in `app.yml`: 
    492  
    493     all: 
    494       a: 
    495         home_as_tab: false 
    496  
    497 ### Reordering Children of the Home Page ### 
    498  
    499 a offers drag-and-drop reordering of the children of 
    500 any page via the navigation links on the left-hand side. But the home 
    501 page, by default, doesn't display those links. So how can you reorder 
    502 its children? 
    503  
    504 One way is to add the subnav component back into your 
    505 local copy of homeTemplate.php: 
    506  
    507     <?php include_component('a', 'subnav') # Left Side Navigation ?> 
    508  
    509 To avoid wrecking the layout of your home page, you could choose to  
    510 insert it only when an admin is logged in: 
    511  
    512     <?php if ($sf_user->hasCredential('cms_admin')): ?> 
    513       <?php include_component('a', 'subnav') # Left Side Navigation ?> 
    514     <?php endif ?> 
    515  
    516 On our "TODO" list: allow the left side navigation controls to be collapsed 
    517 by default for more convenient use on pages with a layout that doesn't 
    518 really accommodate them. 
    519  
    520 ### How to Customize the Page Templates ### 
    521  
    522 The layout is a good place for global elements that should appear on 
    523 every page. But elements specific to certain types of pages are better 
    524 kept in page templates. These are standard Symfony template files with 
    525 a special naming convention. 
    526  
    527 Page template files live in the templates folder of the a module.  
    528 We provide these templates "out of the box:" 
    529  
    530 * homeTemplate.php 
    531 * defaultTemplate.php 
    532  
    533 homeTemplate.php is used by our default home page, and defaultTemplate.php 
    534 is the default template if no other template is chosen. 
    535  
    536 You can change the template used by a page by using the 
    537 template dropdown in the breadcrumb trail. This does not delete 
    538 the slots used by the previous template, so you can switch back 
    539 without losing your work. 
    540  
    541 How do you create your own template files? *Don't* alter the templates 
    542 folder of the plugin. As always with Symfony modules, you chould  
    543 instead create your own a/templates folder within your 
    544 application's modules folder: 
    545  
    546     mkdir -p apps/frontend/modules/a/templates  
    547  
    548 Now you can copy homeTemplate.php and defaultTemplate.php to this folder, 
    549 or just start over from scratch. You can also copy _login.php if you don't 
    550 like the way we present the login and logout options. The same applies 
    551 to _tabs.php and _subnav.php. We *do not recommend* altering the rest of the templates unless  
    552 you have a clear understanding of their purpose and function and are 
    553 willing to make ongoing changes when new releases are made. In general, 
    554 if you can use CSS to match the behavior of our HTML to your needs, 
    555 that will be more forwards-compatible with new releases of the CMS. 
    556  
    557 If you add additional template files, you'll need to adjust 
    558 the `app_a_templates` setting in `app.yml` so that your 
    559 new templates also appear in the dropdown menu: 
    560  
    561     all: 
    562       a: 
    563         templates: 
    564           home: 
    565             Home Page 
    566           default: 
    567             Default Page 
    568           mytemplate: 
    569             My Template 
    570  
    571 ### Inserting Slots in Layouts and Templates ### 
    572  
    573 Of course, creating layouts and templates does you little good if you 
    574 can't insert user-edited content into them. This is where the  
    575 CMS slot helpers come in. 
    576  
    577 Here's how to insert a slot into a layout or page template: 
    578  
    579     <?php # Once at the top of the file ?> 
    580     <?php use_helper('a') ?> 
    581      
    582     <?php # Anywhere you want a particular slot ?> 
    583     <?php a_slot('body', 'aRichText') ?> 
    584  
    585 Notice that two arguments are passed to the <tt>a_slot</tt> 
    586 helper. The first argument is the name of the slot, which distinguishes 
    587 it from other slots on the same page. *Slot names should contain only 
    588 characters that are allowed in HTML ID and NAME attributes*. We recommend 
    589 that you use only letters, digits, underscores and dashes in slot names. 
    590 The slot name will never be seen by the user. It is a useful label 
    591 such as `body` or `sidebar` or `subtitle`. 
    592  
    593 The second argument is the type of the slot. "Out of the box,"  
    594 apostrophePlugin offers two slot types: 
    595  
    596 * aRichText 
    597 * aText 
    598  
    599 You can add additional slot types of your own and release and 
    600 distribute them as plugins as explained later in this document. 
    601  
    602 The special slot name `title` is reserved for the title of the page 
    603 and is always of the type `aText`.  
    604 While you don't need to provide an additional editing interface 
    605 for the title, you might want to insert it as an `h1` somewhere in your 
    606 page layout or template as a design element: 
    607  
    608     <h1> 
    609         <?php a_slot('title', 'aText', 
    610           array('tool' => 'basic')) ?> 
    611     </h1> 
    612  
    613 The behavior of most slot types can be influenced by passing  
    614 options to them from the template or layout.  
    615 You can also pass an array of options as a third argument 
    616 to the helper, like this: 
    617  
    618     <h2> 
    619         <?php a_slot('subtitle', 'aRichText', 
    620           array('tool' => 'basic')) ?> 
    621     </h2> 
    622  
    623 Here we create a subtitle area on the page which is editable, but only 
    624 with the limited palette of options provided in FCK's `basic` toolbar. 
    625 This works because the `aRichText` slot implementation calls 
    626 the `textarea_tag` helper, passing the options array along. (These 
    627 options are also available to the slot implementation at the time 
    628 the slot is saved, which will be discussed in more detail later.) 
    629  
    630 Incidentally, you can create your own custom toolbars for FCK as part 
    631 of your FCK configuration. See the FCK documentation. Note that this 
    632 does not actually prevent the user from submitting other HTML, often 
    633 by copying and pasting from Microsoft Word, etc. We plan to include 
    634 connectors to an HTML tidying package in a future release of the CMS to reduce 
    635 the severity of this problem. 
    636  
    637 In addition to passing familiar options along to the  
    638 `input_tag` and `textarea_tag` helper functions, you can 
    639 also pass the `multiline` option when inserting a slot  
    640 of the type `aText`. When `multiline` is true, the 
    641 plaintext slot is rendered with `textarea_tag` rather than 
    642 `input_tag`. 
    643  
    644 ### Inserting Areas: Unlimited Slots in a Vertical Column ### 
    645  
    646 Slots are great on their own. But when you want to mix paragraphs of text with 
    647 elements inserted by custom slots, it is necessary to create a separate 
    648 template file for every page. This is tedious and requires the 
    649 involvement of an HTML-savvy person on a regular basis. 
    650  
    651 Fortunately apostrophePlugin also offers "areas." An 
    652 area is a continuous vertical column containing multiple slots which 
    653 can be managed on the fly without the need for template changes.  
    654  
    655 You insert an area by calling a_include_area($name) rather than 
    656 a_include_slot($name): 
    657  
    658     <?php a_include_area("sidebar") ?> 
    659  
    660 When you insert an area you are presented with a slightly different 
    661 editing interface. At first there are no editable slots in the area. 
    662 Click "Insert Slot" to add the first one. You can now edit that 
    663 first slot and save it. 
    664  
    665 Add more slots and you'll find that you are also able to delete them 
    666 and reorder them at will. 
    667  
    668 By default new slots appear at the top of an area. If you don't like this, 
    669 you can change it for your entire site via `app.yml`: 
    670  
    671                 all: 
    672                   a: 
    673                     new_slots_top: false 
    674  
    675 An area has just one version control button for the entire area. This 
    676 is because creating, deleting, and reordering slots are themselves 
    677 actions that can be undone through version control. 
    678  
    679 In a project with many custom slot types, you may find it is  
    680 inappropriate to use certain slot types in certain areas. You can 
    681 specify a list of allowed slot types like this: 
    682  
    683     <?php a_include_area("sidebar", 
    684       array("allowed_types" => array("aText", "myCustomType"))) ?> 
    685  
    686 Notice that the second argument to `a_include_area` is an 
    687 associative array of options. The `allowed_types` option allows us to 
    688 specify a list of slot types that are allowed in this particular area. 
    689  
    690 In addition, you can pass options to the slots of each type, much as 
    691 you would when inserting a single slot: 
    692  
    693     a_include_area("sidebar", 
    694       array("allowed_types" => array("aText", "myCustomType"), 
    695         "type_options" => array( 
    696           "aText" => array("multiline" => 1)))); 
    697  
    698 Here the `multiline` option specifies that all  
    699 `aText` slots in the area should have the 
    700 `multiline` option set. 
    701  
    702 ### Inserting the Breadcrumb Trail and Side Navigation ### 
    703  
    704 Your layout, or possibly your page templates if you wish to handle 
    705 it differently on some pages, will need to insert the breadcrumb 
    706 trail and side navigation elements on some or all pages. 
    707  
    708 The breadcrumb trail is inserted into a page template or layout 
    709 by the following code: 
    710  
    711     <?php include_component('a', 'breadcrumb') ?> 
    712  
    713 Sometimes, such as on the home page, you do not want to display the breadcrumb  
    714 trail at all, when the user is not logged in. But you still need it when you  
    715 are are logged in so that you can manage the page. You can handle 
    716 this situation like so: 
    717  
    718     <?php if (aTools::getCurrentPage()->userHasPrivilege('edit')): ?> 
    719       <?php include_component('a', 'breadcrumb') ?> 
    720     ?> 
    721  
    722 The side navigation column ("subnavigation") is inserted similarly: 
    723  
    724     <?php include_component('a', 'subnav') ?> 
    725  
    726 ## Global Slots ## 
    727  
    728 Most of the time, you want the content of a slot to be specific to a page. 
    729 After all, if the content was the same on every page, you wouldn't need 
    730 more than one page. 
    731  
    732 However, it is sometimes useful to have editable content that appears 
    733 on more than one page. For instance, an editable page footer or page  
    734 subtitle might be consistent throughout the site, or at least throughout 
    735 a portion of the site. 
    736  
    737 You can do this by adding a "global slot" to your page template or layout. 
    738 Just set the `global` option to `true` when inserting the slot: 
    739  
    740     <h4> 
    741         <?php a_slot('footer', 'aRichText', 
    742           array('toolbar' => 'basic', 'global' => true)) ?> 
    743     </h4> 
    744  
    745 The content of the resulting slot is shared by all pages that include 
    746 it with the `global` option. 
    747  
    748 Note that global slots can be edited only by users with editing 
    749 privileges throughout the site. Otherwise users with control only over 
    750 a subpage could edit a footer displayed on all pages. 
    751  
    752 Have a need for a slot that is shared by some pages, but not all pages? 
    753 Just name the slot appropriately. For instance, the slot name 
    754 `chemistry-blurb` might be suitable for all pages in the 
    755 chemistry department of an educational institution's website. 
    756  
    757 ## Overriding the Stylesheets ## 
    758  
    759 By default, `apostrophePlugin` provides two stylesheets which are 
    760 automatically added to your pages. There's a lot happening there, 
    761 particularly with regard to the editing interface, and we recommend 
    762 that you keep these and override them as needed in a separate 
    763 stylesheet of your own. However, you can turn them off if you wish 
    764 in `app.yml`: 
    765  
    766     all: 
    767       a: 
    768         use_bundled_stylesheet: false 
    769  
    770 If you do keep our stylesheets and further override them, you'll 
    771 want to specify `position: last` for the stylesheets that should 
    772 override them. 
    773  
    774 ## Access Control ## 
    775  
    776 By default, a a site follows these security rules: 
    777  
    778 * Anyone can view any page without being authenticated. 
    779 * Any authenticated (logged-in) user can edit any page, 
    780 and add and delete pages. 
    781  
    782 This is often sufficient for simple sites. But a can 
    783 also handle more complex security needs. 
    784  
    785 ### Requiring Login to Access All Pages ### 
    786  
    787 To require that the user log in before they view any page 
    788 in the CMS, use the following setting in `app.yml`: 
    789  
    790     all: 
    791       a: 
    792         view_login_required: true 
    793  
    794 ### Requiring Login to Access Some Pages ### 
    795  
    796 To require the user to log in before accessing a particular page, 
    797 just navigate to that page as a user with editing privileges 
    798 and click on the "lock" icon.  
    799  
    800 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: 
    801  
    802                 all: 
    803                   a: 
    804                     view_locked_sufficient_credentials: view_locked 
    805  
    806 Then grant the `view_locked` permission to the appropriate sfGuard groups, and you'll be able to distinguish user-created accounts from invited guests. 
    807  
    808 ### Requiring Special Credentials to Edit Pages ### 
    809  
    810 Editing rights can be controlled in several ways: 
    811  
    812 1) Any user with the `cms_admin` credential can always 
    813 edit, regardless of all other settings. Note that the 
    814 sfGuard "superadmin" user always has all credentials. 
    815  
    816 2) Any user with `edit_sufficient_credentials` can always edit 
    817 pages (but not add or delete them) anywhere on the site. For instance,  
    818 if you add such users to the `executive_editors` sfGuardGroup and grant that  
    819 group the `executive_editors` permission, then you can give them 
    820 full editing privileges with these settings: 
    821  
    822     all: 
    823       a: 
    824         edit_sufficient_credentials: executive_editors 
    825  
    826 Similarly, any user with `manage_sufficient_credentials` can always 
    827 add or delete pages anywhere on the site, in addition to editing content. So  
    828 a more complete setting for a typical setup would be: 
    829  
    830     all: 
    831       a: 
    832         edit_sufficient_credentials: executive_editors 
    833         manage_sufficient_credentials: executive_editors 
    834  
    835 3) Any user who is a member of the group specified by 
    836 `app_a_edit_group` can *potentially* be made an 
    837 editor in particular parts of the site. If 
    838 `app_a_edit_group` is not set, all users are 
    839 potential editors:  
    840  
    841     all: 
    842       a: 
    843         edit_group: editors 
    844  
    845 Similarly, any user who is a member of the group specified by 
    846 `app_a_manage_group` can potentially be given the ability to add 
    847 and delete pages in a particular part of the site. So a  
    848 common setup might be: 
    849  
    850     all: 
    851       a: 
    852         edit_group: editors 
    853         manage_group: editors 
    854  
    855 Why is this feature useful? Two reasons: because checking their membership 
    856 in one group is faster than checking their access privileges in the 
    857 entire chain of ancestor pages, and because when an administrator is 
    858 managing the list of users permitted to edit a page the list of users in the 
    859 editors group is much easier to read than a list of all users (especially 
    860 in a large system with many non-editing users). 
    861  
    862 4) Editing privileges for any specific page and its descendants 
    863 can be granted to any member of the group specified by  
    864 `app_a_edit_group` (if that option is set), or to 
    865 any user if `app_a_edit_group` is not set. 
    866 When a user with the `cms_admin` credential clicks on the "lock" icon,  
    867 they are given the option of assigning editors for that page.  
    868 The same principle applies to "managing" (adding and deleting) 
    869 pages, with the group being indicated by  
    870 your `app_a_manage_group` setting. 
    871  
    872 Note that the pulldown list of possible editors can be quite long 
    873 if there are thousands of people with accounts on your site! This 
    874 is why we recommend setting up groups as described above. 
    875  
    876 ### PublishingPages, by Choice and By Default ### 
    877  
    878 a offers a "published/unpublished" toggle under "manage page settings." 
    879 Pages that are unpublished are completely invisible to users who do not 
    880 have at least the candidate credentials to be an editor; a user without appropriate privileges 
    881 gets a 404 not found error just as if the page did not exist. In most cases 
    882 you should use this in preference to actually deleting the page because the 
    883 content is still available if you choose to bring it back later. 
    884  
    885 By default all new a pages are in the "published" state. 
    886 If you need to approach the matter more conservatively, you can easily 
    887 change this with the following `app.yml` setting: 
    888  
    889     all: 
    890       a: 
    891         default_on: false 
    892  
    893 ### Refreshing Slots ### 
    894  
    895 Most slots are self-contained: they contain all of the information needed to render them, with no references to an external site. However, for efficiency reasons, some slots do contain metadata such as static image URLs pointing to an external site and do not attempt to refresh that data on every single page load. Our media slots are a good example of this. 
    896  
    897 Such slots always contain enough information to update themselves in response to the a:refresh task: 
    898  
    899                 ./symfony a:refresh --env=prod --application=frontend 
    900  
    901 It's a good idea to run that task every day or so via a cron job if you are using our CMS with our media plugin and its supporting slots.  
    902  
    903 For performance reasons this task only looks at the latest version of each slot. If you are carrying out a one-time transition such as moving from development to production and you know that all of your frontend controller URLs have changed as a result, you can use the `--allversions` option to specify that older versions of slots should be refreshed as well: 
    904  
    905                 ./symfony a:refresh --env=prod --application=frontend --allversions 
    906  
    907 If you are implementing custom slot types that have a need to participate in the refresh task (see below for more information about custom slot types), just implement a public `refreshSlot` method in those slot classes. This method takes no arguments and returns nothing.  
    908  
    909 IMPORTANT: command line Symfony tasks have no idea what the hostname of the site is, which is a problem for tasks like a:refresh. If you are using this task with our media slots, you will need to tell Symfony what the hostname of your site is, or what the hostname of the media plugin site is (if they are different). In most cases the CMS site and the media plugin site are one and the same. 
    910  
    911 As far as our plugins are concerned, the simplest way to tell Symfony the hostname of the site is to set: 
    912  
    913 all: 
    914   cli: 
    915     host: www.mysite.com 
    916  
    917 (You can specify different settings for different environments of course.) 
    918  
    919 If you want to specify the location of the media server only, you can do that this way: 
    920  
    921 all: 
    922   aMedia: 
    923     client_site: media.mysite.com 
    924  
    925 (See the media plugin README for more information about setting up a media server on a separate site.) 
    926  
    927 ## Creating Custom Slot Types ## 
    928  
    929 You are not limited to the two slot types provided with 
    930 apostrophePlugin! Anyone can create new slot types by taking 
    931 advantage of normal Symfony features: modules, components, 
    932 actions, templates and Doctrine model classes. 
    933  
    934 Let's look at `aText` to understand how this works. 
    935  
    936 The `aText` slot type is implemented as follows: 
    937  
    938 1) *The model class.* Every slot must be stored in the database.  
    939 All slot types have a model class which inherits from  
    940 `aSlot`. The `aText` model class is 
    941 `aTextSlot`. 
    942  
    943 This is done using a feature of Doctrine called 
    944 *column aggregation inheritance*. Column aggregation inheritance 
    945 allows you to gain the benefits of object-oriented inheritance 
    946 while still storing all of the slot types in the same database table. 
    947 Doctrine manages this automatically for you. 
    948  
    949 The relevant portion of `config/doctrine/schema.yml` for 
    950 `aText` looks like this: 
    951  
    952     aTextSlot: 
    953       inheritance: 
    954         extends: aSlot 
    955         type: column_aggregation 
    956         keyField: type 
    957         keyValue: 'aText' 
    958  
    959 The `extends` keyword specifies the class we are inheriting from, while 
    960 the `keyValue` field must contain the name of the type. Doctrine uses 
    961 this to figure out what class of object to create when loading a  
    962 record from the `a_slot` table. The slot type name is 
    963 recorded in the `type` column. You don't need to worry 
    964 about the details, but for more information about them, 
    965 see the excellent Doctrine documentation. 
    966  
    967 *Note that the keyValue setting does not include the word Slot.* 
    968  
    969 *Yes, you can have custom columns in the database for your type.* 
    970 A plaintext slot doesn't need them because the `value` column works 
    971 well for storing its contents. But you can add whatever columns 
    972 suit your purposes. For example, a `aContextYoutube` slot type might 
    973 have a model class schema like this: 
    974  
    975     aContextYoutubeSlot: 
    976       inheritance: 
    977         extends: aSlot 
    978         type: column_aggregation 
    979         keyField: type 
    980         keyValue: 'aYoutube' 
    981       columns: 
    982         youtube_url: string(300) 
    983         youtube_auto_repeat: boolean 
    984  
    985 Note that I have prefixed the extra columns with `youtube_`. This is 
    986 required if you intend to release your slot type as a plugin because 
    987 collisions between column names intended for use in different slot types 
    988 can cause problems for other users. If your slot type is strictly for 
    989 use in your own project, then you can get by without a prefix. It would 
    990 be nice if Doctrine did this automatically so that the field names 
    991 of subclasses were never really in conflict, but so far it does not. 
    992  
    993 (You may be able to work around this issue with less typing by using  
    994 Doctrine's "name: phpname as sqlname" syntax. But I have not yet tested this 
    995 to see how it interacts with column aggregation inheritance.  
    996 TODO: find out! If you try it, let me know.) 
    997  
    998 Any columns that you add to your custom slot type are automatically 
    999 included in the definition of `a_slot` when the 
    1000 SQL for your database is generated by the `doctrine:build-all` 
    1001 or `doctrine:build-sql` task. 
    1002  
    1003 2) *The module.* Every slot type is implemented by a Symfony module of 
    1004 the same name. The `aText` slot type is implemented by the 
    1005 `aText` module. Create your own modules for your own 
    1006 custom slot types. However, you'll set up your actions, components and 
    1007 templates in a specific way as described below. 
    1008  
    1009 3) *The components class.* The `aText` slot type's 
    1010 component clas is called `aTextComponents`. Unlike  
    1011 typical components classes it inherits from 
    1012 `aBaseComponents`: 
    1013  
    1014     class aTextComponents extends aBaseComponents 
    1015  
    1016 This class typically contains two components, although the 
    1017 base class versions inherited from aBaseComponents are 
    1018 sometimes sufficient to do the job, depending on the behavior you want: 
    1019  
    1020 * The `normalView` component. The `normalView` component displays 
    1021 your slot as a normal user, not a user with editing privileges, 
    1022 would see it. The default implementation simply outputs the 
    1023 contents of of the `value` column of the slot as HTML, which works for 
    1024 both `aRichText` and `aText` (the latter stores 
    1025 its "plaintext" pre-escaped to be displayed as HTML). 
    1026  
    1027 If you choose to change this behavior, you'll need to code the  
    1028 `execute` method like so. Note the use of the 
    1029 `$this->setup()` method, which automatically sets up  
    1030 the slot object for you as well as various other conveniences: 
    1031  
    1032     public function executeNormalView() 
    1033     { 
    1034       // Sets up $this->slot, $this->name, $this->id, etc. automatically 
    1035       $this->setup(); 
    1036       // Examine options with $this->getOption('optionname'), set 
    1037       // various member variables for convenience in the partial, etc.   
    1038     } 
    1039  
    1040 The corresponding partial, `_normalView.php`, could look like this: 
    1041  
    1042     <?php if (!strlen($value)): ?> 
    1043       <?php if ($editable): ?> 
    1044         Double-click to edit. 
    1045       <?php endif ?> 
    1046     <?php else: ?> 
    1047     <?php echo $value ?> 
    1048     <?php endif ?> 
    1049  
    1050 Even though this is the non-editing view (displayed even to editors until 
    1051 they double-click), the `editable` parameter is set as a convenience so that  
    1052 you can determine that the user does have editing privileges. 
    1053  
    1054 * The `editView` component. The `editView` component displays your 
    1055 slot with appropriate editing controls. You can use Symfony 1.2+-style 
    1056 form classes, but you are not required to.  
    1057  
    1058 The `executeEditView` method for `aText' looks like this: 
    1059  
    1060     public function executeEditView() 
    1061     { 
    1062       $this->setup(); 
    1063       $this->multiline = $this->getOption('multiline'); 
    1064       // The rest of the options array is passed as HTML 
    1065       // options to the helper function, but this 
    1066       // should not be 
    1067       unset($this->options['multiline']); 
    1068     } 
    1069  
    1070 And the corresponding template, `_editView.php`, looks like this: 
    1071  
    1072     <?php if ($multiline): ?> 
    1073       <?php echo textarea_tag("value", 
    1074         $value, 
    1075         array_merge(array("id" => "$id-value"), $options)) ?> 
    1076     <?php else: ?> 
    1077       <?php echo input_tag("value", 
    1078         $value, 
    1079         array_merge(array("id" => "$id-value"), $options)) ?> 
    1080     <?php endif ?> 
    1081  
    1082 Note the use of the `$id` variable. This variable is guaranteed to 
    1083 make an ID unique among all of the slot editing forms that may be 
    1084 present on the page at any given time. Be sure to take advantage 
    1085 of `$id` rather than second-guessing the process by inserting 
    1086 `$name` and `$permid` yourself. 
    1087  
    1088 A Symfony 1.2+ forms-based implementation of the  
    1089 executeEditView method looks like this. Note that we don't 
    1090 always have to create a new form object! When the user's first 
    1091 attempt to fill out the form results in a validation error, 
    1092 the form object is automatically restored to `$this->form`. 
    1093 When you output that form object again, the validation errors 
    1094 are displayed just as they would be if you were using 
    1095 ordinary action templates. Neat and tidy. 
    1096  
    1097 Since Symfony generates form fields with ID attributes, 
    1098 it is necessary to set the form's name format in a way that 
    1099 will not conflict with other slot editing forms hidden in 
    1100 the page. This is the purpose of the `setId()` call below. 
    1101 Don't worry, you'll see the form class code that 
    1102 implements that method in a moment. 
    1103  
    1104     public function executeEditView() 
    1105     { 
    1106       $this->setup(); 
    1107       // If there is already a form object reuse it! It contains 
    1108       // validation errors from the user's first attempt to submit it. 
    1109       if (!isset($this->form)) 
    1110       { 
    1111         $this->form = new FvtestForm(); 
    1112  
    1113         // Be sure to show the current value.  
    1114  
    1115         $this->form->setDefault('count', $this->slot->value); 
    1116  
    1117         // Necessary to prevent HTML id collisions between multiple slots 
    1118         // on the same page (see the setId method of the FvtestForm class) 
    1119         $this->form->setId($this->id); 
    1120       } 
    1121     } 
    1122  
    1123 You'll note that we explicitly call `setDefault` to redisplay the 
    1124 current value of the slot. This raises an interesting question: if 
    1125 we were taking full advantage of column aggregation inheritance 
    1126 by using the Doctrine form that Symfony auto-generates for our 
    1127 slot model class, could we skip this step and also avoid the 
    1128 need to create our own form class? 
    1129  
    1130 Unfortunately, as of this writing the answer is no. Doctrine 
    1131 column aggregation inheritance is a beautiful thing, but it doesn't 
    1132 currently generate good forms. That's because the forms generated 
    1133 for the subclasses contain *all* of the fields for *all* of the 
    1134 subclasses. Obviousy, that's not desirable. So build your own 
    1135 form classes to edit your custom slot types... like this one: 
    1136  
    1137     class FvtestForm extends sfForm 
    1138     { 
    1139       public function configure() 
    1140       { 
    1141         $this->setWidgets(array( 
    1142           "count" => new sfWidgetFormInput(array()) 
    1143         )); 
    1144         $this->setValidators(array( 
    1145           "count" => new sfValidatorInteger(array( 
    1146             'min' => 10, 'max' => 20, 'required' => true)))); 
    1147         $this->widgetSchema->setFormFormatterName('table'); 
    1148       } 
    1149       public function setId($id) 
    1150       { 
    1151         $this->widgetSchema->setNameFormat("Fvtest-$id" . "[%s]"); 
    1152       } 
    1153     } 
    1154  
    1155 Note the `setId()` method which takes care of ensuring that each form 
    1156 field has an ID attribute that is unique throughout the document. 
    1157  
    1158 The corresponding `_editView.php` template is as follows 
    1159 (very simple indeed): 
    1160  
    1161     <table> 
    1162     <?php echo $form ?> 
    1163     </table> 
    1164  
    1165 But how does the form get bound and saved? That's the topic of 
    1166 the next section. 
    1167  
    1168 4) *The actions class.* The `aText` slot type's action 
    1169 class is called `aTextActions`. Unlike most action classes, 
    1170 it inherits from `aBaseActions`, like so: 
    1171  
    1172     class aTextActions extends aBaseActions 
    1173  
    1174 Our `aTextActions` class must contain at least 
    1175 one action, the edit action. This action is invoked when the user clicks 
    1176 "save" after editing the slot.  
    1177  
    1178 The `aBaseActions` class provides two private methods that 
    1179 help you code `executeEdit` correctly:  
    1180  
    1181 * `editSetup`, which locates the appropriate options for the slot and loads  
    1182 or creates a slot object. 
    1183 * `editSave`, which does the hard work of managing version 
    1184 control while saving the slot.  
    1185  
    1186 Call `$this->editSetup()` at the beginning of your `executeEdit` method, 
    1187 and *return the result* of `$this->editSave()` at the end. If the user's 
    1188 input is unacceptable and you want them to try again,  
    1189 *return the result* of a call to `$this->editRetry()` instead. 
    1190  
    1191 In between, all you have to do is look at the appropriate  
    1192 request parameters provided by your `_editView.php` template and 
    1193 set the appropriate fields of `$this->slot`. You can store your data in  
    1194 `$this->slot->value` if a variable-length string suits all of your needs 
    1195 (as it often does, especially with PHP's `serialize()` and unserialize()`). 
    1196 Or you can use the custom fields you defined when designing the schema 
    1197 for your model class. This has the advantage that you can more easily 
    1198 look for that information via database queries later.  
    1199  
    1200 For instance, for the Youtube slot mentioned earlier: 
    1201  
    1202     public function executeEdit(sfRequest $request) 
    1203     { 
    1204       $this->editSetup(); 
    1205       $url = $this->getRequestParameter('url'); 
    1206       $autorepeat = $this->getRequestParameter('auto_repeat'); 
    1207       $this->slot->youtube_url = $url; 
    1208       $this->slot->youtube_autorepeat = $autorepeat; 
    1209       // Note that we must return the result! 
    1210       return $this->editSave(); 
    1211     } 
    1212  
    1213 The Symfony 1.2+ forms approach is similar. Note that we once 
    1214 again take advantage of the `setId()` method we added to our 
    1215 form class earlier. We also need to take the unique ID of the 
    1216 slot form into account when calling `bind()`: 
    1217  
    1218     public function executeEdit(sfRequest $request) 
    1219     { 
    1220       $this->editSetup(); 
    1221       $this->form = new FvtestForm(); 
    1222       $this->form->setId($this->id); 
    1223       $this->form->bind($request->getParameter("Fvtest-" . $this->id)); 
    1224       if ($this->form->isValid()) 
    1225       { 
    1226         $this->slot->value = $this->form->getValue('count'); 
    1227         return $this->editSave(); 
    1228       } 
    1229       else 
    1230       { 
    1231         // Automatically passes $this->form on to the  
    1232         // next iteration of the edit form so that 
    1233         // validation errors can be seen 
    1234         return $this->editRetry(); 
    1235       } 
    1236     } 
    1237  
    1238 To simplify validation, `$this->form` is automatically 
    1239 provided to the `editView` component when you call `editRetry()`,  
    1240  
    1241 ### Beyond edit: Additional Actions ### 
    1242  
    1243 Note that your actions class may contain other actions if you wish. Typically 
    1244 these are AJAX actions and other auxiliary actions that share the work 
    1245 of editing the slot object.  
    1246  
    1247 You may also have actions that perform AJAX updates of the normal, non-editing 
    1248 view of the slot. 
    1249  
    1250 In these cases, you may need to be able to retrieve the slot again and 
    1251 refresh a portion of your display. For actions that modify the slot, 
    1252 you can just call `this->editSetup()` to get all of the necessary parameters 
    1253 and initialize `$this->slot`. But what about actions that don't modify 
    1254 the slot but nevertheless need to access its contents? Such actions 
    1255 can call `$this->setup()` instead. The `setup()` method is equivalent 
    1256 to `editSetup()`, except that it allows access by users who do not have 
    1257 editing privileges unless you expressly pass the value `true` for the 
    1258 `$editing` parameter. (It does check for view access.) 
    1259  
    1260 ### Custom Validation ### 
    1261  
    1262 Sometimes `$this->form` isn't enough to meet your needs. You might 
    1263 have more than one Symfony 1.2+ form in the slot (although you should 
    1264 look at `embedForm()` and `mergeForm()` first before you say that).  
    1265 Or you might not be using Symfony 1.2+-style forms at all. 
    1266  
    1267 Fortunately there's a way to pass validation messages from the  
    1268 `executeEdit` action to the next iteration of the `editView` component: 
    1269  
    1270     // Set it in the action 
    1271     $this->validationData['custom'] = 'My error message'; 
    1272  
    1273     // Grab it in the component 
    1274     $this->error = $this->getValidationData('custom'); 
    1275  
    1276     // ... And display it in the template 
    1277     <?php if ($error): ?> 
    1278       <h2><?php echo $this->error ?></h2> 
    1279     <?php endif ?> 
    1280  
    1281 Note that `$this->validationData['form']` is used internally 
    1282 to store `$this->form`, if it exists in the action. So we suggest 
    1283 that you use other names for your validation data fields. 
    1284  
    1285 ### Overriding the Outline Box and Double-Click Behavior ### 
    1286  
    1287 By default, when a user with editing privileges is viewing a slot, 
    1288 the slot has an outline box and can be double-clicked to display 
    1289 the editing view.  
    1290  
    1291 This works well for many slot types but might not be appropriate 
    1292 for yours. If you wish to implement a different behavior for switching to 
    1293 the editor, such as an "Edit" button, just add this method to 
    1294 your slot's model class: 
    1295  
    1296     public function isOutlineEditable() 
    1297     { 
    1298       return false; 
    1299     } 
    1300  
    1301 This will turn off the "double-click to edit" behavior and outline box. 
    1302  
    1303 You can replace this with the following or similar in your 
    1304 `_normalView.php` template: 
    1305  
    1306 <?php if ($editable): ?> 
    1307   <?php echo button_to_function('Edit', $showEditorJS) ?> 
    1308 <?php endif ?> 
    1309  
    1310 Note that `$showEditorJS` comes preloaded with ready-to-run JavaScript code  
    1311 to hide the normal view and display the editing view.  
    1312  
    1313 You can also turn off the outline box for a particular insertion of a slot 
    1314 by padding the `outline_editable` option to the slot helper with  
    1315 a value of `false`. Explicit settings for this option override  
    1316 what is returned by the `isOutlineEditable` module. 
    1317  
    1318 ### Overriding the default view for a new slot ### 
    1319  
    1320 By default, when a user adds a new slot to an area the user must then click the 
    1321 edit button before making changes to the slot.  To take the user straight to 
    1322 the editView of your slot override $editDefault in your slots model.  For an 
    1323 example refer to PluginaTextSlot.class.php. 
    1324  
    1325 ### When You Don't Want an Inline Editor ### 
    1326  
    1327 Most of the time, inline editors are great. But sometimes you might be 
    1328 happier with a full-page interface which eventually redirects back 
    1329 to a when the work is done. In such cases you'll want to  
    1330 display a link to that editor in the normal view template when the user 
    1331 has editing privileges. To make that work, just link to your 
    1332 standalone editor page like this: 
    1333  
    1334     <?php if ($editable): ?> 
    1335       <?php echo link_to("Edit This", "http://my/editor/page") ?> 
    1336     <?php endif ?> 
    1337  
    1338 Then, when the editing is complete, your editor will need to return 
    1339 the information to a by redirecting the browser 
    1340 or POSTing a form to a URL constructed like this: 
    1341  
    1342     url_for("yourSlotModuleName/edit?" . http_build_query( 
    1343       array( 
    1344         "name" => $name, 
    1345         "slug" => $slug, 
    1346         "permid" => $permid, 
    1347         "noajax" => 1))) 
    1348  
    1349 The easiest way to accomplish this is to pass the complete edit-action 
    1350 URL as a parameter when linking to your external editor. This code 
    1351 demonstrates how we do it for our own aContextMediaSlot, which 
    1352 integrates with apostrophePlugin without the need for any  
    1353 CMS-specific code in apostrophePlugin: 
    1354  
    1355     <?php if ($editable): ?> 
    1356       <?php echo link_to('Choose media', 
    1357         sfConfig::get('app_media_site', false) . "aMedia/select?" . 
    1358           http_build_query( 
    1359             array("multiple" => true, 
    1360             "after" => url_for("aMedia/edit?" . 
    1361               http_build_query( 
    1362                 array( 
    1363                   "name" => $name, 
    1364                   "slug" => $slug, 
    1365                   "permid" => $permid, 
    1366                   "noajax" => 1))))), 
    1367         array('class' => 'a-context-button')) ?> 
    1368     <?php endif ?> 
    1369  
    1370 Note the use of the `noajax` parameter. This suppresses the  
    1371 usual "refresh the slot without refreshing the whole page" behavior, 
    1372 which is not appropriate after we've already left the page to 
    1373 display an external editing page. When `noajax` is set, 
    1374 the user is redirected to the updated page instead. This is 
    1375 described in more detail in the next section. 
    1376  
    1377 If you want your slot to work as a global slot, you'll also need 
    1378 to be sure to pass the slug of the actual page as a parameter 
    1379 of your `after` URL. As part of a call to `http_build_query` it 
    1380 looks like this: 
    1381  
    1382     "actual_slug" => aTools::getRealPage()->getSlug() 
    1383  
    1384 You'll also want to turn off the "double-click to edit" behavior that 
    1385 would otherwise open the inline editor, as explained in the 
    1386 previous section. 
    1387  
    1388 ### When You Don't Want AJAX ### 
    1389  
    1390 Normally a displays the updated contents of the slot without 
    1391 refreshing the entire page. If this is unsuitable for your purposes, 
    1392 as will be the case if you are not using an inline editor, 
    1393 then just include a <tt>noajax</tt> parameter in the request received by 
    1394 your edit action, with a value of 1. The edit action will then  
    1395 redirect to the updated page rather than attempting to refresh 
    1396 only the updated slot.  
    1397  
    1398 ## Adding New Global Admin Buttons to the Top Bar ## 
    1399  
    1400 When a user with admin privileges is logged in, a bar appears at the top of each page offering links to useful facilities such as the sfGuardUser admin module. Other plugins such as `apostrophePlugin` extend this button bar with additional links. You can add links of your own. 
    1401  
    1402 First provide a static method in a class belonging to your own plugin or application-level code which invokes `aTools::addGlobalButtons` to add one or more buttons to the bar: 
    1403  
    1404     class aMediaCMSSlotsTools 
    1405     { 
    1406       // You too can do this in a plugin dependent on a, see  
    1407       // the provided stylesheetfor how to correctly specify an icon to go  
    1408       // with your button. See theapostrophePluginConfiguration class for the  
    1409       // registration of the event listener. 
    1410       static public function getGlobalButtons() 
    1411       { 
    1412         aTools::addGlobalButtons(array( 
    1413           new aGlobalButton('Media', 'aMedia/index', 'a-media'))); 
    1414       } 
    1415     } 
    1416  
    1417 The first argument to the `aGlobalButton` constructor is the label of the button. The second 
    1418 is the action (in your own code, typically). And the third is a CMS class to be added to the button, 
    1419 which is typically used to supply your own icon and a left offset for the image to 
    1420 reside in. 
    1421  
    1422 Now, in your plugin or project's `config/config.php` or in the initialize method of your plugin or project's configuration class, make the following call to register interest in the event: 
    1423  
    1424     // Register an event so we can add our buttons to the set of global  
    1425     // CMS back end admin buttons that appear when the apostrophe is clicked.  
    1426     $this->dispatcher->connect('a.getGlobalButtons',  
    1427       array('aMediaCMSSlotsTools', 'getGlobalButtons')); 
    1428  
    1429 The bar at the top of each page will now feature your additional button or buttons. 
    1430  
    1431 *Note:* you should not add large numbers of buttons to the bar. Usually no more than one per plugin is advisable. It's important that the bar remain manageable and convenient for site admins. 
    1432  
    1433 ## Engines: Grafting Symfony Modules Into the CMS Page Tree ## 
    1434  
    1435 Suitably coded Symfony modules can now be grafted into the page tree at any point in a flexible way that allows admins to switch any page from operating as a normal template page to operating as an engine page, with all URLs beginning with that page slug remapped to the actions of the engine module. When the engine page is moved within the site, all of the virtual "pages" associated with the actions of the module move as well. 
    1436  
    1437 *Currently a particular engine can only be placed at a single location in the site.* This is a consequence of the way routing rules are cached in Symfony. We are working to overcome this limitation. However, engine pages are still quite useful with this limitation, as they allow components such as a media plugin or people section to be located at the point in the site where the client wishes to put them without the need to edit configuration files. 
    1438  
    1439 Engine modules are written using normal actions and templates and otherwise-normal routes of the aRoute class. 
    1440  
    1441 This is a very powerful way to integrate non-CMS pages into your site. The media browser of apostrophePlugin will soon be rewritten to take advantage of it. 
    1442  
    1443 Engines should always be used when you find yourself wishing to create a tree of "virtual pages" beginning at a point somewhere within the CMS page tree.  
    1444  
    1445 To create a a engine, begin by creating an ordinary Symfony module. Feel free to test its functionality normally at this point. Then change the parent class from `sfActions` to `aEngineActions`.  
    1446  
    1447 NOTE: if your actions class has a `preExecute` method, be sure to call `parent::preExecute` from that method. Otherwise it will not work as an engine. 
    1448  
    1449 Now, create routes for all of the actions of your module, or a catch-all route for all of them. Make sure you give these routes the `aRoute` class in `routing.yml`. The following are sample routes for a module called `enginetest`: 
    1450  
    1451     # Engine rules must precede any catch-all rules 
    1452  
    1453     enginetest_index: 
    1454       url:  / 
    1455       param: { module: enginetest, action: index } 
    1456       class: aRoute 
    1457  
    1458     enginetest_foo: 
    1459       url:  /foo 
    1460       param: { module: enginetest, action: foo } 
    1461       class: aRoute 
    1462  
    1463     enginetest_bar: 
    1464       url:  /bar 
    1465       param: { module: enginetest, action: bar } 
    1466       class: aRoute 
    1467  
    1468     enginetest_baz: 
    1469       url:  /baz 
    1470       param: { module: enginetest, action: baz } 
    1471       class: aRoute 
    1472  
    1473 You can also use more complex rules to avoid writing a separate rule for each action, exactly as you would for a normal Symfony module. This example could replace the `foo`, `bar`, and `baz` rules above: 
    1474  
    1475     enginetest_action: 
    1476       url: /:action 
    1477       param: { module: enginetest } 
    1478       class: aRoute 
    1479  
    1480 You can also use Doctrine routes. Configure them as you normally would, but set the class name to aDoctrineRoute: 
    1481  
    1482                 a_event_show: 
    1483                   url:     /:slug 
    1484                   param:   { module: aEvent, action: show } 
    1485                   options: { model: Event, type: object } 
    1486                   class:   aDoctrineRoute 
    1487                   requirements: { slug: '[\w-]+' } 
    1488  
    1489  
    1490 In general, you may use all of the usual features available to Symfony routes. 
    1491  
    1492 Note that the URLs for these rules are very short and appear to be at the root of the site. `aRoute` will automatically remap these routes based on the portion of the URL that follows the slug of the "engine page" in question.  
    1493  
    1494 That is, if an engine page is located here: 
    1495  
    1496     /test1 
    1497  
    1498 And the user requests the following URL: 
    1499  
    1500 /test1/foo 
    1501  
    1502 The `aRoute` class will automatically locate the engine page in the stem of the URL, remove the slug from the beginning of the URL, and match the remaining part: 
    1503  
    1504     /foo 
    1505  
    1506 To the appropriate rule. 
    1507  
    1508 As a special case, when the engine page is accessed with no additional components in the URL, `aRoute` will match it to the rule with the URL `/`.  
    1509  
    1510 Note that as a natural consequence of this design, engine pages cannot have subpages in the CMS. In general, it is appropriate to use engines only when you wish to implement "virtual pages" below the level of the CMS page. If you simply wish to customize the behavior of just part of a page, a custom page template or custom slot will better suit your needs. 
    1511  
    1512 Once you have established your routes, you can create subnavigation between the actions of your module by writing normal `link_to` and `url_for` calls: 
    1513  
    1514     echo link_to('Bar', 'enginetest/bar') 
    1515      
    1516 To make the user interface aware of your engine, add the following to `app.yml`: 
    1517  
    1518     all: 
    1519       a: 
    1520         engines: 
    1521           '': 'Template-Based' 
    1522           enginetest: 'Engine Test' 
    1523    
    1524 Substitute the name of your module for `enginetest`. Be sure to keep the "template-based" entry in place, as otherwise normal CMS pages are not permitted on your site. 
    1525  
    1526 Linking to the "index" action of an engine page is as simple as linking to any other page on the site. But what if you need to generate a link to a specific engine action from an unrelated page? For instance, what if you wish to link to a particular employee's profile within an engine page that contains a directory of staffers?  
    1527  
    1528 Just call `link_to` exactly as you did before: 
    1529  
    1530     echo link_to('Bar', 'enginetest/bar') 
    1531      
    1532 If the current page is not an engine page matching the route in question, the a routing system will find the first engine page in the site that does match the route, and generate a link to that engine page.  
    1533  
    1534 Note: if there is currently no engine page for the given engine, this will throw an exception and generate a 500 error. This makes sense: trying to generate a link to an engine page that doesn't exist is a lot like trying to use a route that doesn't exist. You can test to make sure the engine page exists like this: 
    1535  
    1536 <?php if (aPageTable::getFirstEnginePage('enginetest')): ?> 
    1537   <?php echo link_to('Bar', 'enginetest/bar') ?> 
    1538 <?php endif ?> 
    1539  
    1540 Thissystem  works very well as long as there is only one engine page per engine in the site, and therefore no ambiguity about where the link should go. Currently engine support is limited to one engine page per engine in any case. However, we are working on this limitation and hope to soon offer a way to specify a particular engine page as a destination for the link. This will be useful in situations where you wish to instantiate the same engine more than once, possibly with page-specific settings. However, note that you can already have any number of "subpages" of your engine page using engine routes as described above. You can write a complete database-driven sub-application in an engine, with many sub-"pages." The only thing you currently can't do is insert that engine into two completely distinct portions of your CMS page tree. 
    1541  
    1542 After executing a `symfony cc`, you will begin to see your new engine module as a choice in the new "Page Engine" dropdown menu in the page settings form. Select your engine and save your changes. The page will refresh and display your engine.   
    1543  
    1544 Note that engine pages can be moved about the site using the normal drag and drop interface. 
    1545  
    1546 You can create your own subnavigation within your engine page. We suggest overriding appropriate portions of your page layout via Symfony slots. 
    1547  
    1548 ## Internationalization ## 
    1549  
    1550 Internationalization is supported at a basic level: separate versions 
    1551 of content are served depending on the result of calling getCulture() 
    1552 for the current user. When you edit, you are editing the version of 
    1553 the content for your current culture. The user's culture defaults, as 
    1554 usual, to the sf_default_culture settings.yml setting. The search index also  
    1555 distinguishes between cultures. Webmasters who make use of internationalization will want 
    1556 to add a "culture switcher" to their sites so that a user interface is 
    1557 available to make these features visible. Thanks to Quentin 
    1558 Dugauthier for his assistance in debugging these features. 
    1559  
    1560 ## Support ## 
    1561  
    1562 You can obtain support for a via the [a Google group](http://groups.google.com/group/a).  
    1563  
    1564 Also be sure to [follow our Twitter account](http://twitter.com/apostrophenow). 
    1565  
    1566 And be sure to [visit the Apostrophe Now site](http://www.apostrophenow.com/). 
    1567  
    1568 Apostrophe is our CMS product based on the open-source a plugin. 
     5For complete and up to date information. 
  • sandboxes/asandbox/trunk/apps/frontend/config/app.yml

    r113 r145  
    1010           
    1111    slot_types: 
    12       # aRawHTML: HTML 
    13       aImage: Image 
    14       aButton: Button 
    15       aPDF: PDF 
    16       aSlideshow: Slideshow 
    17       aVideo: Video 
     12      # The following slot types are always enabled (meaning only that you can include them in 
     13      # individual templates if you wish, so you still have control over what your end users do): 
     14      # 
     15      # aRichText 
     16      # aText 
     17      # aImage 
     18      # aSlideshow 
     19      # aButton 
     20      # aVideo 
     21      # aPDF 
     22      # aRawHTML 
     23      # 
     24      # You can enable additional slot types implemented at the application level or via 
     25      # plugins by adding their type names here along with a short descriptive label. 
     26      #  
     27      # Example: 
     28      # 
     29      # baseball: "Baseball Box Score" 
     30      # 
     31      # (This assumes you have implemented such a slot type of course.) 
     32       
    1833    home_as_tab: false 
    1934    templates: 
  • sandboxes/asandbox/trunk/apps/frontend/config/settings.yml

    r131 r145  
    3636    #    # Activated modules from plugins or from the symfony core 
    3737    enabled_modules:         
    38       - aCleanLogin 
    3938      - a 
    4039      - aNavigation 
     
    4241      - aRichTextSlot 
    4342      - aTextSlot 
    44       # - aRawHTMLSlot 
     43      - aRawHTMLSlot 
    4544      - aSlideshowSlot 
    4645      - aVideoSlot 
  • sandboxes/asandbox/trunk/lib/model/doctrine/apostrophePlugin/base/BaseaArea.class.php

    r105 r145  
    6969             ), 
    7070             )); 
    71         $this->option('type', 'INNODB'); 
     71        $this->option('symfony', array( 
     72             'form' => false, 
     73             'filter' => false, 
     74             )); 
    7275    } 
    7376 
  • sandboxes/asandbox/trunk/lib/model/doctrine/apostrophePlugin/base/BaseaAreaVersion.class.php

    r105 r145  
    7272             ), 
    7373             )); 
    74         $this->option('type', 'INNODB'); 
     74        $this->option('symfony', array( 
     75             'form' => false, 
     76             'filter' => false, 
     77             )); 
    7578    } 
    7679 
  • sandboxes/asandbox/trunk/lib/model/doctrine/apostrophePlugin/base/BaseaAreaVersionSlot.class.php

    r105 r145  
    6262             ), 
    6363             )); 
    64         $this->option('type', 'INNODB'); 
     64        $this->option('symfony', array( 
     65             'form' => false, 
     66             'filter' => false, 
     67             )); 
    6568    } 
    6669 
  • sandboxes/asandbox/trunk/lib/model/doctrine/apostrophePlugin/base/BaseaLuceneUpdate.class.php

    r105 r145  
    4444             ), 
    4545             )); 
    46         $this->option('type', 'INNODB'); 
     46        $this->option('symfony', array( 
     47             'form' => false, 
     48             'filter' => false, 
     49             )); 
    4750    } 
    4851 
  • sandboxes/asandbox/trunk/lib/model/doctrine/apostrophePlugin/base/BaseaSlot.class.php

    r105 r145  
    5151             )); 
    5252 
    53         $this->option('type', 'INNODB'); 
     53        $this->option('symfony', array( 
     54             'form' => false, 
     55             'filter' => false, 
     56             )); 
    5457 
    5558        $this->setSubClasses(array(