Projet

Général

Profil

Paste
Télécharger (47,1 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / ctools / includes / context.inc @ 74f6bef0

1
<?php
2

    
3
/**
4
 * @file
5
 *
6
 * Contains code related to the ctools system of 'context'.
7
 *
8
 * Context, originally from Panels, is a method of packaging objects into
9
 * a more generic bundle and providing a plugin system so that a UI can
10
 * take advantage of them. The idea is that the context objects
11
 * represent 'the context' that a given operation (usually a page view)
12
 * is operating in or on.
13
 *
14
 * For example, when viewing a page, the 'context' is a node object. When
15
 * viewing a user, the 'context' is a user object. Contexts can also
16
 * have related contexts. For example, when viewing a 'node' you may need
17
 * to know something about the node author. Therefore, the node author
18
 * is a related context.
19
 */
20

    
21
/**
22
 * The context object is largely a wrapper around some other object, with
23
 * an interface to finding out what is contained and getting to both
24
 * the object and information about the object.
25
 *
26
 * Each context object has its own information, but some things are very
27
 * common, such as titles, data, keywords, etc. In particulare, the 'type'
28
 * of the context is important.
29
 */
30
class ctools_context {
31
  var $type = NULL;
32
  var $data = NULL;
33
  // The title of this object.
34
  var $title = '';
35
  // The title of the page if this object exists
36
  var $page_title = '';
37
  // The identifier (in the UI) of this object
38
  var $identifier = '';
39
  var $argument = NULL;
40
  var $keyword = '';
41
  var $original_argument = NULL;
42
  var $restrictions = array();
43
  var $empty = FALSE;
44

    
45
  function ctools_context($type = 'none', $data = NULL) {
46
    $this->type  = $type;
47
    $this->data  = $data;
48
    $this->title = t('Unknown context');
49
  }
50

    
51
  function is_type($type) {
52
    if ($type == 'any' || $this->type == 'any') {
53
      return TRUE;
54
    }
55

    
56
    $a = is_array($type) ? $type : array($type);
57
    $b = is_array($this->type) ? $this->type : array($this->type);
58
    return (bool) array_intersect($a, $b);
59
  }
60

    
61
  function get_argument() {
62
    return $this->argument;
63
  }
64

    
65
  function get_original_argument() {
66
    if (!is_null($this->original_argument)) {
67
      return $this->original_argument;
68
    }
69
    return $this->argument;
70
  }
71

    
72
  function get_keyword() {
73
    return $this->keyword;
74
  }
75

    
76
  function get_identifier() {
77
    return $this->identifier;
78
  }
79

    
80
  function get_title() {
81
    return $this->title;
82
  }
83

    
84
  function get_page_title() {
85
    return $this->page_title;
86
  }
87
}
88

    
89
/**
90
 * Used to create a method of comparing if a list of contexts
91
 * match a required context type.
92
 */
93
class ctools_context_required {
94
  var $keywords = '';
95

    
96
  /**
97
   * If set, the title will be used in the selector to identify
98
   * the context. This is very useful when multiple contexts
99
   * are required to inform the user will be used for what.
100
   */
101
  var $title = NULL;
102

    
103
  /**
104
   * Test to see if this context is required.
105
   */
106
  var $required = TRUE;
107

    
108
  /**
109
   * If TRUE, skip the check in ctools_context_required::select()
110
   * for contexts whose names may have changed.
111
   */
112
  var $skip_name_check = FALSE;
113

    
114
  /**
115
   *
116
   * @param $title
117
   *   The first parameter should be the 'title' of the context for use
118
   *   in UYI selectors when multiple contexts qualify.
119
   * @param ...
120
   *   One or more keywords to use for matching which contexts are allowed.
121
   */
122
  function ctools_context_required($title) {
123
    $args = func_get_args();
124
    $this->title = array_shift($args);
125

    
126
    // If we have a boolean value at the end for $skip_name_check, store it
127
    if (is_bool(end($args))) {
128
      $this->skip_name_check = array_pop($args);
129
    }
130

    
131
    // If we were given restrictions at the end, store them.
132
    if (count($args) > 1 && is_array(end($args))) {
133
      $this->restrictions = array_pop($args);
134
    }
135

    
136
    if (count($args) == 1) {
137
      $args = array_shift($args);
138
    }
139
    $this->keywords = $args;
140
  }
141

    
142
  function filter($contexts) {
143
    $result = array();
144

    
145
    // See which of these contexts are valid
146
    foreach ((array) $contexts as $cid => $context) {
147
      if ($context->is_type($this->keywords)) {
148
        // Compare to see if our contexts were met.
149
        if (!empty($this->restrictions) && !empty($context->restrictions)) {
150
          foreach ($this->restrictions as $key => $values) {
151
            // If we have a restriction, the context must either not have that
152
            // restriction listed, which means we simply don't know what it is,
153
            // or there must be an intersection of the restricted values on
154
            // both sides.
155
            if (!is_array($values)) {
156
              $values = array($values);
157
            }
158
            if (!empty($context->restrictions[$key]) && !array_intersect($values, $context->restrictions[$key])) {
159
              continue 2;
160
            }
161
          }
162
        }
163
        $result[$cid] = $context;
164
      }
165
    }
166

    
167
    return $result;
168
  }
169

    
170
  function select($contexts, $context) {
171
    if (!is_array($contexts)) {
172
      $contexts = array($contexts);
173
    }
174

    
175
    // If we had requested a $context but that $context doesn't exist
176
    // in our context list, there is a good chance that what happened
177
    // is our context IDs changed. See if there's another context
178
    // that satisfies our requirements.
179
    if (!$this->skip_name_check && !empty($context) && !isset($contexts[$context])) {
180
      $choices = $this->filter($contexts);
181

    
182
      // If we got a hit, take the first one that matches.
183
      if ($choices) {
184
        $keys = array_keys($choices);
185
        $context = reset($keys);
186
      }
187
    }
188

    
189
    if (empty($context) || empty($contexts[$context])) {
190
      return FALSE;
191
    }
192
    return $contexts[$context];
193
  }
194
}
195

    
196
/**
197
 * Used to compare to see if a list of contexts match an optional context. This
198
 * can produce empty contexts to use as placeholders.
199
 */
200
class ctools_context_optional extends ctools_context_required {
201
  var $required = FALSE;
202
  function ctools_context_optional() {
203
    $args = func_get_args();
204
    call_user_func_array(array($this, 'ctools_context_required'), $args);
205
  }
206

    
207
  /**
208
   * Add the 'empty' context which is possible for optional
209
   */
210
  function add_empty(&$contexts) {
211
    $context = new ctools_context('any');
212
    $context->title      = t('No context');
213
    $context->identifier = t('No context');
214
    $contexts = array_merge(array('empty' => $context), $contexts);
215
  }
216

    
217
  function filter($contexts) {
218
    $this->add_empty($contexts);
219
    return parent::filter($contexts);
220
  }
221

    
222
  function select($contexts, $context) {
223
    $this->add_empty($contexts);
224
    if (empty($context)) {
225
      return $contexts['empty'];
226
    }
227

    
228
    $result = parent::select($contexts, $context);
229

    
230
    // Don't flip out if it can't find the context; this is optional, put
231
    // in an empty.
232
    if ($result == FALSE) {
233
      $result = $contexts['empty'];
234
    }
235
    return $result;
236
  }
237
}
238

    
239
/**
240
 * Return a keyed array of context that match the given 'required context'
241
 * filters.
242
 *
243
 * Functions or systems that require contexts of a particular type provide a
244
 * ctools_context_required or ctools_context_optional object. This function
245
 * examines that object and an array of contexts to determine which contexts
246
 * match the filter.
247
 *
248
 * Since multiple contexts can be required, this function will accept either
249
 * an array of all required contexts, or just a single required context object.
250
 *
251
 * @param $contexts
252
 *   A keyed array of all available contexts.
253
 * @param $required
254
 *   A ctools_context_required or ctools_context_optional object, or an array
255
 *   of such objects.
256
 *
257
 * @return
258
 *   A keyed array of contexts that match the filter.
259
 */
260
function ctools_context_filter($contexts, $required) {
261
  if (is_array($required)) {
262
    $result = array();
263
    foreach ($required as $r) {
264
      $result = array_merge($result, _ctools_context_filter($contexts, $r));
265
    }
266
    return $result;
267
  }
268

    
269
  return _ctools_context_filter($contexts, $required);
270
}
271

    
272
function _ctools_context_filter($contexts, $required) {
273
  $result = array();
274

    
275
  if (is_object($required)) {
276
    $result = $required->filter($contexts);
277
  }
278

    
279
  return $result;
280
}
281

    
282
/**
283
 * Create a select box to choose possible contexts.
284
 *
285
 * This only creates a selector if there is actually a choice; if there
286
 * is only one possible context, that one is silently assigned.
287
 *
288
 * If an array of required contexts is provided, one selector will be
289
 * provided for each context.
290
 *
291
 * @param $contexts
292
 *   A keyed array of all available contexts.
293
 * @param $required
294
 *   The required context object or array of objects.
295
 *
296
 * @return
297
 *   A form element, or NULL if there are no contexts that satisfy the
298
 *   requirements.
299
 */
300
function ctools_context_selector($contexts, $required, $default) {
301
  if (is_array($required)) {
302
    $result = array('#tree' => TRUE);
303
    $count = 1;
304
    foreach ($required as $id => $r) {
305
      $result[] = _ctools_context_selector($contexts, $r, isset($default[$id]) ? $default[$id] : '', $count++);
306
    }
307
    return $result;
308
  }
309

    
310
  return _ctools_context_selector($contexts, $required, $default);
311
}
312

    
313
function _ctools_context_selector($contexts, $required, $default, $num = 0) {
314
  $filtered = ctools_context_filter($contexts, $required);
315
  $count = count($filtered);
316

    
317
  $form = array();
318

    
319
  if ($count >= 1) {
320
    // If there's more than one to choose from, create a select widget.
321
    foreach ($filtered as $cid => $context) {
322
      $options[$cid] = $context->get_identifier();
323
    }
324
    if (!empty($required->title)) {
325
      $title = $required->title;
326
    }
327
    else {
328
      $title = $num ? t('Context %count', array('%count' => $num)) : t('Context');
329
    }
330

    
331
    return array(
332
      '#type' => 'select',
333
      '#options' => $options,
334
      '#title' => $title,
335
      '#default_value' => $default,
336
    );
337
  }
338
}
339

    
340
/**
341
 * Are there enough contexts for a plugin?
342
 *
343
 * Some plugins can have a 'required contexts' item which can either
344
 * be a context requirement object or an array of them. When contexts
345
 * are required, items that do not have enough contexts should not
346
 * appear. This tests an item to see if it has enough contexts
347
 * to actually appear.
348
 *
349
 * @param $contexts
350
 *   A keyed array of all available contexts.
351
 * @param $required
352
 *   The required context object or array of objects.
353
 *
354
 * @return
355
 *   TRUE if there are enough contexts, FALSE if there are not.
356
 */
357
function ctools_context_match_requirements($contexts, $required) {
358
  if (!is_array($required)) {
359
    $required = array($required);
360
  }
361

    
362
  // Get the keys to avoid bugs in PHP 5.0.8 with keys and loops.
363
  // And use it to remove optional contexts.
364
  $keys = array_keys($required);
365
  foreach ($keys as $key) {
366
    if (empty($required[$key]->required)) {
367
      unset($required[$key]);
368
    }
369
  }
370

    
371
  $count = count($required);
372
  return (count(ctools_context_filter($contexts, $required)) >= $count);
373
}
374

    
375
/**
376
 * Create a select box to choose possible contexts.
377
 *
378
 * This only creates a selector if there is actually a choice; if there
379
 * is only one possible context, that one is silently assigned.
380
 *
381
 * If an array of required contexts is provided, one selector will be
382
 * provided for each context.
383
 *
384
 * @param $contexts
385
 *   A keyed array of all available contexts.
386
 * @param $required
387
 *   The required context object or array of objects.
388
 *
389
 * @return
390
 *   A form element, or NULL if there are no contexts that satisfy the
391
 *   requirements.
392
 */
393
function ctools_context_converter_selector($contexts, $required, $default) {
394
  if (is_array($required)) {
395
    $result = array('#tree' => TRUE);
396
    $count = 1;
397
    foreach ($required as $id => $r) {
398
      $result[] = _ctools_context_converter_selector($contexts, $r, isset($default[$id]) ? $default[$id] : '', $count++);
399
    }
400
    return $result;
401
  }
402

    
403
  return _ctools_context_converter_selector($contexts, $required, $default);
404
}
405

    
406
function _ctools_context_converter_selector($contexts, $required, $default, $num = 0) {
407
  $filtered = ctools_context_filter($contexts, $required);
408
  $count = count($filtered);
409

    
410
  $form = array();
411

    
412
  if ($count > 1) {
413
    // If there's more than one to choose from, create a select widget.
414
    $options = array();
415
    foreach ($filtered as $cid => $context) {
416
      if ($context->type == 'any') {
417
        $options[''] = t('No context');
418
        continue;
419
      }
420
      $key = $context->get_identifier();
421
      if ($converters = ctools_context_get_converters($cid . '.', $context)) {
422
        $options[$key] = $converters;
423
      }
424
    }
425
    if (empty($options)) {
426
      return array(
427
        '#type' => 'value',
428
        '#value' => 'any',
429
      );
430
    }
431
    if (!empty($required->title)) {
432
      $title = $required->title;
433
    }
434
    else {
435
      $title = $num ? t('Context %count', array('%count' => $num)) : t('Context');
436
    }
437

    
438
    return array(
439
      '#type' => 'select',
440
      '#options' => $options,
441
      '#title' => $title,
442
      '#description' => t('Please choose which context and how you would like it converted.'),
443
      '#default_value' => $default,
444
    );
445
  }
446
}
447

    
448
/**
449
 * Get a list of converters available for a given context.
450
 */
451
function ctools_context_get_converters($cid, $context) {
452
  if (empty($context->plugin)) {
453
    return array();
454
  }
455

    
456
  return _ctools_context_get_converters($cid, $context->plugin);
457
}
458

    
459
/**
460
 * Get a list of converters available for a given context.
461
 */
462
function _ctools_context_get_converters($id, $plugin_name) {
463
  $plugin = ctools_get_context($plugin_name);
464
  if (empty($plugin['convert list'])) {
465
    return array();
466
  }
467

    
468
  $converters = array();
469
  if (is_array($plugin['convert list'])) {
470
    $converters = $plugin['convert list'];
471
  }
472
  else if ($function = ctools_plugin_get_function($plugin, 'convert list')) {
473
    $converters = (array) $function($plugin);
474
  }
475

    
476
  foreach (module_implements('ctools_context_convert_list_alter') as $module) {
477
    $function = $module . '_ctools_context_convert_list_alter';
478
    $function($plugin, $converters);
479
  }
480

    
481
  // Now, change them all to include the plugin:
482
  $return = array();
483
  foreach ($converters as $key => $title) {
484
    $return[$id . $key] = $title;
485
  }
486

    
487
  natcasesort($return);
488
  return $return;
489
}
490

    
491
/**
492
 * Get a list of all contexts + converters available.
493
 */
494
function ctools_context_get_all_converters() {
495
  $contexts = ctools_get_contexts();
496
  $converters = array();
497
  foreach ($contexts as $name => $context) {
498
    if (empty($context['no required context ui'])) {
499
      $context_converters = _ctools_context_get_converters($name . '.', $name);
500
      if ($context_converters) {
501
        $converters[$context['title']] = $context_converters;
502
      }
503
    }
504
  }
505

    
506
  return $converters;
507
}
508

    
509
/**
510
 * Let the context convert an argument based upon the converter that was given.
511
 *
512
 * @param $context
513
 *   The context object
514
 * @param $converter
515
 *   The converter to use, which should be a string provided by the converter list.
516
 * @param $converter_options
517
 *   A n array of options to pass on to the generation function. For contexts
518
 *   that use token module, of particular use is 'sanitize' => FALSE which can
519
 *   get raw tokens. This should ONLY be used in values that will later be
520
 *   treated as unsafe user input since these values are by themselves unsafe.
521
 *   It is particularly useful to get raw values from Field API.
522
 */
523
function ctools_context_convert_context($context, $converter, $converter_options = array()) {
524
  // Contexts without plugins might be optional placeholders.
525
  if (empty($context->plugin)) {
526
    return;
527
  }
528

    
529
  $value = $context->argument;
530
  $plugin = ctools_get_context($context->plugin);
531
  if ($function = ctools_plugin_get_function($plugin, 'convert')) {
532
    $value = $function($context, $converter, $converter_options);
533
  }
534

    
535
  foreach (module_implements('ctools_context_converter_alter') as $module) {
536
    $function = $module . '_ctools_context_converter_alter';
537
    $function($context, $converter, $value, $converter_options);
538
  }
539

    
540
  return $value;
541
}
542

    
543
/**
544
 * Choose a context or contexts based upon the selection made via
545
 * ctools_context_filter.
546
 *
547
 * @param $contexts
548
 *   A keyed array of all available contexts
549
 * @param $required
550
 *   The required context object provided by the plugin
551
 * @param $context
552
 *   The selection made using ctools_context_selector
553
 */
554
function ctools_context_select($contexts, $required, $context) {
555
  if (is_array($required)) {
556
    $result = array();
557
    foreach ($required as $id => $r) {
558
      if (empty($required[$id])) {
559
        continue;
560
      }
561

    
562
      if (($result[] = _ctools_context_select($contexts, $r, $context[$id])) === FALSE) {
563
        return FALSE;
564
      }
565
    }
566
    return $result;
567
  }
568

    
569
  return _ctools_context_select($contexts, $required, $context);
570
}
571

    
572
function _ctools_context_select($contexts, $required, $context) {
573
  if (!is_object($required)) {
574
    return FALSE;
575
  }
576

    
577
  return $required->select($contexts, $context);
578
}
579

    
580
/**
581
 * Create a new context object.
582
 *
583
 * @param $type
584
 *   The type of context to create; this loads a plugin.
585
 * @param $data
586
 *   The data to put into the context.
587
 * @param $empty
588
 *   Whether or not this context is specifically empty.
589
 * @param $conf
590
 *   A configuration structure if this context was created via UI.
591
 *
592
 * @return
593
 *   A $context or NULL if one could not be created.
594
 */
595
function ctools_context_create($type, $data = NULL, $conf = FALSE) {
596
  ctools_include('plugins');
597
  $plugin = ctools_get_context($type);
598

    
599
  if ($function = ctools_plugin_get_function($plugin, 'context')) {
600
    return $function(FALSE, $data, $conf, $plugin);
601
  }
602
}
603

    
604
/**
605
 * Create an empty context object.
606
 *
607
 * Empty context objects are primarily used as placeholders in the UI where
608
 * the actual contents of a context object may not be known. It may have
609
 * additional text embedded to give the user clues as to how the context
610
 * is used.
611
 *
612
 * @param $type
613
 *   The type of context to create; this loads a plugin.
614
 *
615
 * @return
616
 *   A $context or NULL if one could not be created.
617
 */
618
function ctools_context_create_empty($type) {
619
  $plugin = ctools_get_context($type);
620
  if ($function = ctools_plugin_get_function($plugin, 'context')) {
621
    $context = $function(TRUE, NULL, FALSE, $plugin);
622
    if (is_object($context)) {
623
      $context->empty = TRUE;
624
    }
625

    
626
    return $context;
627
  }
628
}
629

    
630
/**
631
 * Perform keyword and context substitutions.
632
 */
633
function ctools_context_keyword_substitute($string, $keywords, $contexts, $converter_options = array()) {
634
  // Ensure a default keyword exists:
635
  $keywords['%%'] = '%';
636

    
637
  // Match contexts to the base keywords:
638
  $context_keywords = array();
639
  foreach ($contexts as $context) {
640
    if (isset($context->keyword)) {
641
      $context_keywords[$context->keyword] = $context;
642
    }
643
  }
644

    
645
  // Look for context matches we we only have to convert known matches.
646
  $matches = array();
647
  if (preg_match_all('/%(%|[a-zA-Z0-9_-]+(?:\:[a-zA-Z0-9_-]+)*)/us', $string, $matches)) {
648
    foreach ($matches[1] as $keyword) {
649
      // Ignore anything it finds with %%.
650
      if ($keyword[0] == '%') {
651
        continue;
652
      }
653

    
654
      // If the keyword is already set by something passed in, don't try to
655
      // overwrite it.
656
      if (!empty($keywords['%' . $keyword])) {
657
        continue;
658
      }
659

    
660
      // Figure out our keyword and converter, if specified.
661
      if (strpos($keyword, ':')) {
662
        list($context, $converter) = explode(':', $keyword, 2);
663
      }
664
      else {
665
        $context = $keyword;
666
        if (isset($context_keywords[$keyword])) {
667
          $plugin = ctools_get_context($context_keywords[$context]->plugin);
668

    
669
          // Fall back to a default converter, if specified.
670
          if ($plugin && !empty($plugin['convert default'])) {
671
            $converter = $plugin['convert default'];
672
          }
673
        }
674
      }
675

    
676
      if (empty($context_keywords[$context]) || !empty($context_keywords[$context]->empty)) {
677
        $keywords['%' . $keyword] = '';
678
      }
679
      else if (!empty($converter)) {
680
        $keywords['%' . $keyword] = ctools_context_convert_context($context_keywords[$context], $converter, $converter_options);
681
      }
682
      else {
683
        $keywords['%' . $keyword] = $context_keywords[$keyword]->title;
684
      }
685
    }
686
  }
687
  return strtr($string, $keywords);
688
}
689

    
690
/**
691
 * Determine a unique context ID for a context
692
 *
693
 * Often contexts of many different types will be placed into a list. This
694
 * ensures that even though contexts of multiple types may share IDs, they
695
 * are unique in the final list.
696
 */
697
function ctools_context_id($context, $type = 'context') {
698
  if (!$context['id']) {
699
    $context['id'] = 1;
700
  }
701

    
702
  return $type . '_' . $context['name'] . '_' . $context['id'];
703
}
704

    
705
/**
706
 * Get the next id available given a list of already existing objects.
707
 *
708
 * This finds the next id available for the named object.
709
 *
710
 * @param $objects
711
 *   A list of context descriptor objects, i.e, arguments, relationships, contexts, etc.
712
 * @param $name
713
 *   The name being used.
714
 */
715
function ctools_context_next_id($objects, $name) {
716
  $id = 0;
717
  // Figure out which instance of this argument we're creating
718
  if (!$objects) {
719
    return $id + 1;
720
  }
721

    
722
  foreach ($objects as $object) {
723
    if (isset($object['name']) && $object['name'] == $name) {
724
      if ($object['id'] > $id) {
725
        $id = $object['id'];
726
      }
727
    }
728
  }
729

    
730
  return $id + 1;
731
}
732

    
733

    
734
// ---------------------------------------------------------------------------
735
// Functions related to contexts from arguments.
736

    
737
/**
738
 * Fetch metadata on a specific argument plugin.
739
 *
740
 * @param $argument
741
 *   Name of an argument plugin.
742
 *
743
 * @return
744
 *   An array with information about the requested argument plugin.
745
 */
746
function ctools_get_argument($argument) {
747
  ctools_include('plugins');
748
  return ctools_get_plugins('ctools', 'arguments', $argument);
749
}
750

    
751
/**
752
 * Fetch metadata for all argument plugins.
753
 *
754
 * @return
755
 *   An array of arrays with information about all available argument plugins.
756
 */
757
function ctools_get_arguments() {
758
  ctools_include('plugins');
759
  return ctools_get_plugins('ctools', 'arguments');
760
}
761

    
762
/**
763
 * Get a context from an argument.
764
 *
765
 * @param $argument
766
 *   The configuration of an argument. It must contain the following data:
767
 *   - name: The name of the argument plugin being used.
768
 *   - argument_settings: The configuration based upon the plugin forms.
769
 *   - identifier: The human readable identifier for this argument, usually
770
 *     defined by the UI.
771
 *   - keyword: The keyword used for this argument for substitutions.
772
 *
773
 * @param $arg
774
 *   The actual argument received. This is expected to be a string from a URL but
775
 *   this does not have to be the only source of arguments.
776
 * @param $empty
777
 *   If true, the $arg will not be used to load the context. Instead, an empty
778
 *   placeholder context will be loaded.
779
 *
780
 * @return
781
 *   A context object if one can be loaded.
782
 */
783
function ctools_context_get_context_from_argument($argument, $arg, $empty = FALSE) {
784
  ctools_include('plugins');
785
  if (empty($argument['name'])) {
786
    return;
787
  }
788

    
789
  if ($function = ctools_plugin_load_function('ctools', 'arguments', $argument['name'], 'context')) {
790
    // Backward compatibility: Merge old style settings into new style:
791
    if (!empty($argument['settings'])) {
792
      $argument += $argument['settings'];
793
      unset($argument['settings']);
794
    }
795

    
796
    $context = $function($arg, $argument, $empty);
797

    
798
    if (is_object($context)) {
799
      $context->identifier = $argument['identifier'];
800
      $context->page_title = isset($argument['title']) ? $argument['title'] : '';
801
      $context->keyword    = $argument['keyword'];
802
      $context->id         = ctools_context_id($argument, 'argument');
803
      $context->original_argument = $arg;
804

    
805
      if (!empty($context->empty)) {
806
        $context->placeholder = array(
807
          'type' => 'argument',
808
          'conf' => $argument,
809
        );
810
      }
811
    }
812
    return $context;
813
  }
814
}
815

    
816
/**
817
 * Retrieve a list of empty contexts for all arguments.
818
 */
819
function ctools_context_get_placeholders_from_argument($arguments) {
820
  $contexts = array();
821
  foreach ($arguments as $argument) {
822
    $context = ctools_context_get_context_from_argument($argument, NULL, TRUE);
823
    if ($context) {
824
      $contexts[ctools_context_id($argument, 'argument')] = $context;
825
    }
826
  }
827
  return $contexts;
828
}
829

    
830
/**
831
 * Load the contexts for a given list of arguments.
832
 *
833
 * @param $arguments
834
 *   The array of argument definitions.
835
 * @param &$contexts
836
 *   The array of existing contexts. New contexts will be added to this array.
837
 * @param $args
838
 *   The arguments to load.
839
 *
840
 * @return
841
 *   FALSE if an argument wants to 404.
842
 */
843
function ctools_context_get_context_from_arguments($arguments, &$contexts, $args) {
844
  foreach ($arguments as $argument) {
845
    // pull the argument off the list.
846
    $arg = array_shift($args);
847
    $id = ctools_context_id($argument, 'argument');
848

    
849
    // For % arguments embedded in the URL, our context is already loaded.
850
    // There is no need to go and load it again.
851
    if (empty($contexts[$id])) {
852
      if ($context = ctools_context_get_context_from_argument($argument, $arg)) {
853
        $contexts[$id] = $context;
854
      }
855
    }
856
    else {
857
      $context = $contexts[$id];
858
    }
859

    
860
    if ((empty($context) || empty($context->data)) && !empty($argument['default']) && $argument['default'] == '404') {
861
      return FALSE;
862
    }
863
  }
864
  return TRUE;
865
}
866

    
867
// ---------------------------------------------------------------------------
868
// Functions related to contexts from relationships.
869

    
870
/**
871
 * Fetch metadata on a specific relationship plugin.
872
 *
873
 * @param $content type
874
 *   Name of a panel content type.
875
 *
876
 * @return
877
 *   An array with information about the requested relationship.
878
 */
879
function ctools_get_relationship($relationship) {
880
  ctools_include('plugins');
881
  return ctools_get_plugins('ctools', 'relationships', $relationship);
882
}
883

    
884
/**
885
 * Fetch metadata for all relationship plugins.
886
 *
887
 * @return
888
 *   An array of arrays with information about all available relationships.
889
 */
890
function ctools_get_relationships() {
891
  ctools_include('plugins');
892
  return ctools_get_plugins('ctools', 'relationships');
893
}
894

    
895
/**
896
 *
897
 * @param $relationship
898
 *   The configuration of a relationship. It must contain the following data:
899
 *   - name: The name of the relationship plugin being used.
900
 *   - relationship_settings: The configuration based upon the plugin forms.
901
 *   - identifier: The human readable identifier for this relationship, usually
902
 *     defined by the UI.
903
 *   - keyword: The keyword used for this relationship for substitutions.
904
 *
905
 * @param $source_context
906
 *   The context this relationship is based upon.
907
 *
908
 * @param $placeholders
909
 *   If TRUE, placeholders are acceptable.
910
 *
911
 * @return
912
 *   A context object if one can be loaded.
913
 */
914
function ctools_context_get_context_from_relationship($relationship, $source_context, $placeholders = FALSE) {
915
  ctools_include('plugins');
916
  if ($function = ctools_plugin_load_function('ctools', 'relationships', $relationship['name'], 'context')) {
917
    // Backward compatibility: Merge old style settings into new style:
918
    if (!empty($relationship['relationship_settings'])) {
919
      $relationship += $relationship['relationship_settings'];
920
      unset($relationship['relationship_settings']);
921
    }
922

    
923
    $context = $function($source_context, $relationship, $placeholders);
924
    if ($context) {
925
      $context->identifier = $relationship['identifier'];
926
      $context->page_title = isset($relationship['title']) ? $relationship['title'] : '';
927
      $context->keyword    = $relationship['keyword'];
928
      if (!empty($context->empty)) {
929
        $context->placeholder = array(
930
          'type' => 'relationship',
931
          'conf' => $relationship,
932
        );
933
      }
934
      return $context;
935
    }
936
  }
937
}
938

    
939
/**
940
 * Fetch all relevant relationships.
941
 *
942
 * Relevant relationships are any relationship that can be created based upon
943
 * the list of existing contexts. For example, the 'node author' relationship
944
 * is relevant if there is a 'node' context, but makes no sense if there is
945
 * not one.
946
 *
947
 * @param $contexts
948
 *   An array of contexts used to figure out which relationships are relevant.
949
 *
950
 * @return
951
 *   An array of relationship keys that are relevant for the given set of
952
 *   contexts.
953
 */
954
function ctools_context_get_relevant_relationships($contexts) {
955
  $relevant = array();
956
  $relationships = ctools_get_relationships();
957

    
958
  // Go through each relationship
959
  foreach ($relationships as $rid => $relationship) {
960
    // For each relationship, see if there is a context that satisfies it.
961
    if (empty($relationship['no ui']) && ctools_context_filter($contexts, $relationship['required context'])) {
962
      $relevant[$rid] = $relationship['title'];
963
    }
964
  }
965

    
966
  return $relevant;
967
}
968

    
969
/**
970
 * Fetch all active relationships
971
 *
972
 * @param $relationships
973
 *   An keyed array of relationship data including:
974
 *   - name: name of relationship
975
 *   - context: context id relationship belongs to. This will be used to
976
 *     identify which context in the $contexts array to use to create the
977
 *     relationship context.
978
 *
979
 * @param $contexts
980
 *   A keyed array of contexts used to figure out which relationships
981
 *   are relevant. New contexts will be added to this.
982
 *
983
 * @param $placeholders
984
 *   If TRUE, placeholders are acceptable.
985
 */
986
function ctools_context_get_context_from_relationships($relationships, &$contexts, $placeholders = FALSE) {
987
  $return = array();
988

    
989
  foreach ($relationships as $rdata) {
990
    if (!isset($rdata['context'])) {
991
      continue;
992
    }
993

    
994
    if (is_array($rdata['context'])) {
995
      $rcontexts = array();
996
      foreach ($rdata['context'] as $cid) {
997
        if (empty($contexts[$cid])) {
998
          continue 2;
999
        }
1000
        $rcontexts[] = $contexts[$cid];
1001
      }
1002
    }
1003
    else {
1004
      if (empty($contexts[$rdata['context']])) {
1005
        continue;
1006
      }
1007
      $rcontexts = $contexts[$rdata['context']];
1008
    }
1009

    
1010
    $cid = ctools_context_id($rdata, 'relationship');
1011
    if ($context = ctools_context_get_context_from_relationship($rdata, $rcontexts)) {
1012
      $contexts[$cid] = $context;
1013
    }
1014
  }
1015
}
1016

    
1017
// ---------------------------------------------------------------------------
1018
// Functions related to loading contexts from simple context definitions.
1019

    
1020
/**
1021
 * Fetch metadata on a specific context plugin.
1022
 *
1023
 * @param $context
1024
 *   Name of a context.
1025
 *
1026
 * @return
1027
 *   An array with information about the requested panel context.
1028
 */
1029
function ctools_get_context($context) {
1030
  static $gate = array();
1031
  ctools_include('plugins');
1032
  $plugin = ctools_get_plugins('ctools', 'contexts', $context);
1033
  if (empty($gate['context']) && !empty($plugin['superceded by'])) {
1034
    // This gate prevents infinite loops.
1035
    $gate[$context] = TRUE;
1036
    $new_plugin = ctools_get_plugins('ctools', 'contexts', $plugin['superceded by']);
1037
    $gate[$context] = FALSE;
1038

    
1039
    // If a new plugin was returned, return it. Otherwise fall through and
1040
    // return the original we fetched.
1041
    if ($new_plugin) {
1042
      return $new_plugin;
1043
    }
1044
  }
1045

    
1046
  return $plugin;
1047
}
1048

    
1049
/**
1050
 * Fetch metadata for all context plugins.
1051
 *
1052
 * @return
1053
 *   An array of arrays with information about all available panel contexts.
1054
 */
1055
function ctools_get_contexts() {
1056
  ctools_include('plugins');
1057
  return ctools_get_plugins('ctools', 'contexts');
1058
}
1059

    
1060
/**
1061
 *
1062
 * @param $context
1063
 *   The configuration of a context. It must contain the following data:
1064
 *   - name: The name of the context plugin being used.
1065
 *   - context_settings: The configuration based upon the plugin forms.
1066
 *   - identifier: The human readable identifier for this context, usually
1067
 *     defined by the UI.
1068
 *   - keyword: The keyword used for this context for substitutions.
1069
 * @param $type
1070
 *   This is either 'context' which indicates the context will be loaded
1071
 *   from data in the settings, or 'required_context' which means the
1072
 *   context must be acquired from an external source. This is the method
1073
 *   used to pass pure contexts from one system to another.
1074
 *
1075
 * @return
1076
 *   A context object if one can be loaded.
1077
 */
1078
function ctools_context_get_context_from_context($context, $type = 'context', $argument = NULL) {
1079
  ctools_include('plugins');
1080
  $plugin = ctools_get_context($context['name']);
1081
  if ($function = ctools_plugin_get_function($plugin, 'context')) {
1082
    // Backward compatibility: Merge old style settings into new style:
1083
    if (!empty($context['context_settings'])) {
1084
      $context += $context['context_settings'];
1085
      unset($context['context_settings']);
1086
    }
1087

    
1088
    if (isset($argument) && isset($plugin['placeholder name'])) {
1089
      $context[$plugin['placeholder name']] = $argument;
1090
    }
1091

    
1092
    $return = $function($type == 'requiredcontext', $context, TRUE, $plugin);
1093
    if ($return) {
1094
      $return->identifier = $context['identifier'];
1095
      $return->page_title = isset($context['title']) ? $context['title'] : '';
1096
      $return->keyword    = $context['keyword'];
1097

    
1098
      if (!empty($context->empty)) {
1099
        $context->placeholder = array(
1100
          'type' => 'context',
1101
          'conf' => $context,
1102
        );
1103
      }
1104

    
1105
      return $return;
1106
    }
1107
  }
1108
}
1109

    
1110
/**
1111
 * Retrieve a list of base contexts based upon a simple 'contexts' definition.
1112
 *
1113
 * For required contexts this will always retrieve placeholders.
1114
 *
1115
 * @param $contexts
1116
 *   The list of contexts defined in the UI.
1117
 * @param $type
1118
 *   Either 'context' or 'requiredcontext', which indicates whether the contexts
1119
 *   are loaded from internal data or copied from an external source.
1120
 * @param $placeholders
1121
 *   If true, placeholders are acceptable.
1122
 */
1123
function ctools_context_get_context_from_contexts($contexts, $type = 'context', $placeholders = FALSE) {
1124
  $return = array();
1125
  foreach ($contexts as $context) {
1126
    $ctext = ctools_context_get_context_from_context($context, $type);
1127
    if ($ctext) {
1128
      if ($placeholders) {
1129
        $ctext->placeholder = TRUE;
1130
      }
1131
      $return[ctools_context_id($context, $type)] = $ctext;
1132
    }
1133
  }
1134
  return $return;
1135
}
1136

    
1137
/**
1138
 * Match up external contexts to our required contexts.
1139
 *
1140
 * This function is used to create a list of contexts with proper
1141
 * IDs based upon a list of required contexts.
1142
 *
1143
 * These contexts passed in should match the numeric positions of the
1144
 * required contexts. The caller must ensure this has already happened
1145
 * correctly as this function will not detect errors here.
1146
 *
1147
 * @param $required
1148
 *   A list of required contexts as defined by the UI.
1149
 * @param $contexts
1150
 *   A list of matching contexts as passed in from the calling system.
1151
 */
1152
function ctools_context_match_required_contexts($required, $contexts) {
1153
  $return = array();
1154
  if (!is_array($required)) {
1155
    return $return;
1156
  }
1157

    
1158
  foreach ($required as $r) {
1159
    $context = clone(array_shift($contexts));
1160
    $context->identifier = $r['identifier'];
1161
    $context->page_title = isset($r['title']) ? $r['title'] : '';
1162
    $context->keyword    = $r['keyword'];
1163
    $return[ctools_context_id($r, 'requiredcontext')] = $context;
1164
  }
1165

    
1166
  return $return;
1167
}
1168

    
1169
/**
1170
 * Load a full array of contexts for an object.
1171
 *
1172
 * Not all of the types need to be supported by this object.
1173
 *
1174
 * This function is not used to load contexts from external data, but may
1175
 * be used to load internal contexts and relationships. Otherwise it can also
1176
 * be used to generate a full set of placeholders for UI purposes.
1177
 *
1178
 * @param $object
1179
 *   An object that contains some or all of the following variables:
1180
 *
1181
 * - requiredcontexts: A list of UI configured contexts that are required
1182
 *   from an external source. Since these require external data, they will
1183
 *   only be added if $placeholders is set to TRUE, and empty contexts will
1184
 *   be created.
1185
 * - arguments: A list of UI configured arguments that will create contexts.
1186
 *   Since these require external data, they will only be added if $placeholders
1187
 *   is set to TRUE.
1188
 * - contexts: A list of UI configured contexts that have no external source,
1189
 *   and are essentially hardcoded. For example, these might configure a
1190
 *   particular node or a particular taxonomy term.
1191
 * - relationships: A list of UI configured contexts to be derived from other
1192
 *   contexts that already exist from other sources. For example, these might
1193
 *   be used to get a user object from a node via the node author relationship.
1194
 * @param $placeholders
1195
 *   If TRUE, this will generate placeholder objects for types this function
1196
 *   cannot load.
1197
 * @param $contexts
1198
 *   An array of pre-existing contexts that will be part of the return value.
1199
 */
1200
function ctools_context_load_contexts($object, $placeholders = TRUE, $contexts = array()) {
1201
  if (!empty($object->base_contexts)) {
1202
    $contexts += $object->base_contexts;
1203
  }
1204

    
1205
  if ($placeholders) {
1206
    // This will load empty contexts as placeholders for arguments that come
1207
    // from external sources. If this isn't set, it's assumed these context
1208
    // will already have been matched up and loaded.
1209
    if (!empty($object->requiredcontexts) && is_array($object->requiredcontexts)) {
1210
      $contexts += ctools_context_get_context_from_contexts($object->requiredcontexts, 'requiredcontext', $placeholders);
1211
    }
1212

    
1213
    if (!empty($object->arguments) && is_array($object->arguments)) {
1214
      $contexts += ctools_context_get_placeholders_from_argument($object->arguments);
1215
    }
1216
  }
1217

    
1218
  if (!empty($object->contexts) && is_array($object->contexts)) {
1219
    $contexts += ctools_context_get_context_from_contexts($object->contexts, 'context', $placeholders);
1220
  }
1221

    
1222
  // add contexts from relationships
1223
  if (!empty($object->relationships) && is_array($object->relationships)) {
1224
    ctools_context_get_context_from_relationships($object->relationships, $contexts, $placeholders);
1225
  }
1226

    
1227
  return $contexts;
1228
}
1229

    
1230
/**
1231
 * Return the first context with a form id from a list of contexts.
1232
 *
1233
 * This function is used to figure out which contexts represents 'the form'
1234
 * from a list of contexts. Only one contexts can actually be 'the form' for
1235
 * a given page, since the @code{<form>} tag can not be embedded within
1236
 * itself.
1237
 */
1238
function ctools_context_get_form($contexts) {
1239
  if (!empty($contexts)) {
1240
    foreach ($contexts as $id => $context) {
1241
      // if a form shows its id as being a 'required context' that means the
1242
      // the context is external to this display and does not count.
1243
      if (!empty($context->form_id) && substr($id, 0, 15) != 'requiredcontext') {
1244
        return $context;
1245
      }
1246
    }
1247
  }
1248
}
1249

    
1250
/**
1251
 * Replace placeholders with real contexts using data extracted from a form
1252
 * for the purposes of previews.
1253
 *
1254
 * @param $contexts
1255
 *   All of the contexts, including the placeholders.
1256
 * @param $arguments
1257
 *   The arguments. These will be acquired from $form_state['values'] and the
1258
 *   keys must match the context IDs.
1259
 *
1260
 * @return
1261
 *   A new $contexts array containing the replaced contexts. Not all contexts
1262
 *   may be replaced if, for example, an argument was unable to be converted
1263
 *   into a context.
1264
 */
1265
function ctools_context_replace_placeholders($contexts, $arguments) {
1266
  foreach ($contexts as $cid => $context) {
1267
    if (empty($context->empty)) {
1268
      continue;
1269
    }
1270

    
1271
    $new_context = NULL;
1272
    switch ($context->placeholder['type']) {
1273
      case 'relationship':
1274
        $relationship = $context->placeholder['conf'];
1275
        if (isset($contexts[$relationship['context']])) {
1276
          $new_context = ctools_context_get_context_from_relationship($relationship, $contexts[$relationship['context']]);
1277
        }
1278
        break;
1279
      case 'argument':
1280
        if (isset($arguments[$cid]) && $arguments[$cid] !== '') {
1281
          $argument = $context->placeholder['conf'];
1282
          $new_context = ctools_context_get_context_from_argument($argument, $arguments[$cid]);
1283
        }
1284
        break;
1285
      case 'context':
1286
        if (!empty($arguments[$cid])) {
1287
          $context_info = $context->placeholder['conf'];
1288
          $new_context = ctools_context_get_context_from_context($context_info, 'requiredcontext', $arguments[$cid]);
1289
        }
1290
        break;
1291
    }
1292

    
1293
    if ($new_context && empty($new_context->empty)) {
1294
      $contexts[$cid] = $new_context;
1295
    }
1296
  }
1297

    
1298
  return $contexts;
1299
}
1300

    
1301
/**
1302
 * Provide a form array for getting data to replace placeholder contexts
1303
 * with real data.
1304
 */
1305
function ctools_context_replace_form(&$form, $contexts) {
1306
  foreach ($contexts as $cid => $context) {
1307
    if (empty($context->empty)) {
1308
      continue;
1309
    }
1310

    
1311
    // Get plugin info from the context which should have been set when the
1312
    // empty context was created.
1313
    $info = NULL;
1314
    $plugin = NULL;
1315
    $settings = NULL;
1316
    switch ($context->placeholder['type']) {
1317
      case 'argument':
1318
        $info = $context->placeholder['conf'];
1319
        $plugin = ctools_get_argument($info['name']);
1320
        break;
1321

    
1322
      case 'context':
1323
        $info = $context->placeholder['conf'];
1324
        $plugin = ctools_get_context($info['name']);
1325
        break;
1326
    }
1327

    
1328
    // Ask the plugin where the form is.
1329
    if ($plugin && isset($plugin['placeholder form'])) {
1330
      if (is_array($plugin['placeholder form'])) {
1331
        $form[$cid] = $plugin['placeholder form'];
1332
      }
1333
      else if (function_exists($plugin['placeholder form'])) {
1334
        $widget = $plugin['placeholder form']($info);
1335
        if ($widget) {
1336
          $form[$cid] = $widget;
1337
        }
1338
      }
1339

    
1340
      if (!empty($form[$cid])) {
1341
        $form[$cid]['#title'] = t('@identifier (@keyword)', array('@keyword' => '%' . $context->keyword, '@identifier' => $context->identifier));
1342
      }
1343
    }
1344
  }
1345
}
1346

    
1347
// ---------------------------------------------------------------------------
1348
// Functions related to loading access control plugins
1349

    
1350
/**
1351
 * Fetch metadata on a specific access control plugin.
1352
 *
1353
 * @param $name
1354
 *   Name of a plugin.
1355
 *
1356
 * @return
1357
 *   An array with information about the requested access control plugin.
1358
 */
1359
function ctools_get_access_plugin($name) {
1360
  ctools_include('plugins');
1361
  return ctools_get_plugins('ctools', 'access', $name);
1362
}
1363

    
1364
/**
1365
 * Fetch metadata for all access control plugins.
1366
 *
1367
 * @return
1368
 *   An array of arrays with information about all available access control plugins.
1369
 */
1370
function ctools_get_access_plugins() {
1371
  ctools_include('plugins');
1372
  return ctools_get_plugins('ctools', 'access');
1373
}
1374

    
1375
/**
1376
 * Fetch a list of access plugins that are available for a given list of
1377
 * contexts.
1378
 *
1379
 * if 'logged-in-user' is not in the list of contexts, it will be added as
1380
 * this is required.
1381
 */
1382
function ctools_get_relevant_access_plugins($contexts) {
1383
  if (!isset($contexts['logged-in-user'])) {
1384
    $contexts['logged-in-user'] = ctools_access_get_loggedin_context();
1385
  }
1386

    
1387
  $all_plugins = ctools_get_access_plugins();
1388
  $plugins = array();
1389
  foreach ($all_plugins as $id => $plugin) {
1390
    if (!empty($plugin['required context']) && !ctools_context_match_requirements($contexts, $plugin['required context'])) {
1391
      continue;
1392
    }
1393
    $plugins[$id] = $plugin;
1394
  }
1395

    
1396
  return $plugins;
1397
}
1398

    
1399
/**
1400
 * Create a context for the logged in user.
1401
 */
1402
function ctools_access_get_loggedin_context() {
1403
  $context = ctools_context_create('entity:user', array('type' => 'current'), TRUE);
1404
  $context->identifier = t('Logged in user');
1405
  $context->keyword    = 'viewer';
1406
  $context->id         = 0;
1407

    
1408
  return $context;
1409
}
1410

    
1411
/**
1412
 * Get a summary of an access plugin's settings.
1413
 */
1414
function ctools_access_summary($plugin, $contexts, $test) {
1415
  if (!isset($contexts['logged-in-user'])) {
1416
    $contexts['logged-in-user'] = ctools_access_get_loggedin_context();
1417
  }
1418

    
1419
  $description = '';
1420
  if ($function = ctools_plugin_get_function($plugin, 'summary')) {
1421
    $required_context = isset($plugin['required context']) ? $plugin['required context'] : array();
1422
    $context          = isset($test['context']) ? $test['context'] : array();
1423
    $description      = $function($test['settings'], ctools_context_select($contexts, $required_context, $context), $plugin);
1424
  }
1425

    
1426
  if (!empty($test['not'])) {
1427
    $description = "NOT ($description)";
1428
  }
1429

    
1430
  return $description;
1431
}
1432

    
1433
/**
1434
 * Get a summary of a group of access plugin's settings.
1435
 */
1436
function ctools_access_group_summary($access, $contexts) {
1437
  if (empty($access['plugins'])) {
1438
    return;
1439
  }
1440

    
1441
  $descriptions = array();
1442
  foreach ($access['plugins'] as $id => $test) {
1443
    $plugin = ctools_get_access_plugin($test['name']);
1444
    $descriptions[] = ctools_access_summary($plugin, $contexts, $test);
1445
  }
1446

    
1447
  $separator = (isset($access['logic']) && $access['logic'] == 'and') ? t(', and ') : t(', or ');
1448
  return implode($separator, $descriptions);
1449
}
1450

    
1451
/**
1452
 * Determine if the current user has access via  plugin.
1453
 *
1454
 * @param $settings
1455
 *   An array of settings theoretically set by the user.
1456
 * @param $contexts
1457
 *   An array of zero or more contexts that may be used to determine if
1458
 *   the user has access.
1459
 *
1460
 * @return
1461
 *   TRUE if access is granted, false if otherwise.
1462
 */
1463
function ctools_access($settings, $contexts = array()) {
1464
  if (empty($settings['plugins'])) {
1465
    return TRUE;
1466
  }
1467

    
1468
  if (!isset($settings['logic'])) {
1469
    $settings['logic'] = 'and';
1470
  }
1471

    
1472
  if (!isset($contexts['logged-in-user'])) {
1473
    $contexts['logged-in-user'] = ctools_access_get_loggedin_context();
1474
  }
1475

    
1476
  foreach ($settings['plugins'] as $test) {
1477
    $pass = FALSE;
1478
    $plugin = ctools_get_access_plugin($test['name']);
1479
    if ($plugin && $function = ctools_plugin_get_function($plugin, 'callback')) {
1480
      // Do we need just some contexts or all of them?
1481
      if (!empty($plugin['all contexts'])) {
1482
        $test_contexts = $contexts;
1483
      }
1484
      else {
1485
        $required_context = isset($plugin['required context']) ? $plugin['required context'] : array();
1486
        $context = isset($test['context']) ? $test['context'] : array();
1487
        $test_contexts = ctools_context_select($contexts, $required_context, $context);
1488
      }
1489

    
1490
      $pass = $function($test['settings'], $test_contexts, $plugin);
1491
      if (!empty($test['not'])) {
1492
        $pass = !$pass;
1493
      }
1494
    }
1495

    
1496
    if ($pass && $settings['logic'] == 'or') {
1497
      // Pass if 'or' and this rule passed.
1498
      return TRUE;
1499
    }
1500
    else if (!$pass && $settings['logic'] == 'and') {
1501
      // Fail if 'and' and htis rule failed.
1502
      return FALSE;
1503
    }
1504
  }
1505

    
1506
  // Return TRUE if logic was and, meaning all rules passed.
1507
  // Return FALSE if logic was or, meaning no rule passed.
1508
  return $settings['logic'] == 'and';
1509
}
1510

    
1511
/**
1512
 * Create default settings for a new access plugin.
1513
 *
1514
 * @param $plugin
1515
 *   The access plugin being used.
1516
 *
1517
 * @return
1518
 *   A default configured test that should be placed in $access['plugins'];
1519
 */
1520
function ctools_access_new_test($plugin) {
1521
  $test = array(
1522
    'name' => $plugin['name'],
1523
    'settings' => array(),
1524
  );
1525

    
1526
  // Set up required context defaults.
1527
  if (isset($plugin['required context'])) {
1528
    if (is_object($plugin['required context'])) {
1529
      $test['context'] = '';
1530
    }
1531
    else {
1532
      $test['context'] = array();
1533
      foreach ($plugin['required context'] as $required) {
1534
        $test['context'][] = '';
1535
      }
1536
    }
1537
  }
1538

    
1539

    
1540
  $default = NULL;
1541
  if (isset($plugin['default'])) {
1542
    $default = $plugin['default'];
1543
  }
1544
  elseif (isset($plugin['defaults'])) {
1545
    $default = $plugin['defaults'];
1546
  }
1547

    
1548
  // Setup plugin defaults.
1549
  if (isset($default)) {
1550
    if (is_array($default)) {
1551
      $test['settings'] = $default;
1552
    }
1553
    else if (function_exists($default)) {
1554
      $test['settings'] = $default();
1555
    }
1556
    else {
1557
      $test['settings'] = array();
1558
    }
1559
  }
1560

    
1561
  return $test;
1562
}
1563

    
1564
/**
1565
 * Apply restrictions to contexts based upon the access control configured.
1566
 *
1567
 * These restrictions allow the UI to not show content that may not
1568
 * be relevant to all types of a particular context.
1569
 */
1570
function ctools_access_add_restrictions($settings, $contexts) {
1571
  if (empty($settings['plugins'])) {
1572
    return;
1573
  }
1574

    
1575
  if (!isset($settings['logic'])) {
1576
    $settings['logic'] = 'and';
1577
  }
1578

    
1579
  // We're not going to try to figure out restrictions on the or.
1580
  if ($settings['logic'] == 'or' && count($settings['plugins']) > 1) {
1581
    return;
1582
  }
1583

    
1584
  foreach ($settings['plugins'] as $test) {
1585
    $plugin = ctools_get_access_plugin($test['name']);
1586
    if ($plugin && $function = ctools_plugin_get_function($plugin, 'restrictions')) {
1587
      $required_context = isset($plugin['required context']) ? $plugin['required context'] : array();
1588
      $context = isset($test['context']) ? $test['context'] : array();
1589
      $contexts = ctools_context_select($contexts, $required_context, $context);
1590
      $function($test['settings'], $contexts);
1591
    }
1592
  }
1593
}