Projet

Général

Profil

Paste
Télécharger (59,7 ko) Statistiques
| Branche: | Révision:

root / htmltest / sites / all / modules / panels / panels.module @ c12e7e6a

1
<?php
2

    
3
/**
4
 * @file panels.module
5
 *
6
 * Core functionality for the Panels engine.
7
 */
8

    
9
define('PANELS_REQUIRED_CTOOLS_API', '2.0-alpha');
10

    
11
define('PANELS_TITLE_FIXED', 0); // Hide title use to be true/false. So false remains old behavior.
12
define('PANELS_TITLE_NONE', 1); // And true meant no title.
13
define('PANELS_TITLE_PANE', 2); // And this is the new behavior, where the title field will pick from a pane.
14

    
15
/**
16
 * Returns the API version of Panels. This didn't exist in 1.
17
 *
18
 * @todo -- this should work more like the CTools API version.
19
 *
20
 * @return An array with the major and minor versions
21
 */
22
function panels_api_version() {
23
  return array(3, 1);
24
}
25

    
26
// --------------------------------------------------------------------------
27
// Core Drupal hook implementations
28

    
29
/**
30
 * Implementation of hook_theme()
31
 */
32
function panels_theme() {
33
  // Safety: go away if CTools is not at an appropriate version.
34
  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
35
    return array();
36
  }
37

    
38
  $theme = array();
39
  $theme['panels_layout_link'] = array(
40
    'variables' => array('title' => NULL, 'id' => NULL, 'image' => NULL, 'link' => NULL, 'class' => NULL),
41
  );
42
  $theme['panels_layout_icon'] = array(
43
    'variables' => array('id' => NULL, 'image' => NULL, 'title' => NULL),
44
  );
45
  $theme['panels_pane'] = array(
46
    'variables' => array('output' => array(), 'pane' => array(), 'display' => array()),
47
    'path' => drupal_get_path('module', 'panels') . '/templates',
48
    'template' => 'panels-pane',
49
  );
50
  $theme['panels_common_content_list'] = array(
51
    'variables' => array('display' => NULL),
52
    'file' => 'includes/common.inc',
53
  );
54
  $theme['panels_render_display_form'] = array(
55
    'render element' => 'element',
56
  );
57

    
58
  $theme['panels_dashboard'] = array(
59
    'variables' => array(),
60
    'path' => drupal_get_path('module', 'panels') . '/templates',
61
    'file' => '../includes/callbacks.inc',
62
    'template' => 'panels-dashboard',
63
  );
64

    
65
  $theme['panels_dashboard_link'] = array(
66
    'variables' => array('link' => array()),
67
    'path' => drupal_get_path('module', 'panels') . '/templates',
68
    'file' => '../includes/callbacks.inc',
69
    'template' => 'panels-dashboard-link',
70
  );
71

    
72
  $theme['panels_dashboard_block'] = array(
73
    'variables' => array('block' => array()),
74
    'path' => drupal_get_path('module', 'panels') . '/templates',
75
    'file' => '../includes/callbacks.inc',
76
    'template' => 'panels-dashboard-block',
77
  );
78

    
79
  $theme['panels_add_content_modal'] = array(
80
    'variables' => array('renderer' => NULL, 'categories' => array(), 'region' => NULL, 'category' => NULL, 'column_count' => 2),
81
    'path' => drupal_get_path('module', 'panels') . '/templates',
82
    'file' => '../includes/add-content.inc',
83
    'template' => 'panels-add-content-modal',
84
  );
85

    
86
  $theme['panels_add_content_link'] = array(
87
    'variables' => array('renderer' => NULL, 'region' => NULL, 'content_type' => NULL),
88
    'path' => drupal_get_path('module', 'panels') . '/templates',
89
    'file' => '../includes/add-content.inc',
90
    'template' => 'panels-add-content-link',
91
  );
92

    
93
  // We don't need layout and style themes in maintenance mode.
94
  // Disabling this: See http://drupal.org/node/979912 for information.
95
//  if (defined('MAINTENANCE_MODE')) {
96
//    return $theme;
97
//  }
98

    
99
  // Register layout and style themes on behalf of all of these items.
100
  ctools_include('plugins', 'panels');
101

    
102
  // No need to worry about files; the plugin has to already be loaded for us
103
  // to even know what the theme function is, so files will be auto included.
104
  $layouts = panels_get_layouts();
105
  foreach ($layouts as $name => $data) {
106
    foreach (array('theme', 'admin theme') as $callback) {
107
      if (!empty($data[$callback])) {
108
        $theme[$data[$callback]] = array(
109
          'variables' => array('css_id' => NULL, 'content' => NULL, 'settings' => NULL, 'display' => NULL, 'layout' => NULL, 'renderer' => NULL),
110
          'path' => $data['path'],
111
          'file' => $data['file'],
112
        );
113

    
114
        // if no theme function exists, assume template.
115
        if (!function_exists("theme_$data[theme]")) {
116
          $theme[$data[$callback]]['template'] = str_replace('_', '-', $data[$callback]);
117
          $theme[$data[$callback]]['file'] = $data['file']; // for preprocess.
118
        }
119
      }
120
    }
121
  }
122

    
123
  $styles = panels_get_styles();
124
  foreach ($styles as $name => $data) {
125
    if (!empty($data['render pane'])) {
126
      $theme[$data['render pane']] = array(
127
        'variables' => array('content' => NULL, 'pane' => NULL, 'display' => NULL, 'style' => NULL, 'settings' => NULL),
128
        'path' => $data['path'],
129
        'file' => $data['file'],
130
      );
131
    }
132
    if (!empty($data['render region'])) {
133
      $theme[$data['render region']] = array(
134
        'variables' => array('display' => NULL, 'owner_id' => NULL, 'panes' => NULL, 'settings' => NULL, 'region_id' => NULL, 'style' => NULL),
135
        'path' => $data['path'],
136
        'file' => $data['file'],
137
      );
138
    }
139

    
140
    if (!empty($data['hook theme'])) {
141
      if (is_array($data['hook theme'])) {
142
        $theme += $data['hook theme'];
143
      }
144
      else if (function_exists($data['hook theme'])) {
145
        $data['hook theme']($theme, $data);
146
      }
147
    }
148
  }
149

    
150
  return $theme;
151
}
152

    
153
/**
154
 * Implementation of hook_menu
155
 */
156
function panels_menu() {
157
  // Safety: go away if CTools is not at an appropriate version.
158
  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
159
    return array();
160
  }
161
  $items = array();
162

    
163
  // Base AJAX router callback.
164
  $items['panels/ajax'] = array(
165
    'access arguments' => array('access content'),
166
    'page callback' => 'panels_ajax_router',
167
    'theme callback' => 'ajax_base_page_theme',
168
    'delivery callback' => 'ajax_deliver',
169
    'type' => MENU_CALLBACK,
170
  );
171

    
172
  $admin_base = array(
173
    'file' => 'includes/callbacks.inc',
174
    'access arguments' => array('use panels dashboard'),
175
  );
176
  // Provide a nice location for a panels admin panel.
177
  $items['admin/structure/panels'] = array(
178
    'title' => 'Panels',
179
    'page callback' => 'panels_admin_page',
180
    'description' => 'Get a bird\'s eye view of items related to Panels.',
181
  ) + $admin_base;
182

    
183
  $items['admin/structure/panels/dashboard'] = array(
184
    'title' => 'Dashboard',
185
    'page callback' => 'panels_admin_page',
186
    'type' => MENU_DEFAULT_LOCAL_TASK,
187
    'weight' => -10,
188
  ) + $admin_base;
189

    
190
  $items['admin/structure/panels/settings'] = array(
191
    'title' => 'Settings',
192
    'page callback' => 'drupal_get_form',
193
    'page arguments' => array('panels_admin_settings_page'),
194
    'type' => MENU_LOCAL_TASK,
195
  ) + $admin_base;
196

    
197
  $items['admin/structure/panels/settings/general'] = array(
198
    'title' => 'General',
199
    'page callback' => 'drupal_get_form',
200
    'page arguments' => array('panels_admin_settings_page'),
201
    'access arguments' => array('administer page manager'),
202
    'type' => MENU_DEFAULT_LOCAL_TASK,
203
    'weight' => -10,
204
  ) + $admin_base;
205

    
206
  if (module_exists('page_manager')) {
207
    $items['admin/structure/panels/settings/panel-page'] = array(
208
      'title' => 'Panel pages',
209
      'page callback' => 'panels_admin_panel_context_page',
210
      'type' => MENU_LOCAL_TASK,
211
      'weight' => -10,
212
    ) + $admin_base;
213
  }
214

    
215
  ctools_include('plugins', 'panels');
216
  $layouts = panels_get_layouts();
