Changeset 20531
- Timestamp:
- 07/27/09 21:18:24 (4 years ago)
- Files:
-
- plugins/pkContextCMSPlugin/trunk/README (modified) (1 diff)
- plugins/pkContextCMSPlugin/trunk/UPGRADE (modified) (1 diff)
- plugins/pkContextCMSPlugin/trunk/config/doctrine/schema.yml (modified) (1 diff)
- plugins/pkContextCMSPlugin/trunk/lib/form/pkContextCMSPageSettingsForm.class.php (modified) (4 diffs)
- plugins/pkContextCMSPlugin/trunk/lib/model/doctrine/PluginpkContextCMSPage.class.php (modified) (1 diff)
- plugins/pkContextCMSPlugin/trunk/lib/model/doctrine/PluginpkContextCMSPageTable.class.php (modified) (1 diff)
- plugins/pkContextCMSPlugin/trunk/lib/pkContextCMSEngineActions.class.php (added)
- plugins/pkContextCMSPlugin/trunk/lib/pkContextCMSRoute.php (added)
- plugins/pkContextCMSPlugin/trunk/lib/pkContextCMSTools.php (modified) (5 diffs)
- plugins/pkContextCMSPlugin/trunk/modules/pkContextCMS/lib/BasepkContextCMSActions.class.php (modified) (2 diffs)
- plugins/pkContextCMSPlugin/trunk/modules/pkContextCMS/templates/settingsSuccess.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
plugins/pkContextCMSPlugin/trunk/README
r19592 r20531 1291 1291 *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. 1292 1292 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 1295 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. 1296 1297 A single engine may be grafted into the site in multiple locations. 1298 1299 Engine modules are written using normal actions and templates and otherwise-normal routes of the pkContextCMSRoute class. 1300 1301 This 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 1303 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. 1304 1305 To 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 1307 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 `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 1331 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: 1332 1333 enginetest_action: 1334 url: /:action 1335 param: { module: enginetest } 1336 class: pkContextCMSRoute 1337 1338 In general, you may use all of the usual features available to Symfony routes. 1339 1340 Note 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 1342 That is, if an engine page is located here: 1343 1344 /test1 1345 1346 And the user requests the following URL: 1347 1348 /test1/foo 1349 1350 The `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 1354 To the appropriate rule. 1355 1356 As 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 1358 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. 1359 1360 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: 1361 1362 echo link_to('Bar', 'enginetest/bar') 1363 1364 To 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 1372 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. 1373 1374 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. 1375 1376 Note that engine pages can be moved about the site using the normal drag and drop interface. 1377 1378 You 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 1 1 Upgrade Notes 2 3 2009-07-27: 4 5 ALTER TABLE pk_context_cms_page ADD COLUMN engine VARCHAR(255); 6 7 This statement is necessary to accommodate the new "engine" feature, which greatly 8 improves the extensibility of the CMS. Symfony modules can now be grafted into the 9 page tree at any point, using normal actions and templates and otherwise-normal routes 10 of the pkContextCMSRoute class. See README for details. 2 11 3 12 2009-05-10: plugins/pkContextCMSPlugin/trunk/config/doctrine/schema.yml
r20351 r20531 28 28 deleter_id: 29 29 type: integer(4) 30 engine: 31 type: string(255) 30 32 indexes: 31 33 slugindex: plugins/pkContextCMSPlugin/trunk/lib/form/pkContextCMSPageSettingsForm.class.php
r18943 r20531 18 18 new sfWidgetFormSelect( 19 19 array('choices' => pkContextCMSTools::getTemplates()))); 20 21 $this->setWidget( 22 'engine', 23 new sfWidgetFormSelect( 24 array('choices' => pkContextCMSTools::getEngines()))); 25 20 26 // On vs. off makes more sense to end users, but when we first 21 27 // designed this feature we had an 'archived vs. unarchived' … … 71 77 'choices' => array_keys(pkContextCMSTools::getTemplates())))); 72 78 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 73 88 // The slug of the home page cannot change (chicken and egg problems) 74 89 if ($this->getObject()->getSlug() === '/') … … 135 150 $slug = preg_match("/^(\/.*?)\/*$/", $slug, $matches); 136 151 $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 } 138 157 $this->savePrivileges($object, 'edit', 'editors'); 139 158 $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 } 140 189 } 141 190 protected function savePrivileges($object, $privilege, $widgetName) … … 149 198 $editorIds = array(); 150 199 } 200 151 201 $object->setAccessesById($privilege, $editorIds); 152 202 } plugins/pkContextCMSPlugin/trunk/lib/model/doctrine/PluginpkContextCMSPage.class.php
r19927 r20531 461 461 } 462 462 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); 466 466 } 467 467 plugins/pkContextCMSPlugin/trunk/lib/model/doctrine/PluginpkContextCMSPageTable.class.php
r18481 r20531 218 218 return pkZendSearch::searchLuceneWithScores($this, $query, $culture); 219 219 } 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 } 220 273 } plugins/pkContextCMSPlugin/trunk/lib/pkContextCMSTools.php
r20387 r20531 39 39 return $culture; 40 40 } 41 static public function urlForPage($slug )41 static public function urlForPage($slug, $absolute = true) 42 42 { 43 43 // sfSimpleCMS found a nice workaround for this … … 45 45 // and not get tripped up by the default routing rule which could 46 46 // 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); 48 48 $routed_url = str_replace('-PLACEHOLDER-', $slug, $routed_url); 49 49 // We tend to get double slashes because slugs begin with slashes 50 50 // and the routing engine wants to helpfully add one too. Fix that, 51 51 // 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 } 53 60 return $routed_url; 54 61 } … … 162 169 return $options; 163 170 } 171 164 172 static public function getTemplates() 165 173 { … … 174 182 'home' => 'Home Page')); 175 183 } 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 176 197 // Fetch an internationalized option from app.yml. Example: 177 198 // all: … … 298 319 return self::$allowSlotEditing; 299 320 } 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 } 300 376 } plugins/pkContextCMSPlugin/trunk/modules/pkContextCMS/lib/BasepkContextCMSActions.class.php
r20190 r20531 28 28 } 29 29 $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); 54 32 $this->page = $page; 55 // Necessary to allow the use of56 // pkContextCMSTools::getCurrentPage() in the layout.57 // In Symfony 1.1+, you can't see $this->page from58 // the layout.59 pkContextCMSTools::setCurrentPage($page);60 33 $this->setTemplate($page->template); 61 // Borrowed from sfSimpleCMS62 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 UI71 34 72 35 return 'Template'; 73 36 } 74 37 38 39 75 40 // Note that these fetch based on the id or slug found in the 76 41 // named parameter of the request … … 385 350 } 386 351 $this->page = $this->retrievePageForEditingById($from); 352 387 353 $this->form = new pkContextCMSPageSettingsForm($this->page); 388 354 if ($from === 'settings[id]') plugins/pkContextCMSPlugin/trunk/modules/pkContextCMS/templates/settingsSuccess.php
r19927 r20531 31 31 <?php endif ?> 32 32 <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"> 33 38 <label>Page Template</label> 34 39 <?php echo $form['template'] ?> … … 120 125 <?php endif ?> 121 126 <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(); 122 139 <?php // you can do this: { remove: 'custom html for remove button' } ?> 123 140 pkMultipleSelect('#pk-page-settings', { });