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

root/plugins/apostrophePlugin/trunk/lib/toolkit/BaseaTools.class.php @ 2329

Revision 2329, 24.6 KB (checked in by tboutell, 3 years ago)

Fixed #626 (don't play with EXIF if the file is not a JPEG)

Line 
1<?php
2
3class 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 &amp; 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}
Note: See TracBrowser for help on using the browser.