217
  foreach ($layouts as $name => $data) {
218
    if (!empty($data['hook menu'])) {
219
      if (is_array($data['hook menu'])) {
220
        $items += $data['hook menu'];
221
      }
222
      else if (function_exists($data['hook menu'])) {
223
        $data['hook menu']($items, $data);
224
      }
225
    }
226
  }
227

    
228

    
229
  return $items;
230
}
231

    
232
/**
233
 * Menu loader function to load a cache item for Panels AJAX.
234
 *
235
 * This load all of the includes needed to perform AJAX, and loads the
236
 * cache object and makes sure it is valid.
237
 */
238
function panels_edit_cache_load($cache_key) {
239
  ctools_include('display-edit', 'panels');
240
  ctools_include('plugins', 'panels');
241
  ctools_include('ajax');
242
  ctools_include('modal');
243
  ctools_include('context');
244

    
245
  return panels_edit_cache_get($cache_key);
246
}
247

    
248
/**
249
 * Implementation of hook_init()
250
 */
251
function panels_init() {
252
  // Safety: go away if CTools is not at an appropriate version.
253
  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
254
    if (user_access('administer site configuration')) {
255
      drupal_set_message(t('Panels is enabled but CTools is out of date. All Panels modules are disabled until CTools is updated. See the status page for more information.'), 'error');
256
    }
257
    return;
258
  }
259

    
260
  ctools_add_css('panels', 'panels');
261
  ctools_add_js('panels', 'panels');
262
}
263

    
264
/**
265
 * Implementation of hook_permission().
266
 *
267
 * @todo Almost all of these need to be moved into pipelines.
268
 */
269
function panels_permission() {
270
  return array(
271
    'use panels dashboard' => array(
272
      'title' => t("Use Panels Dashboard"),
273
      'description' => t("Allows a user to access the !link.", array('!link' => l('Panels Dashboard', 'admin/structure/panels'))),
274
    ),
275
    'view pane admin links' => array( // @todo
276
      'title' => t("View administrative links on Panel panes"),
277
      'description' => t(""),
278
    ),
279
    'administer pane access' => array( // @todo should we really have a global perm for this, or should it be moved into a pipeline question?
280
      'title' => t("Configure access settings on Panel panes"),
281
      'description' => t("Access rules (often also called visibility rules) can be configured on a per-pane basis. This permission allows users to configure those settings."),
282
    ),
283
    'use panels in place editing' => array(
284
      'title' => t("Use the Panels In-Place Editor"),
285
      'description' => t("Allows a user to utilize Panels' In-Place Editor."),
286
    ),
287
    'change layouts in place editing' => array(
288
      'title' => t("Change layouts with the Panels In-Place Editor"),
289
      'description' => t("Allows a user to change layouts with the IPE."),
290
    ),
291
    'administer advanced pane settings' => array(
292
      'title' => t("Configure advanced settings on Panel panes"),
293
      'description' => t(""),
294
    ),
295
    'administer panels layouts' => array(
296
      'title' => t("Administer Panels layouts"),
297
      'description' => t("Allows a user to administer exported Panels layout plugins & instances."),
298
    ),
299
    'administer panels styles' => array(
300
      'title' => t("Administer Panels styles"),
301
      'description' => t("Allows a user to administer the styles of Panel panes."),
302
    ),
303
    'use panels caching features' => array(
304
      'title' => t("Configure caching settings on Panels"),
305
      'description' => t("Allows a user to configure caching on Panels displays and panes."),
306
    ),
307
    'use panels locks' => array(
308
      'title' => t('Use panel locks'),
309
      'description' => t('Allows a user to lock and unlock panes in a panel display.'),
310
    ),
311
  );
312
}
313

    
314
/**
315
 * Implementation of hook_flush_caches().
316
 *
317
 * We implement this so that we can be sure our legacy rendering state setting
318
 * in $conf is updated whenever caches are cleared.
319
 */
320
//function panels_flush_caches() {
321
//  $legacy = panels_get_legacy_state();
322
//  $legacy->determineStatus();
323
//}
324

    
325
// ---------------------------------------------------------------------------
326
// CTools hook implementations
327
//
328
// These aren't core Drupal hooks but they are just as important.
329

    
330
/**
331
 * Implementation of hook_ctools_plugin_directory() to let the system know
332
 * we implement task and task_handler plugins.
333
 */
334
function panels_ctools_plugin_directory($module, $plugin) {
335
  // Safety: go away if CTools is not at an appropriate version.
336
  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
337
    return;
338
  }
339

    
340
  // We don't support the 'ctools' 'cache' plugin and pretending to causes
341
  // errors when they're in use.
342
  if ($module == 'ctools' && $plugin == 'cache') {
343
    return;
344
    // if we did we'd make a plugin/ctools_cache or something.
345
  }
346

    
347
  if ($module == 'page_manager' || $module == 'panels' || $module == 'ctools' || $module == 'stylizer') {
348
    // Panels and CTools both implement a 'cache' plugin but we don't implement
349
    // the CTools version.
350
    if ($module == 'ctools' && $plugin == 'cache') {
351
      return;
352
    }
353

    
354
    return 'plugins/' . $plugin;
355
  }
356
}
357

    
358
/**
359
 * Implements hook_ctools_plugin_type().
360
 *
361
 * Register layout, style, cache, and display_renderer plugin types, declaring
362
 * relevant plugin type information as necessary.
363
 */
364
function panels_ctools_plugin_type() {
365
  return array(
366
    'layouts' => array(
367
      'load themes' => TRUE, // Can define layouts in themes
368
      'process' => 'panels_layout_process',
369
      'child plugins' => TRUE,
370
    ),
371
    'styles' => array(
372
      'load themes' => TRUE,
373
      'process' => 'panels_plugin_styles_process',
374
      'child plugins' => TRUE,
375
    ),
376
    'cache' => array(),
377
    'display_renderers' => array(
378
      'classes' => array('renderer'),
379
    ),
380
  );
381
}
382

    
383
/**
384
 * Ensure a layout has a minimal set of data.
385
 */
386
function panels_layout_process(&$plugin) {
387
  $plugin += array(
388
    'category' => t('Miscellaneous'),
389
    'description' => '',
390
  );
391
}
392

    
393
/**
394
 * Implementation of hook_ctools_plugin_api().
395
 *
396
 * Inform CTools about version information for various plugins implemented by
397
 * Panels.
398
 *
399
 * @param string $owner
400
 *   The system name of the module owning the API about which information is
401
 *   being requested.
402
 * @param string $api
403
 *   The name of the API about which information is being requested.
404
 */
405
function panels_ctools_plugin_api($owner, $api) {
406
  if ($owner == 'panels' && $api == 'styles') {
407
    // As of 6.x-3.6, Panels has a slightly new system for style plugins.
408
    return array('version' => 2.0);
409
  }
410

    
411
  if ($owner == 'panels' && $api == 'pipelines') {
412
    return array(
413
      'version' => 1,
414
      'path' => drupal_get_path('module', 'panels') . '/includes',
415
    );
416
  }
417
}
418

    
419
/**
420
 * Implementation of hook_views_api().
421
 */
422
function panels_views_api() {
423
  return array(
424
    'api' => 2,
425
    'path' => drupal_get_path('module', 'panels') . '/plugins/views',
426
  );
427
}
428

    
429
/**
430
 * Perform additional processing on a style plugin.
431
 *
432
 * Currently this is only being used to apply versioning information to style
433
 * plugins in order to ensure the legacy renderer passes the right type of
434
 * parameters to a style plugin in a hybrid environment of both new and old
435
 * plugins.
436
 *
437
 * @see _ctools_process_data()
438
 *
439
 * @param array $plugin
440
 *   The style plugin that is being processed.
441
 * @param array $info
442
 *   The style plugin type info array.
443
 */
444
function panels_plugin_styles_process(&$plugin, $info) {
445
  $plugin += array(
446
    'weight' => 0,
447
  );
448

    
449
  $compliant_modules = ctools_plugin_api_info('panels', 'styles', 2.0, 2.0);
450
  $plugin['version'] = empty($compliant_modules[$plugin['module']]) ? 1.0 : $compliant_modules[$plugin['module']]['version'];
451
}
452

    
453
/**
454
 * Declare what style types Panels uses.
455
 */
