Development

Changeset 20531

You must first sign up to be able to contribute.

Changeset 20531

Show
Ignore:
Timestamp:
07/27/09 21:18:24 (4 years ago)
Author:
boutell
Message:

Support for engines

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • plugins/pkContextCMSPlugin/trunk/README

    r19592 r20531  
    12911291*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. 
    12921292 
    1293 ## Changelog ## 
    1294  
    1295 ### Version 0.15 ### 
    1296  
    1297 Documentation fixes. 
    1298  
    1299 ### Version 0.14 ### 
    1300  
    1301 Another fix to the routing rules of the `cmstest` project. The Symfony 
    1302 default routing rule isn't really compatible with mapping the 
    1303 CMS to the root of the site: it breaks all grandchild pages. We deal 
    1304 with this by prefixing the default routing rule with /admin. So  
    1305 for a simple routing system without a lot of explicit rules for 
    1306 individual actions and modules, either the (a) CMS pages or (b) everything else 
    1307 must be distinguished by a prefix in the URL. The default rules 
    1308 prepended by the plugin take the former approach, while the cmstest 
    1309 project demonstrates the latter.  
    1310  
    1311 You can of course write explicit routing rules 
    1312 for individual Symfony actions and modules that result in nicer URLs for 
    1313 those particular actions and modules. That combines well with the 
    1314 second approach. 
    1315  
    1316 ### Version 0.13 ### 
    1317  
    1318 Fixed routing rules of `cmstest` project. Renamed 
    1319 the `pk_context_cms_page` rule and instituted the use of  
    1320 `@pk_context_cms_page` in the URL generation method to route to it even  
    1321 if the default routing rule would otherwise match `pkContextCMS/show` first. 
    1322  
    1323 ### Version 0.12 ### 
    1324  
    1325 Default routing rules no longer try to override the root 
    1326 of the site. Instead we demonstrate how to do that in the  
    1327 `routing.yml` file of the `cmstest` project. 
     1293## Engines: Grafting Symfony Modules Into the CMS Page Tree ## 
     1294 
     1295Suitably 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. 
     1296 
     1297A single engine may be grafted into the site in multiple locations. 
     1298 
     1299Engine modules are written using normal actions and templates and otherwise-normal routes of the pkContextCMSRoute class. 
     1300 
     1301This is a very powerful way to integrate non-CMS pages into your site. The media browser of pkMediaPlugin will soon be rewritten to take advantage of it. 
     1302 
     1303Engines 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.  
     1304 
     1305To create a pkContextCMS 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 `pkContextCMSEngineActions`.  
     1306 
     1307Now, 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 `pkContextCMSRoute` class in `routing.yml`. The following are sample routes for a module called `enginetest`: 
     1308 
     1309    # Engine rules must precede any catch-all rules 
     1310 
     1311    enginetest_index: 
     1312      url:  / 
     1313      param: { module: enginetest, action: index } 
     1314      class: pkContextCMSRoute 
     1315 
     1316    enginetest_foo: 
     1317      url:  /foo 
     1318      param: { module: enginetest, action: foo } 
     1319      class: pkContextCMSRoute 
     1320 
     1321    enginetest_bar: 
     1322      url:  /bar 
     1323      param: { module: enginetest, action: bar } 
     1324      class: pkContextCMSRoute 
     1325 
     1326    enginetest_baz: 
     1327      url:  /baz 
     1328      param: { module: enginetest, action: baz } 
     1329      class: pkContextCMSRoute 
     1330 
     1331You 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: 
     1332 
     1333    enginetest_action: 
     1334      url: /:action 
     1335      param: { module: enginetest } 
     1336      class: pkContextCMSRoute 
     1337 
     1338In general, you may use all of the usual features available to Symfony routes. 
     1339 
     1340Note that the URLs for these rules are very short and appear to be at the root of the site. `pkContextCMSRoute` will automatically remap these routes based on the portion of the URL that follows the slug of the "engine page" in question.  
     1341 
     1342That is, if an engine page is located here: 
     1343 
     1344    /test1 
     1345 
     1346And the user requests the following URL: 
     1347 
     1348/test1/foo 
     1349 
     1350The `pkContextCMSRoute` 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: 
     1351 
     1352    /foo 
     1353 
     1354To the appropriate rule. 
     1355 
     1356As a special case, when the engine page is accessed with no additional components in the URL, `pkContextCMSRoute` will match it to the rule with the URL `/`.  
     1357 
     1358Note 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. 
     1359 
     1360Once you have established your routes, you can create subnavigation between the actions of your module by writing normal `link_to` and `url_for` calls: 
     1361 
     1362    echo link_to('Bar', 'enginetest/bar') 
     1363     
     1364To make the user interface aware of your engine, add the following to `app.yml`: 
     1365 
     1366    all: 
     1367      pkContextCMS: 
     1368        engines: 
     1369          '': 'Template-Based' 
     1370          enginetest: 'Engine Test' 
     1371   
     1372Substitute 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. 
     1373 
     1374After 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.   
     1375 
     1376Note that engine pages can be moved about the site using the normal drag and drop interface. 
     1377 
     1378You can create your own subnavigation within your engine page. We suggest overriding appropriate portions of your page layout via Symfony slots. 
     1379   
  • plugins/pkContextCMSPlugin/trunk/UPGRADE

    r18236 r20531  
    11Upgrade Notes 
     2 
     32009-07-27: 
     4 
     5ALTER TABLE pk_context_cms_page ADD COLUMN engine VARCHAR(255); 
     6 
     7This statement is necessary to accommodate the new "engine" feature, which greatly 
     8improves the extensibility of the CMS. Symfony modules can now be grafted into the 
     9page tree at any point, using normal actions and templates and otherwise-normal routes 
     10of the pkContextCMSRoute class. See README for details. 
    211 
    3122009-05-10: 
  • plugins/pkContextCMSPlugin/trunk/config/doctrine/schema.yml

    r20351 r20531  
    2828    deleter_id: 
    2929      type: integer(4) 
     30    engine: 
     31      type: string(255) 
    3032  indexes: 
    3133    slugindex: 
  • plugins/pkContextCMSPlugin/trunk/lib/form/pkContextCMSPageSettingsForm.class.php

    r18943 r20531  
    1818      new sfWidgetFormSelect( 
    1919        array('choices' => pkContextCMSTools::getTemplates()))); 
     20      
     21    $this->setWidget( 
     22      'engine', 
     23      new sfWidgetFormSelect( 
     24        array('choices' => pkContextCMSTools::getEngines()))); 
     25 
    2026    // On vs. off makes more sense to end users, but when we first 
    2127    // designed this feature we had an 'archived vs. unarchived' 
     
    7177          'choices' => array_keys(pkContextCMSTools::getTemplates())))); 
    7278 
     79    // Making the empty string one of the choices doesn't seem to be good enough 
     80    // unless we expressly clear 'required' 
     81    $this->setValidator( 
     82      'engine', 
     83      new sfValidatorChoice( 
     84        array('required' => false, 
     85          'choices' => array_keys(pkContextCMSTools::getEngines()))));    
     86 
     87 
    7388    // The slug of the home page cannot change (chicken and egg problems) 
    7489    if ($this->getObject()->getSlug() === '/') 
     
    135150    $slug = preg_match("/^(\/.*?)\/*$/", $slug, $matches); 
    136151    $object->slug = $matches[1]; 
    137  
     152    if (isset($object->engine) && (!strlen($object->engine))) 
     153    { 
     154      // Store it as null for plain ol' executeShow page templating 
     155      $object->engine = null; 
     156    } 
    138157    $this->savePrivileges($object, 'edit', 'editors'); 
    139158    $this->savePrivileges($object, 'manage', 'managers'); 
     159    // Has to be done on shutdown so it comes after the in-memory cache of 
     160    // sfFileCache copies itself back to disk, which otherwise overwrites 
     161    // our attempt to invalidate the routing cache [groan] 
     162    register_shutdown_function(array($this, 'invalidateRoutingCache')); 
     163  } 
     164  public function invalidateRoutingCache() 
     165  { 
     166    // Clear the routing cache on page settings changes. TODO: 
     167    // finesse this to happen only when the engine is changed, 
     168    // and then perhaps further to clear only cache entries 
     169    // relating to this page 
     170    $routing = sfContext::getInstance()->getRouting(); 
     171    if ($routing) 
     172    { 
     173      $cache = $routing->getCache(); 
     174      if ($cache) 
     175      { 
     176        sfContext::getInstance()->getLogger()->info("QZ got cache"); 
     177        $cache->clean(); 
     178        sfContext::getInstance()->getLogger()->info("QZ cleared cache"); 
     179      } 
     180      else 
     181      { 
     182        sfContext::getInstance()->getLogger()->info("QZ no cache"); 
     183      } 
     184    } 
     185    else 
     186    { 
     187      sfContext::getInstance()->getLogger()->info("QZ no routing"); 
     188    } 
    140189  } 
    141190  protected function savePrivileges($object, $privilege, $widgetName) 
     
    149198        $editorIds = array(); 
    150199      } 
     200       
    151201      $object->setAccessesById($privilege, $editorIds); 
    152202    } 
  • plugins/pkContextCMSPlugin/trunk/lib/model/doctrine/PluginpkContextCMSPage.class.php

    r19927 r20531  
    461461  } 
    462462 
    463   public function getUrl(
    464   { 
    465     return pkContextCMSTools::urlForPage($this->getSlug()); 
     463  public function getUrl($absolute = true
     464  { 
     465    return pkContextCMSTools::urlForPage($this->getSlug(), $absolute); 
    466466  } 
    467467 
  • plugins/pkContextCMSPlugin/trunk/lib/model/doctrine/PluginpkContextCMSPageTable.class.php

    r18481 r20531  
    218218    return pkZendSearch::searchLuceneWithScores($this, $query, $culture); 
    219219  } 
     220 
     221  // Returns engine page with the longest matching path. 
     222  // We use a cache so that we don't wind up making separate queries 
     223  // for every engine route in the application 
     224   
     225  protected static $engineCacheUrl = false; 
     226  protected static $engineCachePage = false; 
     227  protected static $engineCacheRemainder = false; 
     228   
     229  static public function getMatchingEnginePage($url, &$remainder) 
     230  { 
     231    if ($url === self::$engineCacheUrl) 
     232    { 
     233      $remainder = self::$engineCacheRemainder; 
     234      return self::$engineCachePage; 
     235    } 
     236    $urls = array(); 
     237    $twig = $url; 
     238    while (true) 
     239    { 
     240      if (($twig === '/') || (!strlen($twig))) 
     241      { 
     242        // Either we've been called for the home page, or we just 
     243        // stripped the first slash and are now considering the home page 
     244        $urls[] = '/'; 
     245        break; 
     246      } 
     247      $urls[] = $twig; 
     248      if (!preg_match('/^(.*)\/[^\/]+$/', $twig, $matches)) 
     249      { 
     250        break; 
     251      } 
     252      $twig = $matches[1]; 
     253    } 
     254    $page = Doctrine_Query::create()-> 
     255      select('p.*, length(p.slug) as len')-> 
     256      from('pkContextCMSPage p')-> 
     257      whereIn('p.slug', $urls)-> 
     258      andWhere('p.engine IS NOT NULL')-> 
     259      orderBy('len desc')-> 
     260      limit(1)-> 
     261      fetchOne(); 
     262    self::$engineCachePage = $page; 
     263    self::$engineCacheUrl = $url; 
     264    self::$engineCacheRemainder = false; 
     265    if ($page) 
     266    { 
     267      $remainder = substr($url, strlen($page->slug)); 
     268      self::$engineCacheRemainder = $remainder; 
     269      return $page; 
     270    } 
     271    return false; 
     272  } 
    220273} 
  • plugins/pkContextCMSPlugin/trunk/lib/pkContextCMSTools.php

    r20387 r20531  
    3939    return $culture; 
    4040  } 
    41   static public function urlForPage($slug
     41  static public function urlForPage($slug, $absolute = true
    4242  { 
    4343    // sfSimpleCMS found a nice workaround for this 
     
    4545    // and not get tripped up by the default routing rule which could 
    4646    // match first if we wrote pkContextCMS/show  
    47     $routed_url = sfContext::getInstance()->getController()->genUrl('@pk_context_cms_page?slug=-PLACEHOLDER-', true); 
     47    $routed_url = sfContext::getInstance()->getController()->genUrl('@pk_context_cms_page?slug=-PLACEHOLDER-', $absolute); 
    4848    $routed_url = str_replace('-PLACEHOLDER-', $slug, $routed_url); 
    4949    // We tend to get double slashes because slugs begin with slashes 
    5050    // and the routing engine wants to helpfully add one too. Fix that, 
    5151    // but don't break http:// 
    52     $routed_url = preg_replace('/([^:])\/\//', '$1/', $routed_url); 
     52    if ($absolute) 
     53    { 
     54      $routed_url = preg_replace('/([^:])\/\//', '$1/', $routed_url); 
     55    } 
     56    else 
     57    { 
     58      $routed_url = preg_replace('/^\/\//', '/', $routed_url); 
     59    } 
    5360    return $routed_url; 
    5461  } 
     
    162169    return $options; 
    163170  } 
     171   
    164172  static public function getTemplates() 
    165173  { 
     
    174182      'home' => 'Home Page')); 
    175183  } 
     184   
     185  static public function getEngines() 
     186  { 
     187    if (sfConfig::get('app_pkContextCMS_get_engines_method')) 
     188    { 
     189      $method = sfConfig::get('app_pkContextCMS_get_engines_method'); 
     190 
     191      return call_user_func($method); 
     192    } 
     193    return sfConfig::get('app_pkContextCMS_engines', array( 
     194      '' => 'Template-Based')); 
     195  } 
     196   
    176197  // Fetch an internationalized option from app.yml. Example: 
    177198  // all: 
     
    298319    return self::$allowSlotEditing; 
    299320  } 
     321   
     322  // Kick the user out to appropriate places if they don't have the proper  
     323  // privileges to be here. pkContextCMS::executeShow and pkContextCMSEngineActions::preExecute 
     324  // both use this  
     325   
     326  static public function validatePageAccess(sfAction $action, $page) 
     327  { 
     328    $action->forward404Unless($page); 
     329    if (!$page->userHasPrivilege('view')) 
     330    { 
     331      // forward rather than login because referrers don't always 
     332      // work. Hopefully the login action will capture the original 
     333      // URI to bring the user back here afterwards. 
     334 
     335      if ($action->getUser()->isAuthenticated()) 
     336      { 
     337        return $action->forward(sfConfig::get('sf_secure_module'), sfConfig::get('sf_secure_action')); 
     338      } 
     339      else 
     340      { 
     341        return $action->forward(sfConfig::get('sf_login_module'), sfConfig::get('sf_login_action')); 
     342 
     343      } 
     344    } 
     345    if ($page->archived && (!$page->userHasPrivilege('edit|manage'))) 
     346    { 
     347      $action->forward404(); 
     348    }     
     349  } 
     350 
     351  // Establish the page title, set the layout, and add the javascripts that are 
     352  // necessary to manage pages. pkContextCMS::executeShow and pkContextCMSEngineActions::preExecute 
     353  // both use this 
     354   
     355  static public function setPageEnvironment(sfAction $action, pkContextCMSPage $page) 
     356  { 
     357    // Title is pre-escaped as valid HTML 
     358    $prefix = pkContextCMSTools::getOptionI18n('title_prefix'); 
     359    $action->getResponse()->setTitle($prefix . $page->getTitle(), false); 
     360    // Necessary to allow the use of 
     361    // pkContextCMSTools::getCurrentPage() in the layout. 
     362    // In Symfony 1.1+, you can't see $action->page from 
     363    // the layout. 
     364    pkContextCMSTools::setCurrentPage($page); 
     365    // Borrowed from sfSimpleCMS 
     366    if(sfConfig::get('app_pkContextCMS_use_bundled_layout', true)) 
     367    { 
     368      $action->setLayout(sfContext::getInstance()->getConfiguration()->getTemplateDir('pkContextCMS', 'layout.php').'/layout'); 
     369    } 
     370 
     371    //JB 6.8.09 These are both necessary 100% of the time, so I added them here at this level. 
     372    $action->getResponse()->addJavascript('/pkToolkitPlugin/js/pkUI.js'); 
     373    $action->getResponse()->addJavascript('/pkToolkitPlugin/js/pkControls.js'); 
     374    $action->getResponse()->addJavascript('/pkToolkitPlugin/js/jquery.hotkeys-0.7.9.min.js'); // this is plugin for hotkey toggle for cms UI 
     375  } 
    300376} 
  • plugins/pkContextCMSPlugin/trunk/modules/pkContextCMS/lib/BasepkContextCMSActions.class.php

    r20190 r20531  
    2828    } 
    2929    $page = pkContextCMSPageTable::retrieveBySlugWithSlots($slug); 
    30     $this->forward404Unless($page); 
    31     if (!$page->userHasPrivilege('view')) 
    32     { 
    33       // forward rather than login because referrers don't always 
    34       // work. Hopefully the login action will capture the original 
    35       // URI to bring the user back here afterwards. 
    36  
    37       if ($this->getUser()->isAuthenticated()) 
    38       { 
    39         return $this->forward(sfConfig::get('sf_secure_module'), sfConfig::get('sf_secure_action')); 
    40       } 
    41       else 
    42       { 
    43         return $this->forward(sfConfig::get('sf_login_module'), sfConfig::get('sf_login_action')); 
    44  
    45       } 
    46     } 
    47     if ($page->archived && (!$page->userHasPrivilege('edit|manage'))) 
    48     { 
    49       $this->forward404(); 
    50     } 
    51     // Title is pre-escaped as valid HTML 
    52     $prefix = pkContextCMSTools::getOptionI18n('title_prefix'); 
    53     $this->getResponse()->setTitle($prefix . $page->getTitle(), false); 
     30    pkContextCMSTools::validatePageAccess($this, $page); 
     31    pkContextCMSTools::setPageEnvironment($this, $page); 
    5432    $this->page = $page; 
    55     // Necessary to allow the use of 
    56     // pkContextCMSTools::getCurrentPage() in the layout. 
    57     // In Symfony 1.1+, you can't see $this->page from 
    58     // the layout. 
    59     pkContextCMSTools::setCurrentPage($page); 
    6033    $this->setTemplate($page->template); 
    61     // Borrowed from sfSimpleCMS 
    62     if(sfConfig::get('app_pkContextCMS_use_bundled_layout', true)) 
    63     { 
    64       $this->setLayout(sfContext::getInstance()->getConfiguration()->getTemplateDir('pkContextCMS', 'layout.php').'/layout'); 
    65     } 
    66  
    67     //JB 6.8.09 These are both necessary 100% of the time, so I added them here at this level. 
    68     $this->getResponse()->addJavascript('/pkToolkitPlugin/js/pkUI.js'); 
    69     $this->getResponse()->addJavascript('/pkToolkitPlugin/js/pkControls.js'); 
    70     $this->getResponse()->addJavascript('/pkToolkitPlugin/js/jquery.hotkeys-0.7.9.min.js'); // this is plugin for hotkey toggle for cms UI 
    7134 
    7235    return 'Template'; 
    7336  } 
    7437 
     38 
     39   
    7540  // Note that these fetch based on the id or slug found in the 
    7641  // named parameter of the request 
     
    385350    } 
    386351    $this->page = $this->retrievePageForEditingById($from); 
     352     
    387353    $this->form = new pkContextCMSPageSettingsForm($this->page); 
    388354    if ($from === 'settings[id]') 
  • plugins/pkContextCMSPlugin/trunk/modules/pkContextCMS/templates/settingsSuccess.php

    r19927 r20531  
    3131      <?php endif ?> 
    3232      <div class="pk-form-row"> 
     33        <label>Page Engine</label> 
     34        <?php echo $form['engine']->render(array('onClick' => 'pkUpdateEngineAndTemplate()')) ?> 
     35        <?php echo $form['engine']->renderError() ?> 
     36      </div> 
     37      <div class="pk-form-row" id="pk-page-template"> 
    3338        <label>Page Template</label> 
    3439        <?php echo $form['template'] ?> 
     
    120125<?php endif ?> 
    121126  <script> 
     127  function pkUpdateEngineAndTemplate() 
     128  { 
     129    if (!$('#settings_engine').val().length) 
     130    { 
     131      $('#settings_template').show(); 
     132    } 
     133    else 
     134    { 
     135      $('#settings_template').hide(); 
     136    } 
     137  } 
     138  pkUpdateEngineAndTemplate(); 
    122139  <?php // you can do this: { remove: 'custom html for remove button' } ?> 
    123140  pkMultipleSelect('#pk-page-settings', { });