| 1 | <?php |
|---|
| 2 | |
|---|
| 3 | class BaseaTools |
|---|
| 4 | { |
|---|
| 5 | // ALL static variables must go here |
|---|
| 6 | |
|---|
| 7 | // We need a separate flag so that even a non-CMS page can |
|---|
| 8 | // restore its state (i.e. set the page back to null) |
|---|
| 9 | static protected $global = false; |
|---|
| 10 | // We now allow fetching of slots from multiple pages, which can be |
|---|
| 11 | // normal pages or outside-of-navigation pages like 'global' that are used |
|---|
| 12 | // solely for this purpose. This allows efficient fetching of only slots that are |
|---|
| 13 | // relevant to your needs, rather than fetching all 'global' slots at once |
|---|
| 14 | static protected $globalCache = array(); |
|---|
| 15 | static protected $currentPage = null; |
|---|
| 16 | static protected $pageStack = array(); |
|---|
| 17 | static protected $globalButtons = false; |
|---|
| 18 | static protected $allowSlotEditing = true; |
|---|
| 19 | static protected $realUrl = null; |
|---|
| 20 | static public $jsCalls = array(); |
|---|
| 21 | |
|---|
| 22 | // Must reset ALL static variables to their initial state |
|---|
| 23 | static public function listenToSimulateNewRequestEvent(sfEvent $event) |
|---|
| 24 | { |
|---|
| 25 | aTools::$global = false; |
|---|
| 26 | aTools::$globalCache = false; |
|---|
| 27 | aTools::$currentPage = null; |
|---|
| 28 | aTools::$pageStack = array(); |
|---|
| 29 | aTools::$globalButtons = false; |
|---|
| 30 | aTools::$allowSlotEditing = true; |
|---|
| 31 | aTools::$realUrl = null; |
|---|
| 32 | aTools::$jsCalls = array(); |
|---|
| 33 | aNavigation::simulateNewRequest(); |
|---|
| 34 | } |
|---|
| 35 | |
|---|
| 36 | static public function cultureOrDefault($culture = false) |
|---|
| 37 | { |
|---|
| 38 | if ($culture) |
|---|
| 39 | { |
|---|
| 40 | return $culture; |
|---|
| 41 | } |
|---|
| 42 | return aTools::getUserCulture(); |
|---|
| 43 | } |
|---|
| 44 | static public function getUserCulture($user = false) |
|---|
| 45 | { |
|---|
| 46 | if ($user == false) |
|---|
| 47 | { |
|---|
| 48 | $culture = false; |
|---|
| 49 | try |
|---|
| 50 | { |
|---|
| 51 | $context = sfContext::getInstance(); |
|---|
| 52 | } catch (Exception $e) |
|---|
| 53 | { |
|---|
| 54 | // Not present in tasks |
|---|
| 55 | $context = false; |
|---|
| 56 | } |
|---|
| 57 | if ($context) |
|---|
| 58 | { |
|---|
| 59 | $user = sfContext::getInstance()->getUser(); |
|---|
| 60 | } |
|---|
| 61 | } |
|---|
| 62 | if ($user) |
|---|
| 63 | { |
|---|
| 64 | $culture = $user->getCulture(); |
|---|
| 65 | } |
|---|
| 66 | if (!$culture) |
|---|
| 67 | { |
|---|
| 68 | $culture = sfConfig::get('sf_default_culture', 'en'); |
|---|
| 69 | } |
|---|
| 70 | return $culture; |
|---|
| 71 | } |
|---|
| 72 | static public function urlForPage($slug, $absolute = true) |
|---|
| 73 | { |
|---|
| 74 | // sfSimpleCMS found a nice workaround for this |
|---|
| 75 | // By using @a_page we can skip to a shorter URL form |
|---|
| 76 | // and not get tripped up by the default routing rule which could |
|---|
| 77 | // match first if we wrote a/show |
|---|
| 78 | $routed_url = sfContext::getInstance()->getController()->genUrl('@a_page?slug=-PLACEHOLDER-', $absolute); |
|---|
| 79 | $routed_url = str_replace('-PLACEHOLDER-', $slug, $routed_url); |
|---|
| 80 | // We tend to get double slashes because slugs begin with slashes |
|---|
| 81 | // and the routing engine wants to helpfully add one too. Fix that, |
|---|
| 82 | // but don't break http:// |
|---|
| 83 | $matches = array(); |
|---|
| 84 | // This is good both for dev controllers and for absolute URLs |
|---|
| 85 | $routed_url = preg_replace('/([^:])\/\//', '$1/', $routed_url); |
|---|
| 86 | // For non-absolute URLs without a controller |
|---|
| 87 | if (!$absolute) |
|---|
| 88 | { |
|---|
| 89 | $routed_url = preg_replace('/^\/\//', '/', $routed_url); |
|---|
| 90 | } |
|---|
| 91 | return $routed_url; |
|---|
| 92 | } |
|---|
| 93 | |
|---|
| 94 | static public function setCurrentPage($page) |
|---|
| 95 | { |
|---|
| 96 | aTools::$currentPage = $page; |
|---|
| 97 | } |
|---|
| 98 | |
|---|
| 99 | static public function getCurrentPage() |
|---|
| 100 | { |
|---|
| 101 | return aTools::$currentPage; |
|---|
| 102 | } |
|---|
| 103 | |
|---|
| 104 | // Similar to getCurrentPage, but returns null if the current page is an admin page, |
|---|
| 105 | // and therefore not suitable for normal navigation like the breadcrumb and subnav |
|---|
| 106 | static public function getCurrentNonAdminPage() |
|---|
| 107 | { |
|---|
| 108 | $page = aTools::getCurrentPage(); |
|---|
| 109 | return $page ? ($page->admin ? null : $page) : null; |
|---|
| 110 | } |
|---|
| 111 | |
|---|
| 112 | /** |
|---|
| 113 | * We've fetched a page on our own using aPageTable::queryWithSlots and we want |
|---|
| 114 | * to make Apostrophe aware of it so that areas on the current page that live on |
|---|
| 115 | * that virtual page don't generate a superfluous second query |
|---|
| 116 | * |
|---|
| 117 | * @param array, Doctrine_Collection, aPage $pages |
|---|
| 118 | */ |
|---|
| 119 | static public function cacheVirtualPages($pages) |
|---|
| 120 | { |
|---|
| 121 | if(get_class($pages) == 'Doctrine_Collection' || is_array($pages)) |
|---|
| 122 | { |
|---|
| 123 | foreach($pages as $page) |
|---|
| 124 | { |
|---|
| 125 | aTools::$globalCache[$page['slug']] = $page; |
|---|
| 126 | } |
|---|
| 127 | } |
|---|
| 128 | else |
|---|
| 129 | { |
|---|
| 130 | aTools::$globalCache[$pages['slug']] = $pages; |
|---|
| 131 | } |
|---|
| 132 | } |
|---|
| 133 | |
|---|
| 134 | static public function globalSetup($options) |
|---|
| 135 | { |
|---|
| 136 | if (isset($options['global']) && $options['global']) |
|---|
| 137 | { |
|---|
| 138 | if (!isset($options['slug'])) |
|---|
| 139 | { |
|---|
| 140 | $options['slug'] = 'global'; |
|---|
| 141 | } |
|---|
| 142 | } |
|---|
| 143 | if (isset($options['slug'])) |
|---|
| 144 | { |
|---|
| 145 | // Note that we push onto the stack even if the page specified is the same page |
|---|
| 146 | // we're looking at. This doesn't hurt because of caching, and it allows us |
|---|
| 147 | // to keep the stack count properly |
|---|
| 148 | $slug = $options['slug']; |
|---|
| 149 | aTools::$pageStack[] = aTools::getCurrentPage(); |
|---|
| 150 | // Caching the global page speeds up pages with two or more global slots |
|---|
| 151 | if (isset(aTools::$globalCache[$slug])) |
|---|
| 152 | { |
|---|
| 153 | $global = aTools::$globalCache[$slug]; |
|---|
| 154 | } |
|---|
| 155 | else |
|---|
| 156 | { |
|---|
| 157 | $global = aPageTable::retrieveBySlugWithSlots($slug); |
|---|
| 158 | if (!$global) |
|---|
| 159 | { |
|---|
| 160 | $global = new aPage(); |
|---|
| 161 | $global->slug = $slug; |
|---|
| 162 | $global->save(); |
|---|
| 163 | } |
|---|
| 164 | aTools::$globalCache[$slug] = $global; |
|---|
| 165 | } |
|---|
| 166 | aTools::setCurrentPage($global); |
|---|
| 167 | aTools::$global = true; |
|---|
| 168 | } |
|---|
| 169 | } |
|---|
| 170 | |
|---|
| 171 | static public function globalShutdown() |
|---|
| 172 | { |
|---|
| 173 | if (aTools::$global) |
|---|
| 174 | { |
|---|
| 175 | aTools::setCurrentPage(array_pop(aTools::$pageStack)); |
|---|
| 176 | aTools::$global = (count(aTools::$pageStack)); |
|---|
| 177 | } |
|---|
| 178 | } |
|---|
| 179 | |
|---|
| 180 | static public function getSlotOptionsGroup($groupName) |
|---|
| 181 | { |
|---|
| 182 | $optionGroups = sfConfig::get('app_a_slot_option_groups', |
|---|
| 183 | array()); |
|---|
| 184 | if (isset($optionGroups[$groupName])) |
|---|
| 185 | { |
|---|
| 186 | return $optionGroups[$groupName]; |
|---|
| 187 | } |
|---|
| 188 | throw new sfException("Option group $groupName is not defined in app.yml"); |
|---|
| 189 | } |
|---|
| 190 | |
|---|
| 191 | // Oops: we can't cache this list because it's different for various areas on the same page. |
|---|
| 192 | |
|---|
| 193 | static public function getSlotTypesInfo($options) |
|---|
| 194 | { |
|---|
| 195 | $instance = sfContext::getInstance(); |
|---|
| 196 | $slotTypes = array_merge( |
|---|
| 197 | array( |
|---|
| 198 | 'aText' => 'Plain Text', |
|---|
| 199 | 'aRichText' => 'Rich Text', |
|---|
| 200 | 'aFeed' => 'RSS Feed', |
|---|
| 201 | 'aImage' => 'Image', |
|---|
| 202 | 'aSlideshow' => 'Slideshow', |
|---|
| 203 | 'aButton' => 'Button', |
|---|
| 204 | 'aAudio' => 'Audio', |
|---|
| 205 | 'aVideo' => 'Video', |
|---|
| 206 | 'aFile' => 'File', |
|---|
| 207 | 'aRawHTML' => 'Raw HTML'), |
|---|
| 208 | sfConfig::get('app_a_slot_types', array())); |
|---|
| 209 | if (isset($options['allowed_types'])) |
|---|
| 210 | { |
|---|
| 211 | $newSlotTypes = array(); |
|---|
| 212 | foreach($options['allowed_types'] as $type) |
|---|
| 213 | { |
|---|
| 214 | if (isset($slotTypes[$type])) |
|---|
| 215 | { |
|---|
| 216 | $newSlotTypes[$type] = $slotTypes[$type]; |
|---|
| 217 | } |
|---|
| 218 | } |
|---|
| 219 | $slotTypes = $newSlotTypes; |
|---|
| 220 | } |
|---|
| 221 | $info = array(); |
|---|
| 222 | |
|---|
| 223 | foreach ($slotTypes as $type => $label) |
|---|
| 224 | { |
|---|
| 225 | $info[$type]['label'] = $label; |
|---|
| 226 | // We COULD cache this. Would it pay to do so? |
|---|
| 227 | $info[$type]['class'] = strtolower(preg_replace('/^a(\w)/', 'a-$1', $type)); |
|---|
| 228 | } |
|---|
| 229 | return $info; |
|---|
| 230 | } |
|---|
| 231 | |
|---|
| 232 | // Includes classes for buttons for adding each slot type |
|---|
| 233 | static public function getSlotTypeOptionsAndClasses($options) |
|---|
| 234 | { |
|---|
| 235 | |
|---|
| 236 | } |
|---|
| 237 | |
|---|
| 238 | static public function getOption($array, $name, $default) |
|---|
| 239 | { |
|---|
| 240 | if (isset($array[$name])) |
|---|
| 241 | { |
|---|
| 242 | return $array[$name]; |
|---|
| 243 | } |
|---|
| 244 | return $default; |
|---|
| 245 | } |
|---|
| 246 | static public function getRealPage() |
|---|
| 247 | { |
|---|
| 248 | if (count(aTools::$pageStack)) |
|---|
| 249 | { |
|---|
| 250 | $page = aTools::$pageStack[0]; |
|---|
| 251 | if ($page) |
|---|
| 252 | { |
|---|
| 253 | return $page; |
|---|
| 254 | } |
|---|
| 255 | else |
|---|
| 256 | { |
|---|
| 257 | return false; |
|---|
| 258 | } |
|---|
| 259 | } |
|---|
| 260 | elseif (aTools::$currentPage) |
|---|
| 261 | { |
|---|
| 262 | return aTools::$currentPage; |
|---|
| 263 | } |
|---|
| 264 | else |
|---|
| 265 | { |
|---|
| 266 | return false; |
|---|
| 267 | } |
|---|
| 268 | } |
|---|
| 269 | // Fetch options array saved in session |
|---|
| 270 | static public function getAreaOptions($pageid, $name) |
|---|
| 271 | { |
|---|
| 272 | $lookingFor = "area-options-$pageid-$name"; |
|---|
| 273 | $options = array(); |
|---|
| 274 | $user = sfContext::getInstance()->getUser(); |
|---|
| 275 | if ($user->hasAttribute($lookingFor, 'apostrophe')) |
|---|
| 276 | { |
|---|
| 277 | $options = $user->getAttribute( |
|---|
| 278 | $lookingFor, false, 'apostrophe'); |
|---|
| 279 | } |
|---|
| 280 | return $options; |
|---|
| 281 | } |
|---|
| 282 | |
|---|
| 283 | static public function getTemplates() |
|---|
| 284 | { |
|---|
| 285 | if (sfConfig::get('app_a_get_templates_method')) |
|---|
| 286 | { |
|---|
| 287 | $method = sfConfig::get('app_a_get_templates_method'); |
|---|
| 288 | |
|---|
| 289 | return call_user_func($method); |
|---|
| 290 | } |
|---|
| 291 | return sfConfig::get('app_a_templates', array( |
|---|
| 292 | 'default' => 'Default Page', |
|---|
| 293 | 'home' => 'Home Page')); |
|---|
| 294 | } |
|---|
| 295 | |
|---|
| 296 | static public function getEngines() |
|---|
| 297 | { |
|---|
| 298 | if (sfConfig::get('app_a_get_engines_method')) |
|---|
| 299 | { |
|---|
| 300 | $method = sfConfig::get('app_a_get_engines_method'); |
|---|
| 301 | |
|---|
| 302 | return call_user_func($method); |
|---|
| 303 | } |
|---|
| 304 | return sfConfig::get('app_a_engines', array( |
|---|
| 305 | '' => 'Template-Based')); |
|---|
| 306 | } |
|---|
| 307 | |
|---|
| 308 | // Fetch an internationalized option from app.yml. Example: |
|---|
| 309 | // all: |
|---|
| 310 | // a: |
|---|
| 311 | |
|---|
| 312 | static public function getOptionI18n($option, $default = false, $culture = false) |
|---|
| 313 | { |
|---|
| 314 | $culture = aTools::cultureOrDefault($culture); |
|---|
| 315 | $values = sfConfig::get("app_a_$option", array()); |
|---|
| 316 | if (!is_array($values)) |
|---|
| 317 | { |
|---|
| 318 | // Convenience for single-language sites |
|---|
| 319 | return $values; |
|---|
| 320 | } |
|---|
| 321 | if (isset($values[$culture])) |
|---|
| 322 | { |
|---|
| 323 | return $values[$culture]; |
|---|
| 324 | } |
|---|
| 325 | return $default; |
|---|
| 326 | } |
|---|
| 327 | |
|---|
| 328 | static public function getGlobalButtonsInternal(sfEvent $event) |
|---|
| 329 | { |
|---|
| 330 | // If we needed a context object we could get it from $event->getSubject(), |
|---|
| 331 | // but this is a simple static thing |
|---|
| 332 | |
|---|
| 333 | // Add the users button only if the user has the admin credential. |
|---|
| 334 | // This is typically only given to admins and superadmins. |
|---|
| 335 | // TODO: there is also the cms_admin credential, should I differentiate here? |
|---|
| 336 | $user = sfContext::getInstance()->getUser(); |
|---|
| 337 | if ($user->hasCredential('admin')) |
|---|
| 338 | { |
|---|
| 339 | $extraAdminButtons = sfConfig::get('app_a_extra_admin_buttons', |
|---|
| 340 | array('users' => array('label' => 'Users', 'action' => 'aUserAdmin/index', 'class' => 'a-users'), |
|---|
| 341 | 'categories' => array('label' => 'Categories & Tags', 'action' => 'aCategoryAdmin/index', 'class' => 'a-categories'), |
|---|
| 342 | 'reorganize' => array('label' => 'Reorganize', 'action' => 'a/reorganize', 'class' => 'a-reorganize') |
|---|
| 343 | )); |
|---|
| 344 | // Eventually this one too. Reorganize will probably get moved into it |
|---|
| 345 | // ('Settings', 'a/globalSettings', 'a-settings') |
|---|
| 346 | |
|---|
| 347 | if (is_array($extraAdminButtons)) |
|---|
| 348 | { |
|---|
| 349 | foreach ($extraAdminButtons as $name => $data) |
|---|
| 350 | { |
|---|
| 351 | aTools::addGlobalButtons(array(new aGlobalButton( |
|---|
| 352 | $name, $data['label'], $data['action'], isset($data['class']) ? $data['class'] : ''))); |
|---|
| 353 | } |
|---|
| 354 | } |
|---|
| 355 | } |
|---|
| 356 | } |
|---|
| 357 | |
|---|
| 358 | // To be called only in response to a a.getGlobalButtons event |
|---|
| 359 | static public function addGlobalButtons($array) |
|---|
| 360 | { |
|---|
| 361 | aTools::$globalButtons = array_merge(aTools::$globalButtons, $array); |
|---|
| 362 | } |
|---|
| 363 | |
|---|
| 364 | static public function getGlobalButtons() |
|---|
| 365 | { |
|---|
| 366 | if (aTools::$globalButtons !== false) |
|---|
| 367 | { |
|---|
| 368 | return aTools::$globalButtons; |
|---|
| 369 | } |
|---|
| 370 | $buttonsOrder = sfConfig::get('app_a_global_button_order', false); |
|---|
| 371 | aTools::$globalButtons = array(); |
|---|
| 372 | // We could pass parameters here but it's a simple static thing in this case |
|---|
| 373 | // so the recipients just call back to addGlobalButtons |
|---|
| 374 | sfContext::getInstance()->getEventDispatcher()->notify(new sfEvent(null, 'a.getGlobalButtons', array())); |
|---|
| 375 | |
|---|
| 376 | $buttonsByName = array(); |
|---|
| 377 | foreach (aTools::$globalButtons as $button) |
|---|
| 378 | { |
|---|
| 379 | $buttonsByName[$button->getName()] = $button; |
|---|
| 380 | } |
|---|
| 381 | if ($buttonsOrder === false) |
|---|
| 382 | { |
|---|
| 383 | ksort($buttonsByName); |
|---|
| 384 | $orderedButtons = array_values($buttonsByName); |
|---|
| 385 | } |
|---|
| 386 | else |
|---|
| 387 | { |
|---|
| 388 | $orderedButtons = array(); |
|---|
| 389 | foreach ($buttonsOrder as $name) |
|---|
| 390 | { |
|---|
| 391 | if (isset($buttonsByName[$name])) |
|---|
| 392 | { |
|---|
| 393 | $orderedButtons[] = $buttonsByName[$name]; |
|---|
| 394 | } |
|---|
| 395 | } |
|---|
| 396 | } |
|---|
| 397 | |
|---|
| 398 | aTools::$globalButtons = $orderedButtons; |
|---|
| 399 | return $orderedButtons; |
|---|
| 400 | } |
|---|
| 401 | |
|---|
| 402 | static public function globalToolsPrivilege() |
|---|
| 403 | { |
|---|
| 404 | // if you can edit the page, there are tools for you in the apostrophe |
|---|
| 405 | if (aTools::getCurrentPage() && aTools::getCurrentPage()->userHasPrivilege('edit')) |
|---|
| 406 | { |
|---|
| 407 | return true; |
|---|
| 408 | } |
|---|
| 409 | // if you are the site admin, there are ALWAYS tools for you in the apostrophe |
|---|
| 410 | $user = sfContext::getInstance()->getUser(); |
|---|
| 411 | return $user->hasCredential('cms_admin'); |
|---|
| 412 | } |
|---|
| 413 | |
|---|
| 414 | // These methods allow slot editing to be turned off even for people with |
|---|
| 415 | // full and appropriate privileges. |
|---|
| 416 | |
|---|
| 417 | // Most of the time being able to edit a global slot on a non-CMS page is a |
|---|
| 418 | // good thing, especially if that's the only place the global slot appears. |
|---|
| 419 | // But sometimes, as in the case where you're editing other types of data, |
|---|
| 420 | // it's just a source of confusion to have those buttons displayed. |
|---|
| 421 | |
|---|
| 422 | // (Suppressing editing of slots on normal CMS pages is of course a bad idea, |
|---|
| 423 | // because how else would you ever edit them?) |
|---|
| 424 | |
|---|
| 425 | static public function setAllowSlotEditing($value) |
|---|
| 426 | { |
|---|
| 427 | aTools::$allowSlotEditing = $value; |
|---|
| 428 | } |
|---|
| 429 | static public function getAllowSlotEditing() |
|---|
| 430 | { |
|---|
| 431 | return aTools::$allowSlotEditing; |
|---|
| 432 | } |
|---|
| 433 | |
|---|
| 434 | // Kick the user out to appropriate places if they don't have the proper |
|---|
| 435 | // privileges to be here. a::executeShow and aEngineActions::preExecute |
|---|
| 436 | // both use this |
|---|
| 437 | |
|---|
| 438 | static public function validatePageAccess(sfAction $action, $page) |
|---|
| 439 | { |
|---|
| 440 | $action->forward404Unless($page); |
|---|
| 441 | if (!$page->userHasPrivilege('view')) |
|---|
| 442 | { |
|---|
| 443 | // forward rather than login because referrers don't always |
|---|
| 444 | // work. Hopefully the login action will capture the original |
|---|
| 445 | // URI to bring the user back here afterwards. |
|---|
| 446 | |
|---|
| 447 | if ($action->getUser()->isAuthenticated()) |
|---|
| 448 | { |
|---|
| 449 | return $action->forward(sfConfig::get('sf_secure_module'), sfConfig::get('sf_secure_action')); |
|---|
| 450 | } |
|---|
| 451 | else |
|---|
| 452 | { |
|---|
| 453 | return $action->forward(sfConfig::get('sf_login_module'), sfConfig::get('sf_login_action')); |
|---|
| 454 | |
|---|
| 455 | } |
|---|
| 456 | } |
|---|
| 457 | if ($page->archived && (!$page->userHasPrivilege('edit'))) |
|---|
| 458 | { |
|---|
| 459 | $action->forward404(); |
|---|
| 460 | } |
|---|
| 461 | } |
|---|
| 462 | |
|---|
| 463 | // Establish the page title, set the layout, and add the javascripts that are |
|---|
| 464 | // necessary to manage pages. a::executeShow and aEngineActions::preExecute |
|---|
| 465 | // both use this. TODO: is this redundant now that aHelper does it? |
|---|
| 466 | |
|---|
| 467 | static public function setPageEnvironment(sfAction $action, aPage $page) |
|---|
| 468 | { |
|---|
| 469 | // Title is pre-escaped as valid HTML |
|---|
| 470 | $prefix = aTools::getOptionI18n('title_prefix'); |
|---|
| 471 | $suffix = aTools::getOptionI18n('title_suffix'); |
|---|
| 472 | $action->getResponse()->setTitle($prefix . $page->getTitle() . $suffix, false); |
|---|
| 473 | // Necessary to allow the use of |
|---|
| 474 | // aTools::getCurrentPage() in the layout. |
|---|
| 475 | // In Symfony 1.1+, you can't see $action->page from |
|---|
| 476 | // the layout. |
|---|
| 477 | aTools::setCurrentPage($page); |
|---|
| 478 | // Borrowed from sfSimpleCMS |
|---|
| 479 | if(sfConfig::get('app_a_use_bundled_layout', true)) |
|---|
| 480 | { |
|---|
| 481 | $action->setLayout(sfContext::getInstance()->getConfiguration()->getTemplateDir('a', 'layout.php').'/layout'); |
|---|
| 482 | } |
|---|
| 483 | |
|---|
| 484 | // Loading the a helper at this point guarantees not only |
|---|
| 485 | // helper functions but also necessary JavaScript and CSS |
|---|
| 486 | sfContext::getInstance()->getConfiguration()->loadHelpers('a'); |
|---|
| 487 | } |
|---|
| 488 | |
|---|
| 489 | static public function pageIsDescendantOfInfo($page, $info) |
|---|
| 490 | { |
|---|
| 491 | return ($page->lft > $info['lft']) && ($page->rgt < $info['rgt']); |
|---|
| 492 | } |
|---|
| 493 | |
|---|
| 494 | // Same rules found in aPage::userHasPrivilege(), but without checking for |
|---|
| 495 | // a particular page, so we return true even for users who are just *potential* editors |
|---|
| 496 | // when granted privileges at an appropriate point in the page tree. This is useful for |
|---|
| 497 | // deciding whether the tabs control should show archived pages or not. (Showing those to |
|---|
| 498 | // a few editors who can't edit them is not a major problem, and checking the privs on |
|---|
| 499 | // each and every one is an unacceptable performance hit) |
|---|
| 500 | |
|---|
| 501 | static public function isPotentialEditor($user = false) |
|---|
| 502 | { |
|---|
| 503 | if ($user === false) |
|---|
| 504 | { |
|---|
| 505 | $user = sfContext::getInstance()->getUser(); |
|---|
| 506 | } |
|---|
| 507 | // Rule 1: admin can do anything |
|---|
| 508 | // Work around a bug in some releases of sfDoctrineGuard: users sometimes |
|---|
| 509 | // still have credentials even though they are not logged in |
|---|
| 510 | if ($user->isAuthenticated() && $user->hasCredential('cms_admin')) |
|---|
| 511 | { |
|---|
| 512 | return true; |
|---|
| 513 | } |
|---|
| 514 | |
|---|
| 515 | // The editor permission, which (like the editor group) makes you a candidate to edit |
|---|
| 516 | // if actually granted that privilege somewhere in the tree (via membership in a group |
|---|
| 517 | // that has the editor permission), is generally received from a group. In older installs the |
|---|
| 518 | // editor group itself won't have it, so we still check by other means (see below). |
|---|
| 519 | if ($user->isAuthenticated() && $user->hasCredential(sfConfig::get('app_a_group_editor_permission', 'editor'))) |
|---|
| 520 | { |
|---|
| 521 | return true; |
|---|
| 522 | } |
|---|
| 523 | |
|---|
| 524 | $sufficientCredentials = sfConfig::get("app_a_edit_sufficient_credentials", false); |
|---|
| 525 | $sufficientGroup = sfConfig::get("app_a_edit_sufficient_group", false); |
|---|
| 526 | $candidateGroup = sfConfig::get("app_a_edit_candidate_group", false); |
|---|
| 527 | // By default users must log in to do anything except view |
|---|
| 528 | $loginRequired = sfConfig::get("app_a_edit_login_required", true); |
|---|
| 529 | |
|---|
| 530 | if ($loginRequired) |
|---|
| 531 | { |
|---|
| 532 | if (!$user->isAuthenticated()) |
|---|
| 533 | { |
|---|
| 534 | return false; |
|---|
| 535 | } |
|---|
| 536 | // Rule 3: if there are no sufficient credentials and there is no |
|---|
| 537 | // required or sufficient group, then login alone is sufficient. Common |
|---|
| 538 | // on sites with one admin |
|---|
| 539 | if (($sufficientCredentials === false) && ($candidateGroup === false) && ($sufficientGroup === false)) |
|---|
| 540 | { |
|---|
| 541 | // Logging in is the only requirement |
|---|
| 542 | return true; |
|---|
| 543 | } |
|---|
| 544 | // Rule 4: if the user has sufficient credentials... that's sufficient! |
|---|
| 545 | // Many sites will want to simply say 'editors can edit everything' etc |
|---|
| 546 | if ($sufficientCredentials && |
|---|
| 547 | ($user->hasCredential($sufficientCredentials))) |
|---|
| 548 | { |
|---|
| 549 | |
|---|
| 550 | return true; |
|---|
| 551 | } |
|---|
| 552 | if ($sufficientGroup && |
|---|
| 553 | ($user->hasGroup($sufficientGroup))) |
|---|
| 554 | { |
|---|
| 555 | return true; |
|---|
| 556 | } |
|---|
| 557 | |
|---|
| 558 | // Rule 5: if there is a candidate group, make sure the user is a member |
|---|
| 559 | if ($candidateGroup && |
|---|
| 560 | (!$user->hasGroup($candidateGroup))) |
|---|
| 561 | { |
|---|
| 562 | return false; |
|---|
| 563 | } |
|---|
| 564 | return true; |
|---|
| 565 | } |
|---|
| 566 | else |
|---|
| 567 | { |
|---|
| 568 | // No login required |
|---|
| 569 | return true; |
|---|
| 570 | } |
|---|
| 571 | } |
|---|
| 572 | |
|---|
| 573 | static public function getVariantsForSlotType($type, $options = array()) |
|---|
| 574 | { |
|---|
| 575 | // 1. By default, all variants of the slot are allowed. |
|---|
| 576 | // 2. If app_a_allowed_variants is set and a specific list of allowed variants |
|---|
| 577 | // is provided for this slot type, those variants are allowed. |
|---|
| 578 | // 3. If app_a_allowed_variants is set and a specific list is not present for this slot type, |
|---|
| 579 | // no variants are allowed for this slot type. |
|---|
| 580 | // 4. An allowed_variants option in an a_slot or a_area call overrides all of the above. |
|---|
| 581 | |
|---|
| 582 | // This makes it easy to define lots of variants, then disable them by default for |
|---|
| 583 | // templates that don't explicitly enable them. This is useful because variants are often |
|---|
| 584 | // specific to the dimensions or other particulars of a particular template |
|---|
| 585 | |
|---|
| 586 | if (sfConfig::has('app_a_allowed_slot_variants')) |
|---|
| 587 | { |
|---|
| 588 | $allowedVariantsAll = sfConfig::get('app_a_allowed_slot_variants', array()); |
|---|
| 589 | $allowedVariants = array(); |
|---|
| 590 | if (isset($allowedVariantsAll[$type])) |
|---|
| 591 | { |
|---|
| 592 | $allowedVariants = $allowedVariantsAll[$type]; |
|---|
| 593 | } |
|---|
| 594 | } |
|---|
| 595 | if (isset($options['allowed_variants'])) |
|---|
| 596 | { |
|---|
| 597 | $allowedVariants = $options['allowed_variants']; |
|---|
| 598 | } |
|---|
| 599 | |
|---|
| 600 | $variants = sfConfig::get('app_a_slot_variants'); |
|---|
| 601 | if (!is_array($variants)) |
|---|
| 602 | { |
|---|
| 603 | return array(); |
|---|
| 604 | } |
|---|
| 605 | if (!isset($variants[$type])) |
|---|
| 606 | { |
|---|
| 607 | return array(); |
|---|
| 608 | } |
|---|
| 609 | $variants = $variants[$type]; |
|---|
| 610 | if (isset($allowedVariants)) |
|---|
| 611 | { |
|---|
| 612 | $allowed = array_flip($allowedVariants); |
|---|
| 613 | $keep = array(); |
|---|
| 614 | foreach ($variants as $name => $value) |
|---|
| 615 | { |
|---|
| 616 | if (isset($allowed[$name])) |
|---|
| 617 | { |
|---|
| 618 | $keep[$name] = $value; |
|---|
| 619 | } |
|---|
| 620 | } |
|---|
| 621 | $variants = $keep; |
|---|
| 622 | } |
|---|
| 623 | return $variants; |
|---|
| 624 | } |
|---|
| 625 | |
|---|
| 626 | static protected function i18nDummy() |
|---|
| 627 | { |
|---|
| 628 | __('Reorganize', null, 'apostrophe'); |
|---|
| 629 | __('Users', null, 'apostrophe'); |
|---|
| 630 | __('Plain Text', null, 'apostrophe'); |
|---|
| 631 | __('Rich Text', null, 'apostrophe'); |
|---|
| 632 | __('RSS Feed', null, 'apostrophe'); |
|---|
| 633 | __('Image', null, 'apostrophe'); |
|---|
| 634 | __('Slideshow', null, 'apostrophe'); |
|---|
| 635 | __('Button', null, 'apostrophe'); |
|---|
| 636 | __('Video', null, 'apostrophe'); |
|---|
| 637 | __('PDF', null, 'apostrophe'); |
|---|
| 638 | __('Raw HTML', null, 'apostrophe'); |
|---|
| 639 | __('Template-Based', null, 'apostrophe'); |
|---|
| 640 | __('Users', null, 'apostrophe'); |
|---|
| 641 | __('Reorganize', null, 'apostrophe'); |
|---|
| 642 | } |
|---|
| 643 | |
|---|
| 644 | static public function getRealUrl() |
|---|
| 645 | { |
|---|
| 646 | if (isset(aTools::$realUrl)) |
|---|
| 647 | { |
|---|
| 648 | return aTools::$realUrl; |
|---|
| 649 | } |
|---|
| 650 | return sfContext::getInstance()->getRequest()->getUri(); |
|---|
| 651 | } |
|---|
| 652 | |
|---|
| 653 | static public function setRealUrl($url) |
|---|
| 654 | { |
|---|
| 655 | aTools::$realUrl = $url; |
|---|
| 656 | } |
|---|
| 657 | |
|---|
| 658 | // Returns a regexp fragment that matches a valid slug in a UTF8-aware way. |
|---|
| 659 | // Does not reject slugs with consecutive dashes or slashes. DOES accept the % |
|---|
| 660 | // sign because URLs generated by url_for arrive with the UTF8 characters |
|---|
| 661 | // %-encoded. You should anchor it with ^ and $ if your goal is to match one slug as the whole string |
|---|
| 662 | static public function getSlugRegexpFragment($allowSlashes = false) |
|---|
| 663 | { |
|---|
| 664 | // Looks like the 'u' modifier is purely for allowing UTF8 in the pattern *itself*. So we |
|---|
| 665 | // shouldn't need it to achieve |
|---|
| 666 | if (function_exists('mb_strtolower')) |
|---|
| 667 | { |
|---|
| 668 | // UTF-8 capable replacement for \W. Works fine for English and also for Greek, etc. |
|---|
| 669 | // ALlow % as well to work with preescaped UTF8, which is common in URLs |
|---|
| 670 | $alnum = '\p{L}\p{N}_%'; |
|---|
| 671 | $modifier = ''; |
|---|
| 672 | } |
|---|
| 673 | else |
|---|
| 674 | { |
|---|
| 675 | $alnum = '\w'; |
|---|
| 676 | $modifier = ''; |
|---|
| 677 | } |
|---|
| 678 | if ($allowSlashes) |
|---|
| 679 | { |
|---|
| 680 | $alnum .= '\/'; |
|---|
| 681 | } |
|---|
| 682 | $regexp = "[$alnum\-]+"; |
|---|
| 683 | return $regexp; |
|---|
| 684 | } |
|---|
| 685 | |
|---|
| 686 | // UTF-8 where available. If your UTF-8 gets munged make sure your PHP has the |
|---|
| 687 | // mbstring extension. allowSlashes will allow / but will reduce duplicate / and |
|---|
| 688 | // remove any / at the end. Everything that isn't a letter or a number |
|---|
| 689 | // (or a slash, when allowed) is converted to a -. Consecutive -'s are reduced and leading and |
|---|
| 690 | // trailing -'s are removed |
|---|
| 691 | |
|---|
| 692 | // $betweenWords must not contain characters that have special meaning in a regexp. |
|---|
| 693 | // Usually it is - (the default) or ' ' |
|---|
| 694 | |
|---|
| 695 | static public function slugify($path, $allowSlashes = false, $allowUnderscores = true, $betweenWords = '-') |
|---|
| 696 | { |
|---|
| 697 | // This is the inverse of the method above |
|---|
| 698 | if (function_exists('mb_strtolower')) |
|---|
| 699 | { |
|---|
| 700 | // UTF-8 capable replacement for \W. Works fine for English and also for Greek, etc. |
|---|
| 701 | $alnum = '\p{L}\p{N}' . ($allowUnderscores ? '_' : ''); |
|---|
| 702 | $modifier = 'u'; |
|---|
| 703 | } |
|---|
| 704 | else |
|---|
| 705 | { |
|---|
| 706 | $alnum = $allowUnderscores ? '\w' : '[A-Za-z0-9]'; |
|---|
| 707 | $modifier = ''; |
|---|
| 708 | } |
|---|
| 709 | if ($allowSlashes) |
|---|
| 710 | { |
|---|
| 711 | $alnum .= '\/'; |
|---|
| 712 | } |
|---|
| 713 | // Removing - here expands flexibility and shouldn't hurt because it's the replacement anyway |
|---|
| 714 | $regexp = "/[^$alnum]+/$modifier"; |
|---|
| 715 | $path = aTools::strtolower(preg_replace($regexp, $betweenWords, $path)); |
|---|
| 716 | if ($allowSlashes) |
|---|
| 717 | { |
|---|
| 718 | // No multiple consecutive / |
|---|
| 719 | $path = preg_replace("/\/+/$modifier", "/", $path); |
|---|
| 720 | // No trailing / |
|---|
| 721 | $path = preg_replace("/\/$/$modifier", '', $path); |
|---|
| 722 | } |
|---|
| 723 | // No consecutive dashes |
|---|
| 724 | $path = preg_replace("/$betweenWords+/$modifier", $betweenWords, $path); |
|---|
| 725 | // Leading and trailing dashes are silly. This has the effect of trim() |
|---|
| 726 | // among other sensible things |
|---|
| 727 | $path = preg_replace("/^-*(.*?)-*$/$modifier", '$1', $path); |
|---|
| 728 | return $path; |
|---|
| 729 | } |
|---|
| 730 | |
|---|
| 731 | static public function strtolower($s) |
|---|
| 732 | { |
|---|
| 733 | if (function_exists('mb_strtolower')) |
|---|
| 734 | { |
|---|
| 735 | return mb_strtolower($s, 'UTF-8'); |
|---|
| 736 | } |
|---|
| 737 | else |
|---|
| 738 | { |
|---|
| 739 | return strtolower($s); |
|---|
| 740 | } |
|---|
| 741 | } |
|---|
| 742 | |
|---|
| 743 | static public function addStylesheetsIfDesired($array) |
|---|
| 744 | { |
|---|
| 745 | $response = sfContext::getInstance()->getResponse(); |
|---|
| 746 | $preferences = sfConfig::get('app_a_use_bundled_stylesheets', array()); |
|---|
| 747 | foreach ($array as $stylesheet) |
|---|
| 748 | { |
|---|
| 749 | $good = true; |
|---|
| 750 | if (isset($preferences[$stylesheet])) |
|---|
| 751 | { |
|---|
| 752 | $good = $preferences[$stylesheet]; |
|---|
| 753 | } |
|---|
| 754 | if ($good) |
|---|
| 755 | { |
|---|
| 756 | $response->addStylesheet('/apostrophePlugin/css/a-' . $stylesheet . '.css'); |
|---|
| 757 | } |
|---|
| 758 | } |
|---|
| 759 | } |
|---|
| 760 | |
|---|
| 761 | static protected $locks = array(); |
|---|
| 762 | |
|---|
| 763 | // Lock names must be \w+ |
|---|
| 764 | static public function lock($name) |
|---|
| 765 | { |
|---|
| 766 | $dir = aFiles::getWritableDataFolder(array('a', 'locks')); |
|---|
| 767 | if (!preg_match('/^\w+$/', $name)) |
|---|
| 768 | { |
|---|
| 769 | throw new sfException("Lock name is empty or contains non-word characters"); |
|---|
| 770 | } |
|---|
| 771 | $file = "$dir/$name.lck"; |
|---|
| 772 | while (true) |
|---|
| 773 | { |
|---|
| 774 | $fp = fopen($file, 'a'); |
|---|
| 775 | if ($fp) |
|---|
| 776 | { |
|---|
| 777 | if (flock($fp, LOCK_EX)) |
|---|
| 778 | { |
|---|
| 779 | break; |
|---|
| 780 | } |
|---|
| 781 | } |
|---|
| 782 | sleep(1); |
|---|
| 783 | } |
|---|
| 784 | flock($fp, LOCK_EX); |
|---|
| 785 | aTools::$locks[] = $fp; |
|---|
| 786 | } |
|---|
| 787 | |
|---|
| 788 | static public function unlock() |
|---|
| 789 | { |
|---|
| 790 | if (count(aTools::$locks)) |
|---|
| 791 | { |
|---|
| 792 | $fp = array_pop(aTools::$locks); |
|---|
| 793 | fclose($fp); |
|---|
| 794 | } |
|---|
| 795 | else |
|---|
| 796 | { |
|---|
| 797 | // It's OK to call with no lock, this greatly simplifies methods like flunkUnless() |
|---|
| 798 | // If you are using multiple names you are responsible for making sure you unlock consistently. |
|---|
| 799 | } |
|---|
| 800 | } |
|---|
| 801 | } |
|---|