456
function panels_ctools_style_base_types() {
457
  return array(
458
    'region' => array(
459
      'title' => t('Panel region'),
460
      'preview' => 'panels_stylizer_region_preview',
461
      'theme variables' => array('settings' => NULL, 'class' => NULL, 'content' => NULL),
462
    ),
463
    'pane' => array(
464
      'title' => t('Panel pane'),
465
      'preview' => 'panels_stylizer_pane_preview',
466
      'theme variables' => array('settings' => NULL, 'content' => NULL, 'pane' => NULL, 'display' => NULL),
467
    ),
468
  );
469
}
470

    
471
function panels_stylizer_lipsum() {
472
  return "
473
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus at velit dolor. Donec egestas tellus sit amet urna rhoncus adipiscing. Proin nec porttitor sem. Maecenas aliquam, purus nec tempus dignissim, nulla arcu aliquam diam, non tincidunt massa ante vel dolor. Aliquam sapien sapien, tincidunt id tristique at, pretium sagittis libero.</p>
474

    
475
    <p>Nulla facilisi. Curabitur lacinia, tellus sed tristique consequat, diam lorem scelerisque felis, at dictum purus augue facilisis lorem. Duis pharetra dignissim rutrum. Curabitur ac elit id dui dapibus tincidunt. Nulla eget sem quam, non eleifend eros. Cras porttitor tempus lectus ac scelerisque. Curabitur vehicula bibendum lorem, vitae ornare ligula venenatis ut.</p>
476
  ";
477
}
478

    
479
/**
480
 * Generate a preview given the current settings.
481
 */
482
function panels_stylizer_region_preview($plugin, $settings) {
483
  ctools_stylizer_add_css($plugin, $settings);
484
  return theme($plugin['theme'], array('settings' => $settings, 'class' => ctools_stylizer_get_css_class($plugin, $settings), 'content' => panels_stylizer_lipsum()));
485
}
486

    
487
/**
488
 * Generate a preview given the current settings.
489
 */
490
function panels_stylizer_pane_preview($plugin, $settings) {
491
  ctools_stylizer_add_css($plugin, $settings);
492
  $pane = new stdClass();
493

    
494
  $content = new stdClass;
495
  $content->title = t('Lorem ipsum');
496
  $content->content = panels_stylizer_lipsum();
497
  $content->type = 'dummy';
498
  $content->subtype = 'dummy';
499

    
500
  $content->css_class = ctools_stylizer_get_css_class($plugin, $settings);
501

    
502
  $display = new panels_display();
503

    
504
  if (!empty($plugin['theme'])) {
505
    return theme($plugin['theme'], array('settings' => $settings, 'content' => $content, 'pane' => $pane, 'display' => $display));
506
  }
507
  else {
508
    return theme('panels_pane', array('content' => $content, 'pane' => $pane, 'display' => $display));
509
  }
510
}
511

    
512
// ---------------------------------------------------------------------------
513
// Panels display editing
514

    
515
/**
516
 * @defgroup mainapi Functions comprising the main panels API
517
 * @{
518
 */
519

    
520
/**
521
 * Main API entry point to edit a panel display.
522
 *
523
 * Sample implementations utiltizing the the complex $destination behavior can be found
524
 * in panels_page_edit_content() and, in a separate contrib module, OG Blueprints
525
 * (http://drupal.org/project/og_blueprints), og_blueprints_blueprint_edit().
526
 *
527
 * @ingroup mainapi
528
 *
529
 * @param object $display instanceof panels_display \n
530
 *  A fully loaded panels $display object, as returned from panels_load_display().
531
 *  Merely passing a did is NOT sufficient. \n
532
 *  Note that 'fully loaded' means the $display must already be loaded with any contexts
533
 *  the caller wishes to have set for the display.
534
 * @param mixed $destination \n
535
 *  The redirect destination that the user should be taken to on form submission or
536
 *  cancellation. With panels_edit, $destination has complex effects on the return
537
 *  values of panels_edit() once the form has been submitted. See the explanation of
538
 *  the return value below to understand the different types of values returned by panels_edit()
539
 *  at different stages of FAPI. Under most circumstances, simply passing in
540
 *  drupal_get_destination() is all that's necessary.
541
 * @param array $content_types \n
542
 *  An associative array of allowed content types, typically as returned from
543
 *  panels_common_get_allowed_types(). Note that context partially governs available content types,
544
 *  so you will want to create any relevant contexts using panels_create_context() or
545
 *  panels_create_context_empty() to make sure all the appropriate content types are available.
546
 *
547
 * @return
548
 *  Because the functions called by panels_edit() invoke the form API, this function
549
 *  returns different values depending on the stage of form submission we're at. In Drupal 5,
550
 *  the phase of form submission is indicated by the contents of $_POST['op']. Here's what you'll
551
 *  get at different stages:
552
 *    -# If !$_POST['op']: then we're on on the initial passthrough and the form is being
553
 *       rendered, so it's the $form itself that's being returned. Because negative margins,
554
 *       a common CSS technique, bork the display editor's ajax drag-and-drop, it's important
555
 *       that the $output be printed, not returned. Use this syntax in the caller function: \n
556
 *          print theme('page', panels_edit($display, $destination, $content_types), FALSE); \n
557
 *    -# If $_POST['op'] == t('Cancel'): form submission has been cancelled. If empty($destination) == FALSE,
558
 *       then there is no return value and the panels API takes care of redirecting to $destination.
559
 *       If empty($destination) == TRUE, then there's still no return value, but the caller function
560
 *       has to take care of form redirection.
561
 *    -# If $_POST['op'] == ('Save'): the form has been submitted successfully and has run through
562
 *        panels_edit_display_submit(). $output depends on the value of $destination:
563
 *      - If empty($destination) == TRUE: $output contains the modified $display
564
 *        object, and no redirection will occur. This option is useful if the caller
565
 *        needs to perform additional operations on or with the modified $display before
566
 *        the page request is complete. Using hook_form_alter() to add an additional submit
567
 *        handler is typically the preferred method for something like this, but there
568
 *        are certain use cases where that is infeasible and $destination = NULL should
569
 *        be used instead. If this method is employed, the caller will need to handle form
570
 *        redirection. Note that having $_REQUEST['destination'] set, whether via
571
 *        drupal_get_destination() or some other method, will NOT interfere with this
572
 *        functionality; consequently, you can use drupal_get_destination() to safely store
573
 *        your desired redirect in the caller function, then simply use drupal_goto() once
574
 *        panels_edit() has done its business.
575
 *      - If empty($destination) == FALSE: the form will redirect to the URL string
576
 *        given in $destination and NO value will be returned.
577
 */
578
function panels_edit($display, $destination = NULL, $content_types = NULL, $title = FALSE) {
579
  ctools_include('display-edit', 'panels');
580
  ctools_include('ajax');
581
  ctools_include('plugins', 'panels');
582
  return _panels_edit($display, $destination, $content_types, $title);
583
}
584

    
585
/**
586
 * API entry point for selecting a layout for a given display.
587
 *
588
 * Layout selection is nothing more than a list of radio items encompassing the available
589
 * layouts for this display, as defined by .inc files in the panels/layouts subdirectory.
590
 * The only real complexity occurs when a user attempts to change the layout of a display
591
 * that has some content in it.
592
 *
593
 * @param object $display instanceof panels_display \n
594
 *  A fully loaded panels $display object, as returned from panels_load_display().
595
 *  Merely passing a did is NOT sufficient.
596
 * @param string $finish
597
 *  A string that will be used for the text of the form submission button. If no value is provided,
598
 *  then the form submission button will default to t('Save').
599
 * @param mixed $destination
600
 *  Basic usage is a string containing the URL that the form should redirect to upon submission.
601
 *  For a discussion of advanced usages, see panels_edit().
602
 * @param mixed $allowed_layouts
603
 *  Allowed layouts has three different behaviors that depend on which of three value types
604
 *  are passed in by the caller:
605
 *    #- if $allowed_layouts instanceof panels_allowed_layouts (includes subclasses): the most
606
 *       complex use of the API. The caller is passing in a loaded panels_allowed_layouts object
607
 *       that the client module previously created and stored somewhere using a custom storage
608
 *       mechanism.
609
 *    #- if is_string($allowed_layouts): the string will be used in a call to variable_get() which
610
 *       will call the $allowed_layouts . '_allowed_layouts' var. If the data was stored properly
611
 *       in the system var, the $allowed_layouts object will be unserialized and recreated.
612
 *       @see panels_common_set_allowed_layouts()
613
 *    #- if is_null($allowed_layouts): the default behavior, which also provides backwards
614
 *       compatibility for implementations of the Panels2 API written before beta4. In this case,
615
 *       a dummy panels_allowed_layouts object is created which does not restrict any layouts.
616
 *       Subsequent behavior is indistinguishable from pre-beta4 behavior.
617
 *
618
 * @return
619
 *  Can return nothing, or a modified $display object, or a redirection string; return values for the
620
 *  panels_edit* family of functions are quite complex. See panels_edit() for detailed discussion.
621
 * @see panels_edit()
622
 */
623
function panels_edit_layout($display, $finish, $destination = NULL, $allowed_layouts = NULL) {
624
  ctools_include('display-layout', 'panels');
625
  ctools_include('plugins', 'panels');
626
  return _panels_edit_layout($display, $finish, $destination, $allowed_layouts);
627
}
628

    
629
// ---------------------------------------------------------------------------
630
// Panels database functions
631

    
632
/**
633
 * Forms the basis of a panel display
634
 *
635
 */
636
class panels_display {
637
  var $args = array();
638
  var $content = array();
639
  var $panels = array();
640
  var $incoming_content = NULL;
641
  var $css_id = NULL;
642
  var $context = array();
643
  var $did = 'new';
644
  var $renderer = 'standard';
645

    
646
  function add_pane(&$pane, $location = NULL) {
647
    // If no location specified, use what's set in the pane.
648
    if (empty($location)) {
649
      $location = $pane->panel;
650
    }
651
    else {
652
      $pane->panel = $location;
653
    }
654

    
655
    // Get a temporary pid for this pane.
656
    $pane->pid = "new-" . $this->next_new_pid();
657

    
658
    // Add the pane to the approprate spots.
659
    $this->content[$pane->pid] = &$pane;
660
    $this->panels[$location][] = $pane->pid;
661
  }
662

    
663
  function duplicate_pane($pid, $location = FALSE) {
664
    $pane = $this->clone_pane($pid);
665
    $this->add_pane($pane, $location);
666
  }
667

    
668
  function clone_pane($pid) {
669
    $pane = clone $this->content[$pid];
670
    return $pane;
671
  }
672

    
673
  function next_new_pid() {
674
    // We don't use static vars to record the next new pid because
675
    // temporary pids can last for years in exports and in caching
676
    // during editing.
677
    $id = array(0);
678
    foreach (array_keys($this->content) as $pid) {
679
      if (!is_numeric($pid)) {
680
        $id[] = substr($pid, 4);
681
      }
682
    }
683
    $next_id = max($id);
684
    return ++$next_id;
685
  }
686

    
687
  /**
688
   * Get the title from a display.
689
   *
690
   * The display must have already been rendered, or the setting to set the
691
   * display's title from a pane's title will not have worked.
692
   *
693
   * @return
694
   *   The title to use. If NULL, this means to let any default title that may be in use
695
   *   pass through. i.e, do not actually set the title.
696
   */
697
  function get_title() {
698
    switch ($this->hide_title) {
699
      case PANELS_TITLE_NONE:
700
        return '';
701

    
702
      case PANELS_TITLE_PANE:
703
        return isset($this->stored_pane_title) ? $this->stored_pane_title : '';
704

    
705
      case PANELS_TITLE_FIXED:
706
      case FALSE; // For old exported panels that are not in the database.
707
        if (!empty($this->title)) {
708
          return filter_xss_admin(ctools_context_keyword_substitute($this->title, array(), $this->context));
709
        }
710
        return NULL;
711
    }
712
  }
713

    
714
  /**
715
   * Render this panels display.
716
   *
717
   * After checking to ensure the designated layout plugin is valid, a
718
   * display renderer object is spawned and runs its rendering logic.
719
   *
720
   * @param mixed $renderer
721
   *    An instantiated display renderer object, or the name of a display
722
   *    renderer plugin+class to be fetched. Defaults to NULL. When NULL, the
723
   *    predesignated display renderer will be used.
724
   */
725
  function render($renderer = NULL) {
726
    $layout = panels_get_layout($this->layout);
727
    if (!$layout) {
728
      return NULL;
729
    }
730

    
731
    // If we were not given a renderer object, load it.
732
    if (!is_object($renderer)) {
733
      // If the renderer was not specified, default to $this->renderer
734
      // which is either standard or was already set for us.
735
      $renderer = panels_get_renderer_handler(!empty($renderer) ? $renderer : $this->renderer, $this);
736
      if (!$renderer) {
737
        return NULL;
738
      }
739
    }
740

    
741
    $output = '';
742
    // Let modules act just prior to render.
743
    foreach (module_implements('panels_pre_render') as $module) {
744
      $function = $module . '_panels_pre_render';
745
      $output .= $function($this, $renderer);
746
    }
747

    
748
    $output .= $renderer->render();
749

    
750
    // Let modules act just after render.
751
    foreach (module_implements('panels_post_render') as $module) {
752
      $function = $module . '_panels_post_render';
753
      $output .= $function($this, $renderer);
754
    }
755
    return $output;
756
  }
757
}
758

    
759
/**
760
 * }@ End of 'defgroup mainapi', although other functions are specifically added later
761
 */
762

    
763
/**
764
 * Creates a new display, setting the ID to our magic new id.
765
 */
766
function panels_new_display() {
767
  ctools_include('export');
768
  $display = ctools_export_new_object('panels_display', FALSE);
769
  $display->did = 'new';
770
  return $display;
771
}
772

    
773
/**
774
 * Create a new pane.
775
 *
776
 * @todo -- use schema API for some of this?
777
 */
778
function panels_new_pane($type, $subtype, $set_defaults = FALSE) {
779
  ctools_include('export');
780
  $pane = ctools_export_new_object('panels_pane', FALSE);
781
  $pane->pid = 'new';
782
  $pane->type = $type;
783
  $pane->subtype = $subtype;
784
  if ($set_defaults) {
785
    ctools_include('content');
786
    $content_type = ctools_get_content_type($type);
787
    $content_subtype = ctools_content_get_subtype($content_type, $subtype);
788
    $pane->configuration = ctools_content_get_defaults($content_type, $content_subtype);
789
  }
790

    
791
  return $pane;
792
}
793

    
794
/**
795
 * Load and fill the requested $display object(s).
796
 *
797
 * Helper function primarily for for panels_load_display().
798
 *
799
 * @param array $dids
800
 *  An indexed array of dids to be loaded from the database.
801
 *
802
 * @return $displays
803
 *  An array of displays, keyed by their display dids.
804
 *
805
 * @todo schema API can drasticly simplify this code.
806
 */
807
function panels_load_displays($dids) {
808
  $displays = array();
809
  if (empty($dids) || !is_array($dids)) {
810
    return $displays;
811
  }
812

    
813
  $result = db_query("SELECT * FROM {panels_display} WHERE did IN (:dids)", array(':dids' => $dids));
814

    
815
  ctools_include('export');
816
  foreach ($result as $obj) {
817
    $displays[$obj->did] = ctools_export_unpack_object('panels_display', $obj);
818
    // Modify the hide_title field to go from a bool to an int if necessary.
819
  }
820

    
821
  $result = db_query("SELECT * FROM {panels_pane} WHERE did IN (:dids) ORDER BY did, panel, position", array(':dids' => $dids));
822
  foreach ($result as $obj) {
823
    $pane = ctools_export_unpack_object('panels_pane', $obj);
824

    
825
    $displays[$pane->did]->panels[$pane->panel][] = $pane->pid;
826
    $displays[$pane->did]->content[$pane->pid] = $pane;
827
  }
828

    
829
  return $displays;
830
}
831

    
832
/**
833
 * Load a single display.
834
 *
835
 * @ingroup mainapi
836
 *
837
 * @param int $did
838
 *  The display id (did) of the display to be loaded.
839
 *
840
 * @return object $display instanceof panels_display \n
841
 *  Returns a partially-loaded panels_display object. $display objects returned from
842
 *  from this function have only the following data:
843
 *    - $display->did (the display id)
844
 *    - $display->name (the 'name' of the display, where applicable - it often isn't)
845
 *    - $display->layout (a string with the system name of the display's layout)
846
 *    - $display->panel_settings (custom layout style settings contained in an associative array; NULL if none)
847
 *    - $display->layout_settings (panel size and configuration settings for Flexible layouts; NULL if none)
848
 *    - $display->css_id (the special css_id that has been assigned to this display, if any; NULL if none)
849
 *    - $display->content (an array of pane objects, keyed by pane id (pid))
850
 *    - $display->panels (an associative array of panel regions, each an indexed array of pids in the order they appear in that region)
851
 *    - $display->cache (any relevant data from panels_simple_cache)
852
 *    - $display->args
853
 *    - $display->incoming_content
854
 *
855
 * While all of these members are defined, $display->context is NEVER defined in the returned $display;
856
 * it must be set using one of the ctools_context_create() functions.
857
 */
858
function panels_load_display($did) {
859
  $displays = panels_load_displays(array($did));
860
  if (!empty($displays)) {
861
    return array_shift($displays);
862
  }
863
}
864

    
865
/**
866
 * Save a display object.
867
 *
868
 * @ingroup mainapi
869
 *
870
 * Note a new $display only receives a real did once it is run through this function.
871
 * Until then, it uses a string placeholder, 'new', in place of a real did. The same
872
 * applies to all new panes (whether on a new $display or not); in addition,
873
 * panes have sequential numbers appended, of the form 'new-1', 'new-2', etc.
874
 *
875
 * @param object $display instanceof panels_display \n
876
 *  The display object to be saved. Passed by reference so the caller need not use
877
 *  the return value for any reason except convenience.
878
 *
879
 * @return object $display instanceof panels_display \n
880
 */
881
function panels_save_display(&$display) {
882
  $update = (isset($display->did) && is_numeric($display->did)) ? array('did') : array();
883
  drupal_write_record('panels_display', $display, $update);
884

    
885
  $pids = array();
886
  if ($update) {
887
    // Get a list of all panes currently in the database for this display so we can know if there
888
    // are panes that need to be deleted. (i.e, aren't currently in our list of panes).
889
    $result = db_query("SELECT pid FROM {panels_pane} WHERE did = :did", array(':did' => $display->did));
890
    foreach ($result as $pane) {
891
      $pids[$pane->pid] = $pane->pid;
892
    }
893
  }
894

    
895
  // update all the panes
896
  ctools_include('plugins', 'panels');
897
  ctools_include('content');
898

    
899
  foreach ($display->panels as $id => $panes) {
900
    $position = 0;
901
    $new_panes = array();
902
    foreach ((array) $panes as $pid) {
903
      if (!isset($display->content[$pid])) {
904
        continue;
905
      }
906
      $pane = $display->content[$pid];
907
      $type = ctools_get_content_type($pane->type);
908

    
909
      $pane->position = $position++;
910
      $pane->did = $display->did;
911

    
912
      $old_pid = $pane->pid;
913
      drupal_write_record('panels_pane', $pane, is_numeric($pid) ? array('pid') : array());
914

    
915
      if ($pane->pid != $old_pid) {
916
        // and put it back so our pids and positions can be used
917
        unset($display->content[$id]);
918
        $display->content[$pane->pid] = $pane;
919

    
920
        // If the title pane was one of our panes that just got its ID changed,
921
        // we need to change it in the database, too.
922
        if (isset($display->title_pane) && $display->title_pane == $old_pid) {
923
          $display->title_pane = $pane->pid;
924
          // Do a simple update query to write it so we don't have to rewrite
925
          // the whole record. We can't just save writing the whole record here
926
          // because it was needed to get the did. Chicken, egg, more chicken.
927
          db_update('panels_display')
928
            ->fields(array(
929
              'title_pane' => $pane->pid
930
            ))
931
            ->condition('did', $display->did)
932
            ->execute();
933
        }
934
      }
935

    
936
      // re-add this to the list of content for this panel.
937
      $new_panes[] = $pane->pid;
938

    
939
      // Remove this from the list of panes scheduled for deletion.
940
      if (isset($pids[$pane->pid])) {
941
        unset($pids[$pane->pid]);
942
      }
943
    }
944

    
945
    $display->panels[$id] = $new_panes;
946
  }
947
  if (!empty($pids)) {
948
    db_delete('panels_pane')->condition('pid', $pids)->execute();
949
  }
950

    
951
  // Clear any cached content for this display.
952
  panels_clear_cached_content($display);
953

    
954
  // Allow other modules to take action when a display is saved.
955
  module_invoke_all('panels_display_save', $display);
956

    
957
  // Log the change to watchdog, using the same style as node.module
958
  $watchdog_args = array('%did' => $display->did);
959
  if (!empty($display->title)) {
960
    $watchdog_args['%title'] = $display->title;
961
    watchdog('content', 'Panels: saved display "%title" with display id %did', $watchdog_args, WATCHDOG_NOTICE);
962
  }
963
  else {
964
    watchdog('content', 'Panels: saved display with id %did', $watchdog_args, WATCHDOG_NOTICE);
965
  }
966

    
967
  // to be nice, even tho we have a reference.
968
  return $display;
969
}
970

    
971
/**
972
 * Delete a display.
973
 */
974
function panels_delete_display($display) {
975
  if (is_object($display)) {
976
    $did = $display->did;
977
  }
978
  else {
979
    $did = $display;
980
  }
981
  db_delete('panels_display')->condition('did', $did)->execute();
982
  db_delete('panels_pane')->condition('did', $did)->execute();
983
}
984

    
985
/**
986
 * Exports the provided display into portable code.
987
 *
988
 * This function is primarily intended as a mechanism for cloning displays.
989
 * It generates an exact replica (in code) of the provided $display, with
990
 * the exception that it replaces all ids (dids and pids) with 'new-*' values.
991
 * Only once panels_save_display() is called on the code version of $display will
992
 * the exported display written to the database and permanently saved.
993
 *
994
 * @see panels_page_export() or _panels_page_fetch_display() for sample implementations.
995
 *
996
 * @ingroup mainapi
997
 *
998
 * @param object $display instanceof panels_display \n
999
 *  This export function does no loading of additional data about the provided
1000
 *  display. Consequently, the caller should make sure that all the desired data
1001
 *  has been loaded into the $display before calling this function.
1002
 * @param string $prefix
1003
 *  A string prefix that is prepended to each line of exported code. This is primarily
1004
 *  used for prepending a double space when exporting so that the code indents and lines up nicely.
1005
 *
1006
 * @return string $output
1007
 *  The passed-in $display expressed as code, ready to be imported. Import by running
1008
 *  eval($output) in the caller function; doing so will create a new $display variable
1009
 *  with all the exported values. Note that if you have already defined a $display variable in
1010
 *  the same scope as where you eval(), your existing $display variable WILL be overwritten.
1011
 */
1012
function panels_export_display($display, $prefix = '') {
1013
  ctools_include('export');
1014
  $output = ctools_export_object('panels_display', $display, $prefix);
1015

    
1016
  $pid_counter = &drupal_static(__FUNCTION__, 0);
1017

    
1018
  // Initialize empty properties.
1019
  $output .= $prefix . '$display->content = array()' . ";\n";
1020
  $output .= $prefix . '$display->panels = array()' . ";\n";
1021
  $panels = array();
1022

    
1023
  $title_pid = 0;
1024
  if (!empty($display->content)) {
1025
    $region_counters = array();
1026
    foreach ($display->content as $pane) {
1027
      $pid = 'new-' . ++$pid_counter;
1028
      if ($pane->pid == $display->title_pane) {
1029
        $title_pid = $pid;
1030
      }
1031
      $pane->pid = $pid;
1032
      $output .= ctools_export_object('panels_pane', $pane, $prefix . '  ');
1033
      $output .= "$prefix  " . '$display->content[\'' . $pane->pid . '\'] = $pane' . ";\n";
1034
      if (!isset($region_counters[$pane->panel])) {
1035
        $region_counters[$pane->panel] = 0;
1036
      }
1037
      $output .= "$prefix  " . '$display->panels[\'' . $pane->panel . '\'][' . $region_counters[$pane->panel]++ .'] = \'' . $pane->pid . "';\n";
1038
    }
1039
  }
1040
  $output .= $prefix . '$display->hide_title = ';
1041
  switch ($display->hide_title) {
1042
    case PANELS_TITLE_FIXED:
1043
      $output .= 'PANELS_TITLE_FIXED';
1044
      break;
1045
    case PANELS_TITLE_NONE:
1046
      $output .= 'PANELS_TITLE_NONE';
1047
      break;
1048
    case PANELS_TITLE_PANE:
1049
      $output .= 'PANELS_TITLE_PANE';
1050
      break;
1051
  }
1052
  $output .= ";\n";
1053

    
1054
  $output .= $prefix . '$display->title_pane =' . " '$title_pid';\n";
1055
  return $output;
1056
}
1057

    
1058
/**
1059
 * Render a display by loading the content into an appropriate
1060
 * array and then passing through to panels_render_layout.
1061
 *
1062
 * if $incoming_content is NULL, default content will be applied. Use
1063
 * an empty string to indicate no content.
1064
 * @ingroup hook_invocations
1065
 */
1066
function panels_render_display(&$display, $renderer = NULL) {
1067
  ctools_include('plugins', 'panels');
1068
  ctools_include('context');
1069

    
1070
  if (!empty($display->context)) {
1071
    if ($form_context = ctools_context_get_form($display->context)) {
1072
      $form_context->form['#theme'] = 'panels_render_display_form';
1073
      $form_context->form['#display'] = &$display;
1074
      return $form_context->form;
1075
    }
1076
  }
1077
  return $display->render($renderer);
1078
}
1079

    
1080
/**
1081
 * Theme function to render our panel as a form.
1082
 *
1083
 * When rendering a display as a form, the entire display needs to be
1084
 * inside the <form> tag so that the form can be spread across the
1085
 * panes. This sets up the form system to be the main caller and we
1086
 * then operate as a theme function of the form.
1087
 */
1088
function theme_panels_render_display_form($vars) {
1089
  // @todo this is probably broken in D7
1090
  $render = $vars['element']['#display']->render();
1091
  $vars['element']['#children'] = $render;
1092
  return theme('form', $vars);
1093
}
1094

    
1095
// @layout
1096
function panels_print_layout_icon($id, $layout, $title = NULL) {
1097
  ctools_add_css('panels_admin', 'panels');
1098
  $file = $layout['path'] . '/' . $layout['icon'];
1099
  return theme('panels_layout_icon', array('id' => $id, 'image' => theme('image', array('path' => $file, 'alt' => strip_tags($layout['title']), 'title' => strip_tags($layout['description']))), 'title' => $title));
1100
}
1101

    
1102
/**
1103
 * Theme the layout icon image
1104
 * @layout
1105
 * @todo move to theme.inc
1106
 */
1107
function theme_panels_layout_icon($vars) {
1108
  $id = $vars['id'];
1109
  $image = $vars['image'];
1110
  $title = $vars['title'];
1111

    
1112
  $output = '<div class="layout-icon">';
1113
  $output .= $image;
1114
  if ($title) {
1115
    $output .= '<div class="caption">' . $title . '</div>';
1116
  }
1117
  $output .= '</div>';
1118
  return $output;
1119
}
1120

    
1121
/**
1122
 * Theme the layout link image
1123
 * @layout
1124
 *
1125
 * @todo Why isn't this a template at this point?
1126
 * @todo Why does this take 4 arguments but only makes use of two?
1127
 */
1128
function theme_panels_layout_link($vars) {
1129
  $title = $vars['title'];
1130
  $image = $vars['image'];
1131
  $class = $vars['class'];
1132

    
1133
  $output = '<div class="' . implode(' ', $class) . '">';
1134
  $output .= $vars['image'];
1135
  $output .= '<div>' . $vars['title'] . '</div>';
1136
  $output .= '</div>';
1137
  return $output;
1138
}
1139

    
1140
/**
1141
 * Print the layout link. Sends out to a theme function.
1142
 * @layout
1143
 */
1144
function panels_print_layout_link($id, $layout, $link, $options = array(), $current_layout = FALSE) {
1145
  if (isset($options['query']['q'])) {
1146
    unset($options['query']['q']);
1147
  }
1148

    
1149
  // Setup classes for layout link, including current-layout information
1150
  $class = array('layout-link');
1151
  if ($current_layout == $id) {
1152
    $options['attributes']['class'][] = 'current-layout-link';
1153
    $class[] = 'current-layout';
1154
  }
1155

    
1156
  ctools_add_css('panels_admin', 'panels');
1157
  $file = $layout['path'] . '/' . $layout['icon'];
1158
  $image = l(theme('image', array('path' => $file)), $link, array('html' => true) + $options);
1159
  $title = l($layout['title'], $link, $options);
1160
  return theme('panels_layout_link', array('title' => $title, 'image' => $image, 'class' => $class));
1161
}
1162

    
1163

    
1164
/**
1165
 * Gateway to the PanelsLegacyState class/object, which does all legacy state
1166
 * checks and provides information about the cause of legacy states as needed.
1167
 *
1168
 * @return PanelsLegacyState $legacy
1169
 */
1170
function panels_get_legacy_state() {
1171
  static $legacy = NULL;
1172
  if (!isset($legacy)) {
1173
    ctools_include('legacy', 'panels');
1174
    $legacy = new PanelsLegacyState();
1175
  }
1176
  return $legacy;
1177
}
1178

    
1179
/**
1180
 * Get the display that is currently being rendered as a page.
1181
 *
1182
 * Unlike in previous versions of this, this only returns the display,
1183
 * not the page itself, because there are a number of different ways
1184
 * to get to this point. It is hoped that the page data isn't needed
1185
 * at this point. If it turns out there is, we will do something else to
1186
 * get that functionality.
1187
 */
1188
function panels_get_current_page_display($change = NULL) {
1189
  static $display = NULL;
1190
  if ($change) {
1191
    $display = $change;
1192
  }
1193

    
1194
  return $display;
1195
}
1196

    
1197
/**
1198
 * Clean up the panel pane variables for the template.
1199
 */
1200
function template_preprocess_panels_pane(&$vars) {
1201
  $content = &$vars['content'];
1202

    
1203
  $vars['contextual_links'] = array();
1204
  $vars['classes_array'] = array();
1205
  $vars['admin_links'] = '';
1206

    
1207
  if (module_exists('contextual') && user_access('access contextual links')) {
1208
    $links = array();
1209
    // These are specified by the content.
1210
    if (!empty($content->admin_links)) {
1211
      $links += $content->admin_links;
1212
    }
1213

    
1214
    // Take any that may have been in the render array we were given and
1215
    // move them up so they appear outside the pane properly.
1216
    if (is_array($content->content) && isset($content->content['#contextual_links'])) {
1217
      $element = array(
1218
        '#type' => 'contextual_links',
1219
        '#contextual_links' => $content->content['#contextual_links'],
1220
      );
1221
      unset($content->content['#contextual_links']);
1222

    
1223
      // Add content to $element array
1224
      if (is_array($content->content)) {
1225
        $element['#element'] = $content->content;
1226
      }
1227

    
1228
      $element = contextual_pre_render_links($element);
1229
      $links += $element['#links'];
1230
    }
1231

    
1232
    if ($links) {
1233
      $build = array(
1234
        '#prefix' => '<div class="contextual-links-wrapper">',
1235
        '#suffix' => '</div>',
1236
        '#theme' => 'links__contextual',
1237
        '#links' => $links,
1238
        '#attributes' => array('class' => array('contextual-links')),
1239
        '#attached' => array(
1240
          'library' => array(array('contextual', 'contextual-links')),
1241
        ),
1242
      );
1243
      $vars['classes_array'][] = 'contextual-links-region';
1244
      $vars['admin_links'] = drupal_render($build);
1245
    }
1246
  }
1247

    
1248
  // basic classes
1249
  $vars['classes_array'][] = 'panel-pane';
1250
  $vars['id'] = '';
1251

    
1252
  // Add some usable classes based on type/subtype
1253
  ctools_include('cleanstring');
1254
  $type_class = $content->type ? 'pane-'. ctools_cleanstring($content->type, array('lower case' => TRUE)) : '';
1255
  $subtype_class = $content->subtype ? 'pane-'. ctools_cleanstring($content->subtype, array('lower case' => TRUE)) : '';
1256

    
1257
  // Sometimes type and subtype are the same. Avoid redundant classes.
1258
  $vars['classes_array'][] = $type_class;
1259
  if ($type_class != $subtype_class) {
1260
    $vars['classes_array'][] = $subtype_class;
1261
  }
1262

    
1263
  // Add id and custom class if sent in.
1264
  if (!empty($content->content)) {
1265
    if (!empty($content->css_id)) {
1266
      $vars['id'] = ' id="' . $content->css_id . '"';
1267
    }
1268
    if (!empty($content->css_class)) {
1269
      $vars['classes_array'][] = $content->css_class;
1270
    }
1271
  }
1272

    
1273
  // Set up some placeholders for constructing template file names.
1274
  $base = 'panels_pane';
1275
  $delimiter = '__';
1276

    
1277
  // Add template file suggestion for content type and sub-type.
1278
  $vars['theme_hook_suggestions'][] = $base . $delimiter . $content->type;
1279
  $vars['theme_hook_suggestions'][] = $base . $delimiter . strtr($content->type, '-', '_') . $delimiter . strtr($content->subtype, '-', '_');
1280

    
1281
  $vars['pane_prefix'] = !empty($content->pane_prefix) ? $content->pane_prefix : '';
1282
  $vars['pane_suffix'] = !empty($content->pane_suffix) ? $content->pane_suffix : '';
1283

    
1284
  $vars['title'] = !empty($content->title) ? $content->title : '';
1285
  $vars['title_attributes_array']['class'][] = 'pane-title';
1286

    
1287
  $vars['feeds'] = !empty($content->feeds) ? implode(' ', $content->feeds) : '';
1288

    
1289
  $vars['links'] = !empty($content->links) ? theme('links', array('links' => $content->links)) : '';
1290
  $vars['more'] = '';
1291
  if (!empty($content->more)) {
1292
    if (empty($content->more['title'])) {
1293
      $content->more['title'] = t('more');
1294
    }
1295
    $vars['more'] = l($content->more['title'], $content->more['href'], $content->more);
1296
  }
1297

    
1298
  $vars['content'] = !empty($content->content) ? $content->content : '';
1299

    
1300
}
1301

    
1302
/**
1303
 * Route Panels' AJAX calls to the correct object.
1304
 *
1305
 * Panels' AJAX is controlled mostly by renderer objects. This menu callback
1306
 * accepts the incoming request, figures out which object should handle the
1307
 * request, and attempts to route it. If no object can be found, the default
1308
 * Panels editor object is used.
1309
 *
1310
 * Calls are routed via the ajax_* method space. For example, if visiting
1311
 * panels/ajax/add-pane then $renderer::ajax_add_pane() will be called.
1312
 * This means commands can be added without having to create new callbacks.
1313
 *
1314
 * The first argument *must always* be the cache key so that a cache object
1315
 * can be passed through. Other arguments will be passed through untouched
1316
 * so that the method can do whatever it needs to do.
1317
 */
1318
function panels_ajax_router() {
1319
  $args = func_get_args();
1320
  if (count($args) < 3) {
1321
    return MENU_NOT_FOUND;
1322
  }
1323

    
1324
  ctools_include('display-edit', 'panels');
1325
  ctools_include('plugins', 'panels');
1326
  ctools_include('ajax');
1327
  ctools_include('modal');
1328
  ctools_include('context');
1329
  ctools_include('content');
1330

    
1331
  $plugin_name = array_shift($args);
1332
  $method = array_shift($args);
1333
  $cache_key = array_shift($args);
1334

    
1335
  $plugin = panels_get_display_renderer($plugin_name);
1336
  if (!$plugin) {
1337
    // This is the default renderer for handling AJAX commands.
1338
    $plugin = panels_get_display_renderer('editor');
1339
  }
1340

    
1341
  $cache = panels_edit_cache_get($cache_key);
1342
  if (empty($cache)) {
1343
    return MENU_ACCESS_DENIED;
1344
  }
1345

    
1346
  $renderer = panels_get_renderer_handler($plugin, $cache->display);
1347
  if (!$renderer) {
1348
    return MENU_ACCESS_DENIED;
1349
  }
1350

    
1351
  $method = 'ajax_' . str_replace('-', '_', $method);
1352
  if (!method_exists($renderer, $method)) {
1353
    return MENU_NOT_FOUND;
1354
  }
1355

    
1356
  $renderer->cache = &$cache;
1357
  ctools_include('cleanstring');
1358
  $renderer->clean_key = ctools_cleanstring($cache_key);
1359

    
1360
  $output = call_user_func_array(array($renderer, $method), $args);
1361

    
1362
  if (empty($output) && !empty($renderer->commands)) {
1363
    return array(
1364
      '#type' => 'ajax',
1365
      '#commands' => $renderer->commands,
1366
    );
1367
  }
1368
  else {
1369
    return $output;
1370
  }
1371
}
1372

    
1373
// --------------------------------------------------------------------------
1374
// Panels caching functions and callbacks
1375
//
1376
// When editing displays and the like, Panels has a caching system that relies
1377
// on a callback to determine where to get the actual cache.
1378

    
1379
// @todo This system needs to be better documented so that it can be
1380
// better used.
1381

    
1382
/**
1383
 * Get an object from cache.
1384
 */
1385
function panels_cache_get($obj, $did, $skip_cache = FALSE) {
1386
  ctools_include('object-cache');
1387
  // we often store contexts in cache, so let's just make sure we can load
1388
  // them.
1389
  ctools_include('context');
1390
  return ctools_object_cache_get($obj, 'panels_display:' . $did, $skip_cache);
1391
}
1392

    
1393
/**
1394
 * Save the edited object into the cache.
1395
 */
1396
function panels_cache_set($obj, $did, $cache) {
1397
  ctools_include('object-cache');
1398
  return ctools_object_cache_set($obj, 'panels_display:' . $did, $cache);
1399
}
1400

    
1401
/**
1402
 * Clear a object from the cache; used if the editing is aborted.
1403
 */
1404
function panels_cache_clear($obj, $did) {
1405
  ctools_include('object-cache');
1406
  return ctools_object_cache_clear($obj, 'panels_display:' . $did);
1407
}
1408

    
1409
/**
1410
 * Create the default cache for editing panel displays.
1411
 *
1412
 * If an application is using the Panels display editor without having
1413
 * specified a cache key, this method can be used to create the default
1414
 * cache.
1415
 */
1416
function panels_edit_cache_get_default(&$display, $content_types = NULL, $title = FALSE) {
1417
  if (empty($content_types)) {
1418
    $content_types = ctools_content_get_available_types();
1419
  }
1420

    
1421
  $display->cache_key = $display->did;
1422
  panels_cache_clear('display', $display->did);
1423

    
1424
  $cache = new stdClass();
1425
  $cache->display = &$display;
1426
  $cache->content_types = $content_types;
1427
  $cache->display_title = $title;
1428

    
1429
  panels_edit_cache_set($cache);
1430
  return $cache;
1431
}
1432

    
1433
/**
1434
 * Method to allow modules to provide their own caching mechanism for the
1435
 * display editor.
1436
 */
1437
function panels_edit_cache_get($cache_key) {
1438
  if (strpos($cache_key, ':') !== FALSE) {
1439
    list($module, $argument) = explode(':', $cache_key, 2);
1440
    return module_invoke($module, 'panels_cache_get', $argument);
1441
  }
1442

    
1443
  // Fall back to our normal method:
1444
  return panels_cache_get('display', $cache_key);
1445
}
1446

    
1447
/**
1448
 * Method to allow modules to provide their own caching mechanism for the
1449
 * display editor.
1450
 */
1451
function panels_edit_cache_set($cache) {
1452
  $cache_key = $cache->display->cache_key;
1453
  if (strpos($cache_key, ':') !== FALSE) {
1454
    list($module, $argument) = explode(':', $cache_key, 2);
1455
    return module_invoke($module, 'panels_cache_set', $argument, $cache);
1456
  }
1457

    
1458
  // Fall back to our normal method:
1459
  return panels_cache_set('display', $cache_key, $cache);
1460
}
1461

    
1462
/**
1463
 * Method to allow modules to provide their own mechanism to write the
1464
 * cache used in the display editor.
1465
 */
1466
function panels_edit_cache_save($cache) {
1467
  $cache_key = $cache->display->cache_key;
1468
  if (strpos($cache_key, ':') !== FALSE) {
1469
    list($module, $argument) = explode(':', $cache_key, 2);
1470
    if (function_exists($module . '_panels_cache_save')) {
1471
      return module_invoke($module, 'panels_cache_save', $argument, $cache);
1472
    }
1473
  }
1474

    
1475
  // Fall back to our normal method:
1476
  return panels_save_display($cache->display);
1477
}
1478

    
1479
/**
1480
 * Method to allow modules to provide their own mechanism to clear the
1481
 * cache used in the display editor.
1482
 */
1483
function panels_edit_cache_clear($cache) {
1484
  $cache_key = $cache->display->cache_key;
1485
  if (strpos($cache_key, ':') !== FALSE) {
1486
    list($module, $argument) = explode(':', $cache_key, 2);
1487
    if (function_exists($module . '_panels_cache_clear')) {
1488
      return module_invoke($module, 'panels_cache_clear', $argument, $cache);
1489
    }
1490
  }
1491

    
1492
  // Fall back to our normal method:
1493
  return panels_cache_clear('display', $cache_key);
1494
}
1495

    
1496
/**
1497
 * Method to allow modules to provide a mechanism to break locks.
1498
 */
1499
function panels_edit_cache_break_lock($cache) {
1500
  if (empty($cache->locked)) {
1501
    return;
1502
  }
1503

    
1504
  $cache_key = $cache->display->cache_key;
1505
  if (strpos($cache_key, ':') !== FALSE) {
1506
    list($module, $argument) = explode(':', $cache_key, 2);
1507
    if (function_exists($module . '_panels_cache_break_lock')) {
1508
      return module_invoke($module, 'panels_cache_break_lock', $argument, $cache);
1509
    }
1510
  }
1511

    
1512
  // Normal panel display editing has no locks, so we do nothing if there is
1513
  // no fallback.
1514
  return;
1515
}
1516

    
1517
// --------------------------------------------------------------------------
1518
// Callbacks on behalf of the panel_context plugin.
1519
//
1520
// The panel_context plugin lets Panels be used in page manager. These
1521
// callbacks allow the display editing system to use the page manager
1522
// cache rather than the default display cache. They are routed by the cache
1523
// key via panels_edit_cache_* functions.
1524

    
1525
/**
1526
 * Get display edit cache on behalf of panel context.
1527
 *
1528
 * The key is the second half of the key in this form:
1529
 * panel_context:TASK_NAME:HANDLER_NAME;
1530
 */
1531
function panel_context_panels_cache_get($key) {
1532
  ctools_include('common', 'panels');
1533
  ctools_include('context');
1534
  ctools_include('context-task-handler');
1535
  // this loads the panel context inc even if we don't use the plugin.
1536
  $plugin = page_manager_get_task_handler('panel_context');
1537

    
1538
  list($task_name, $handler_name) = explode(':', $key, 2);
1539
  $page = page_manager_get_page_cache($task_name);
1540
  if (isset($page->display_cache[$handler_name])) {
1541
    return $page->display_cache[$handler_name];
1542
  }
1543

    
1544
  if ($handler_name) {
1545
    $handler = &$page->handlers[$handler_name];
1546
  }
1547
  else {
1548
    $handler = &$page->new_handler;
1549
  }
1550
  $cache = new stdClass();
1551

    
1552
  $cache->display = &panels_panel_context_get_display($handler);
1553
  $cache->display->context = ctools_context_handler_get_all_contexts($page->task, $page->subtask, $handler);
1554
  $cache->display->cache_key = 'panel_context:' . $key;
1555
  $cache->content_types = panels_common_get_allowed_types('panels_page', $cache->display->context);
1556
  $cache->display_title = TRUE;
1557
  $cache->locked = $page->locked;
1558

    
1559
  return $cache;
1560
}
1561

    
1562
/**
1563
 * Get the Page Manager cache for the panel_context plugin.
1564
 */
1565
function _panel_context_panels_cache_get_page_cache($key, $cache) {
1566
  list($task_name, $handler_name) = explode(':', $key, 2);
1567
  $page = page_manager_get_page_cache($task_name);
1568
  $page->display_cache[$handler_name] = $cache;
1569
  if ($handler_name) {
1570
    $page->handlers[$handler_name]->conf['display'] = $cache->display;
1571
    $page->handler_info[$handler_name]['changed'] |= PAGE_MANAGER_CHANGED_CACHED;
1572
  }
1573
  else {
1574
    $page->new_handler->conf['display'] = $cache->display;
1575
  }
1576

    
1577
  return $page;
1578
}
1579

    
1580
/**
1581
 * Store a display edit in progress in the page cache.
1582
 */
1583
function panel_context_panels_cache_set($key, $cache) {
1584
  $page = _panel_context_panels_cache_get_page_cache($key, $cache);
1585
  page_manager_set_page_cache($page);
1586
}
1587

    
1588
/**
1589
 * Save all changes made to a display using the Page Manager page cache.
1590
 */
1591
function panel_context_panels_cache_clear($key, $cache) {
1592
  $page = _panel_context_panels_cache_get_page_cache($key, $cache);
1593
  page_manager_clear_page_cache($page->task_name);
1594
}
1595

    
1596
/**
1597
 * Save all changes made to a display using the Page Manager page cache.
1598
 */
1599
function panel_context_panels_cache_save($key, $cache) {
1600
  $page = _panel_context_panels_cache_get_page_cache($key, $cache);
1601
  page_manager_save_page_cache($page);
1602
}
1603

    
1604
/**
1605
 * Break the lock on a page manager page.
1606
 */
1607
function panel_context_panels_cache_break_lock($key, $cache) {
1608
  $page = _panel_context_panels_cache_get_page_cache($key, $cache);
1609
  ctools_object_cache_clear_all('page_manager_page', $page->task_name);
1610
}
1611

    
1612
// --------------------------------------------------------------------------
1613
// Callbacks on behalf of the panels page wizards
1614
//
1615
// The page wizards are a pluggable set of 'wizards' to make it easy to create
1616
// specific types of pages based upon whatever someone felt like putting
1617
// together. Since they will very often have content editing, we provide
1618
// a generic mechanism to allow them to store their editing cache in the
1619
// wizard cache.
1620
//
1621
// For them to use this mechanism, they just need to use:
1622
//   $cache = panels_edit_cache_get('panels_page_wizard:' . $plugin['name']);
1623

    
1624
/**
1625
 * Get display edit cache for the panels mini export UI
1626
 *
1627
 * The key is the second half of the key in this form:
1628
 * panels_page_wizard:TASK_NAME:HANDLER_NAME;
1629
 */
1630
function panels_page_wizard_panels_cache_get($key) {
1631
  ctools_include('page-wizard');
1632
  ctools_include('context');
1633
  $wizard_cache = page_manager_get_wizard_cache($key);
1634
  if (isset($wizard_cache->display_cache)) {
1635
    return $wizard_cache->display_cache;
1636
  }
1637

    
1638
  ctools_include('common', 'panels');
1639
  $cache = new stdClass();
1640
  $cache->display = $wizard_cache->display;
1641
  $cache->display->context = !empty($wizard_cache->context) ? $wizard_cache->context : array();
1642
  $cache->display->cache_key = 'panels_page_wizard:' . $key;
1643
  $cache->content_types = panels_common_get_allowed_types('panels_page', $cache->display->context);
1644
  $cache->display_title = TRUE;
1645

    
1646
  return $cache;
1647
}
1648

    
1649
/**
1650
 * Store a display edit in progress in the page cache.
1651
 */
1652
function panels_page_wizard_panels_cache_set($key, $cache) {
1653
  ctools_include('page-wizard');
1654
  $wizard_cache = page_manager_get_wizard_cache($key);
1655
  $wizard_cache->display_cache = $cache;
1656
  page_manager_set_wizard_cache($wizard_cache);
1657
}
1658

    
1659
// --------------------------------------------------------------------------
1660
// General utility functions
1661

    
1662
/**
1663
 * Perform a drupal_goto on a destination that may be an array like url().
1664
 */
1665
function panels_goto($destination) {
1666
  if (!is_array($destination)) {
1667
    return drupal_goto($destination);
1668
  }
1669
  else {
1670
    // Prevent notices by adding defaults
1671
    $destination += array(
1672
      'query' => NULL,
1673
      'fragment' => NULL,
1674
      'http_response_code' => NULL,
1675
    );
1676

    
1677
    return drupal_goto($destination['path'], $destination['query'], $destination['fragment'], $destination['http_response_code']);
1678
  }
1679
}
1680

    
1681

    
1682
/**
1683
 * For external use: Given a layout ID and a $content array, return the
1684
 * panel display.
1685
 *
1686
 * The content array is filled in based upon the content available in the
1687
 * layout. If it's a two column with a content array defined like
1688
 * @code
1689
 *   array(
1690
 *    'left' => t('Left side'),
1691
 *    'right' => t('Right side')
1692
 *  ),
1693
 * @endcode
1694
 *
1695
 * Then the $content array should be
1696
 * @code
1697
 * array(
1698
 *   'left' => $output_left,
1699
 *   'right' => $output_right,
1700
 * )
1701
 * @endcode
1702
 *
1703
 * The output within each panel region can be either a single rendered
1704
 * HTML string or an array of rendered HTML strings as though they were
1705
 * panes. They will simply be concatenated together without separators.
1706
 */
1707
function panels_print_layout($layout, $content, $meta = 'standard') {
1708
  ctools_include('plugins', 'panels');
1709

    
1710
  // Create a temporary display for this.
1711
  $display = panels_new_display();
1712
  $display->layout = is_array($layout) ? $layout['name'] : $layout;
1713
  $display->content = $content;
1714

    
1715
  // Get our simple renderer
1716
  $renderer = panels_get_renderer_handler('simple', $display);
1717
  $renderer->meta_location = $meta;
1718

    
1719
  return $renderer->render();
1720
}
1721

    
1722
/**
1723
 * Filter callback for array_filter to remove builders from a list of layouts.
1724
 */
1725
function _panels_builder_filter($layout) {
1726
  return empty($layout['builder']);
1727
}
1728

    
1729
// --------------------------------------------------------------------------
1730
// Deprecated functions
1731
//
1732
// Everything below this line will eventually go away.
1733

    
1734
/**
1735
 * panels path helper function
1736
 */
1737
function panels_get_path($file, $base_path = FALSE, $module = 'panels') {
1738
  $output = $base_path ? base_path() : '';
1739
  return $output . drupal_get_path('module', $module) . '/' . $file;
1740
}
1741

    
1742
/**
1743
 * Remove default sidebar related body classes and provide own css classes
1744
 */
1745
function panels_preprocess_html(&$vars) {
1746
  $panel_body_css = &drupal_static('panel_body_css');
1747
  if (!empty($panel_body_css['body_classes_to_remove'])) {
1748
    $classes_to_remove = explode(' ', $panel_body_css['body_classes_to_remove']);
1749
    foreach ($vars['classes_array'] as $key => $css_class) {
1750
      if (in_array($css_class, $classes_to_remove)) {
1751
        unset($vars['classes_array'][$key]);
1752
      }
1753
    }
1754
  }
1755
  if (!empty($panel_body_css['body_classes_to_add'])) {
1756
    $vars['classes_array'][] = check_plain($panel_body_css['body_classes_to_add']);
1757
  }
1758
}