Projet

Général

Profil

Paste
Télécharger (68,8 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / panels / panels.module @ 64156087

1
<?php
2

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

    
8
define('PANELS_REQUIRED_CTOOLS_API', '2.0.9');
9

    
10
/**
11
 * The current working panels version.
12
 *
13
 * In a release, it should be 7.x-3.x, which should match what drush make will
14
 * create. In a dev format, it should be 7.x-3.(x+1)-dev, which will allow
15
 * modules depending on new features in panels to depend on panels > 7.x-3.x.
16
 *
17
 * To define a specific version of Panels as a dependency for another module,
18
 * simply include a dependency line in that module's info file, e.g.:
19
 *   ; Requires Panels v7.x-3.4 or newer.
20
 *   dependencies[] = panels (>=3.4)
21
 */
22
define('PANELS_VERSION', '7.x-3.8');
23

    
24

    
25
// Hide title use to be TRUE/FALSE. So FALSE remains old behavior.
26
define('PANELS_TITLE_FIXED', 0);
27
// And TRUE meant no title.
28
define('PANELS_TITLE_NONE', 1);
29
// And this is the new behavior, where the title field will pick from a pane.
30
define('PANELS_TITLE_PANE', 2);
31

    
32
/**
33
 * Returns the API version of Panels. This didn't exist in 1.
34
 *
35
 * @todo -- this should work more like the CTools API version.
36
 *
37
 * @return array
38
 *   An array with the major and minor versions
39
 */
40
function panels_api_version() {
41
  return array(3, 1);
42
}
43

    
44
/**
45
 * Implements hook_theme().
46
 */
47
function panels_theme() {
48
  // Safety: go away if CTools is not at an appropriate version.
49
  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
50
    return array();
51
  }
52

    
53
  $theme = array();
54
  $theme['panels_layout_link'] = array(
55
    'variables' => array(
56
      'title' => NULL,
57
      'id' => NULL,
58
      'image' => NULL,
59
      'link' => NULL,
60
      'class' => NULL,
61
    ),
62
  );
63
  $theme['panels_layout_icon'] = array(
64
    'variables' => array(
65
      'id' => NULL,
66
      'image' => NULL,
67
      'title' => NULL,
68
    ),
69
  );
70
  $theme['panels_pane'] = array(
71
    'variables' => array(
72
      'content' => array(),
73
      'pane' => array(),
74
      'display' => array(),
75
    ),
76
    'path' => drupal_get_path('module', 'panels') . '/templates',
77
    'template' => 'panels-pane',
78
  );
79
  $theme['panels_common_content_list'] = array(
80
    'variables' => array('display' => NULL),
81
    'file' => 'includes/common.inc',
82
  );
83
  $theme['panels_render_display_form'] = array(
84
    'render element' => 'element',
85
  );
86

    
87
  $theme['panels_dashboard'] = array(
88
    'variables' => array(),
89
    'path' => drupal_get_path('module', 'panels') . '/templates',
90
    'file' => '../includes/callbacks.inc',
91
    'template' => 'panels-dashboard',
92
  );
93

    
94
  $theme['panels_dashboard_link'] = array(
95
    'variables' => array('link' => array()),
96
    'path' => drupal_get_path('module', 'panels') . '/templates',
97
    'file' => '../includes/callbacks.inc',
98
    'template' => 'panels-dashboard-link',
99
  );
100

    
101
  $theme['panels_dashboard_block'] = array(
102
    'variables' => array('block' => array()),
103
    'path' => drupal_get_path('module', 'panels') . '/templates',
104
    'file' => '../includes/callbacks.inc',
105
    'template' => 'panels-dashboard-block',
106
  );
107

    
108
  $theme['panels_add_content_modal'] = array(
109
    'variables' => array(
110
      'renderer' => NULL,
111
      'categories' => array(),
112
      'region' => NULL,
113
      'category' => NULL,
114
      'column_count' => 2,
115
    ),
116
    'path' => drupal_get_path('module', 'panels') . '/templates',
117
    'file' => '../includes/add-content.inc',
118
    'template' => 'panels-add-content-modal',
119
  );
120

    
121
  $theme['panels_add_content_link'] = array(
122
    'variables' => array(
123
      'renderer' => NULL,
124
      'region' => NULL,
125
      'content_type' => NULL,
126
    ),
127
    'path' => drupal_get_path('module', 'panels') . '/templates',
128
    'file' => '../includes/add-content.inc',
129
    'template' => 'panels-add-content-link',
130
  );
131

    
132
  // Register layout and style themes on behalf of all of these items.
133
  ctools_include('plugins', 'panels');
134

    
135
  // No need to worry about files; the plugin has to already be loaded for us
136
  // to even know what the theme function is, so files will be auto included.
137
  $layouts = panels_get_layouts();
138
  foreach ($layouts as $name => $data) {
139
    foreach (array('theme', 'admin theme') as $callback) {
140
      if (!empty($data[$callback])) {
141
        $theme[$data[$callback]] = array(
142
          'variables' => array(
143
            'css_id' => NULL,
144
            'content' => NULL,
145
            'settings' => NULL,
146
            'display' => NULL,
147
            'layout' => NULL,
148
            'renderer' => NULL,
149
          ),
150
          'path' => $data['path'],
151
          'file' => $data['file'],
152
        );
153

    
154
        // If no theme function exists, assume template.
155
        if (!function_exists("theme_$data[theme]")) {
156
          $theme[$data[$callback]]['template'] = str_replace('_', '-', $data[$callback]);
157
          // For preprocess.
158
          $theme[$data[$callback]]['file'] = $data['file'];
159
        }
160
      }
161
    }
162
  }
163

    
164
  $styles = panels_get_styles();
165
  foreach ($styles as $name => $data) {
166
    if (!empty($data['render pane'])) {
167
      $theme[$data['render pane']] = array(
168
        'variables' => array(
169
          'content' => NULL,
170
          'pane' => NULL,
171
          'display' => NULL,
172
          'style' => NULL,
173
          'settings' => NULL,
174
        ),
175
        'path' => $data['path'],
176
        'file' => $data['file'],
177
      );
178
    }
179
    if (!empty($data['render region'])) {
180
      $theme[$data['render region']] = array(
181
        'variables' => array(
182
          'display' => NULL,
183
          'owner_id' => NULL,
184
          'panes' => NULL,
185
          'settings' => NULL,
186
          'region_id' => NULL,
187
          'style' => NULL,
188
        ),
189
        'path' => $data['path'],
190
        'file' => $data['file'],
191
      );
192
    }
193

    
194
    if (!empty($data['hook theme'])) {
195
      if (is_array($data['hook theme'])) {
196
        $theme += $data['hook theme'];
197
      }
198
      elseif (function_exists($data['hook theme'])) {
199
        $data['hook theme']($theme, $data);
200
      }
201
    }
202
  }
203
  return $theme;
204
}
205

    
206
/**
207
 * Implements hook_menu().
208
 */
209
function panels_menu() {
210
  // Safety: go away if CTools is not at an appropriate version.
211
  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
212
    return array();
213
  }
214
  $items = array();
215

    
216
  // Base AJAX router callback.
217
  $items['panels/ajax'] = array(
218
    'access arguments' => array('access content'),
219
    'page callback' => 'panels_ajax_router',
220
    'theme callback' => 'ajax_base_page_theme',
221
    'delivery callback' => 'ajax_deliver',
222
    'type' => MENU_CALLBACK,
223
  );
224

    
225
  $admin_base = array(
226
    'file' => 'includes/callbacks.inc',
227
    'access arguments' => array('use panels dashboard'),
228
  );
229
  // Provide a nice location for a panels admin panel.
230
  $items['admin/structure/panels'] = array(
231
    'title' => 'Panels',
232
    'page callback' => 'panels_admin_page',
233
    'description' => 'Get a bird\'s eye view of items related to Panels.',
234
  ) + $admin_base;
235

    
236
  $items['admin/structure/panels/dashboard'] = array(
237
    'title' => 'Dashboard',
238
    'page callback' => 'panels_admin_page',
239
    'type' => MENU_DEFAULT_LOCAL_TASK,
240
    'weight' => -10,
241
  ) + $admin_base;
242

    
243
  $items['admin/structure/panels/settings'] = array(
244
    'title' => 'Settings',
245
    'page callback' => 'drupal_get_form',
246
    'page arguments' => array('panels_admin_settings_page'),
247
    'type' => MENU_LOCAL_TASK,
248
  ) + $admin_base;
249

    
250
  $items['admin/structure/panels/settings/general'] = array(
251
    'title' => 'General',
252
    'page callback' => 'drupal_get_form',
253
    'page arguments' => array('panels_admin_settings_page'),
254
    'access arguments' => array('administer page manager'),
255
    'type' => MENU_DEFAULT_LOCAL_TASK,
256
    'weight' => -10,
257
  ) + $admin_base;
258

    
259
  if (module_exists('page_manager')) {
260
    $items['admin/structure/panels/settings/panel-page'] = array(
261
      'title' => 'Panel pages',
262
      'page callback' => 'panels_admin_panel_context_page',
263
      'type' => MENU_LOCAL_TASK,
264
      'weight' => -10,
265
    ) + $admin_base;
266
  }
267

    
268
  ctools_include('plugins', 'panels');
269
  $layouts = panels_get_layouts();
270
  foreach ($layouts as $name => $data) {
271
    if (!empty($data['hook menu'])) {
272
      if (is_array($data['hook menu'])) {
273
        $items += $data['hook menu'];
274
      }
275
      elseif (function_exists($data['hook menu'])) {
276
        $data['hook menu']($items, $data);
277
      }
278
    }
279
  }
280
  return $items;
281
}
282

    
283
/**
284
 * Menu loader function to load a cache item for Panels AJAX.
285
 *
286
 * This load all of the includes needed to perform AJAX, and loads the
287
 * cache object and makes sure it is valid.
288
 */
289
function panels_edit_cache_load($cache_key) {
290
  ctools_include('display-edit', 'panels');
291
  ctools_include('plugins', 'panels');
292
  ctools_include('ajax');
293
  ctools_include('modal');
294
  ctools_include('context');
295

    
296
  return panels_edit_cache_get($cache_key);
297
}
298

    
299
/**
300
 * Implements hook_init().
301
 */
302
function panels_init() {
303
  // Safety: go away if CTools is not at an appropriate version.
304
  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
305
    if (user_access('administer site configuration')) {
306
      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');
307
    }
308
    return;
309
  }
310

    
311
  ctools_add_css('panels', 'panels');
312
}
313

    
314
/**
315
 * Implements hook_permission().
316
 *
317
 * @todo Almost all of these need to be moved into pipelines.
318
 */
319
function panels_permission() {
320
  return array(
321
    'use panels dashboard' => array(
322
      'title' => t("Use Panels Dashboard"),
323
      'description' => t('Allows a user to access the <a href="@url">Panels Dashboard</a>.', array('@url' => url('admin/structure/panels'))),
324
    ),
325
    // @todo
326
    'view pane admin links' => array(
327
      'title' => t("View administrative links on Panel panes"),
328
      'description' => "",
329
    ),
330
    // @todo should we really have a global perm for this, or should it be moved into a pipeline question?
331
    'administer pane access' => array(
332
      'title' => t("Configure access settings on Panel panes"),
333
      '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."),
334
    ),
335
    'use panels in place editing' => array(
336
      'title' => t("Use the Panels In-Place Editor"),
337
      'description' => t("Allows a user to utilize Panels' In-Place Editor."),
338
    ),
339
    'change layouts in place editing' => array(
340
      'title' => t("Change layouts with the Panels In-Place Editor"),
341
      'description' => t("Allows a user to change layouts with the IPE."),
342
    ),
343
    'bypass access in place editing' => array(
344
      'title' => t("Bypass access checks when using Panels In-Place Editor"),
345
      'description' => t("Allows using IPE even if user does not have additional permissions granted by other modules."),
346
      'restrict access' => TRUE,
347
    ),
348
    'administer advanced pane settings' => array(
349
      'title' => t("Configure advanced settings on Panel panes"),
350
      'description' => "",
351
    ),
352
    'administer panels layouts' => array(
353
      'title' => t("Administer Panels layouts"),
354
      'description' => t("Allows a user to administer exported Panels layout plugins & instances."),
355
    ),
356
    'administer panels styles' => array(
357
      'title' => t("Administer Panels styles"),
358
      'description' => t("DEPRECATED: Modules using this permission should use specific style permissions. See Issue #2329419 for more info."),
359
    ),
360
    'administer panels display styles' => array(
361
      'title' => t("Administer Panels display styles"),
362
      'description' => t("Allows a user to administer the styles of Panel displays."),
363
    ),
364
    'administer panels pane styles' => array(
365
      'title' => t("Administer Panels pane styles"),
366
      'description' => t("Allows a user to administer the styles of Panel panes."),
367
    ),
368
    'administer panels region styles' => array(
369
      'title' => t("Administer Panels region styles"),
370
      'description' => t("Allows a user to administer the styles of Panel regions."),
371
    ),
372
    'use panels caching features' => array(
373
      'title' => t("Configure caching settings on Panels"),
374
      'description' => t("Allows a user to configure caching on Panels displays and panes."),
375
    ),
376
    'use panels locks' => array(
377
      'title' => t('Use panel locks'),
378
      'description' => t('Allows a user to lock and unlock panes in a panel display.'),
379
    ),
380
    'use ipe with page manager' => array(
381
      'title' => t("Use the Panels In-Place Editor with Page Manager"),
382
      'description' => t('Allows users with access to the In-Place editor to administer page manager pages. This permission is only needed for users without "use page manager" access.'),
383
    ),
384
  );
385
}
386

    
387
/**
388
 * Implements hook_flush_caches().
389
 */
390
function panels_flush_caches() {
391
  if (db_table_exists('cache_panels')) {
392
    return array('cache_panels');
393
  }
394
}
395

    
396
/**
397
 * CTools hook implementations.
398
 *
399
 * These aren't core Drupal hooks but they are just as important.
400
 */
401

    
402
/**
403
 * Implements hook_ctools_plugin_directory().
404
 */
405
function panels_ctools_plugin_directory($module, $plugin) {
406
  // To let the system know we implement task and task_handler plugins.
407
  // Safety: go away if CTools is not at an appropriate version.
408
  if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
409
    return;
410
  }
411

    
412
  // We don't support the 'ctools' 'cache' plugin and pretending to causes
413
  // errors when they're in use.
414
  if ($module == 'ctools' && $plugin == 'cache') {
415
    // If we did we'd make a plugin/ctools_cache or something.
416
    return;
417
  }
418

    
419
  if ($module == 'page_manager' || $module == 'panels' || $module == 'ctools' || $module == 'stylizer') {
420
    // Panels and CTools both implement a 'cache' plugin but we don't implement
421
    // the CTools version.
422
    if ($module == 'ctools' && $plugin == 'cache') {
423
      return;
424
    }
425
    return 'plugins/' . $plugin;
426
  }
427
}
428

    
429
/**
430
 * Implements hook_ctools_plugin_type().
431
 *
432
 * Register layout, style, cache, and display_renderer plugin types, declaring
433
 * relevant plugin type information as necessary.
434
 */
435
function panels_ctools_plugin_type() {
436
  return array(
437
    'layouts' => array(
438
      // We can define layouts in themes.
439
      'load themes' => TRUE,
440
      'process' => 'panels_layout_process',
441
      'child plugins' => TRUE,
442
    ),
443
    'styles' => array(
444
      'load themes' => TRUE,
445
      'process' => 'panels_plugin_styles_process',
446
      'child plugins' => TRUE,
447
    ),
448
    'cache' => array(),
449
    'display_renderers' => array(
450
      'classes' => array('renderer'),
451
    ),
452
    'panels_storage' => array(),
453
  );
454
}
455

    
456
/**
457
 * Ensure a layout has a minimal set of data.
458
 */
459
function panels_layout_process(&$plugin) {
460
  $plugin += array(
461
    'category' => t('Miscellaneous'),
462
    'description' => '',
463
  );
464
}
465

    
466
/**
467
 * Implements hook_ctools_plugin_api().
468
 */
469
function panels_ctools_plugin_api($owner, $api) {
470
  // Inform CTools about version information for various plugins implemented by
471
  // panels.
472
  if ($owner == 'panels' && $api == 'styles') {
473
    // As of 6.x-3.6, Panels has a slightly new system for style plugins.
474
    return array('version' => 2.0);
475
  }
476

    
477
  if ($owner == 'panels' && $api == 'pipelines') {
478
    return array(
479
      'version' => 1,
480
      'path' => drupal_get_path('module', 'panels') . '/includes',
481
    );
482
  }
483
}
484

    
485
/**
486
 * Implements hook_views_api().
487
 */
488
function panels_views_api() {
489
  return array(
490
    'api' => 2,
491
    'path' => drupal_get_path('module', 'panels') . '/plugins/views',
492
  );
493
}
494

    
495
/**
496
 * Perform additional processing on a style plugin.
497
 *
498
 * Currently this is only being used to apply versioning information to style
499
 * plugins in order to ensure the legacy renderer passes the right type of
500
 * parameters to a style plugin in a hybrid environment of both new and old
501
 * plugins.
502
 *
503
 * @param array $plugin
504
 *   The style plugin that is being processed.
505
 * @param array $info
506
 *   The style plugin type info array.
507
 *
508
 * @see _ctools_process_data()
509
 */
510
function panels_plugin_styles_process(&$plugin, $info) {
511
  $plugin += array(
512
    'weight' => 0,
513
  );
514

    
515
  $compliant_modules = ctools_plugin_api_info('panels', 'styles', 2.0, 2.0);
516
  $plugin['version'] = empty($compliant_modules[$plugin['module']]) ? 1.0 : $compliant_modules[$plugin['module']]['version'];
517
}
518

    
519
/**
520
 * Declare what style types Panels uses.
521
 */
522
function panels_ctools_style_base_types() {
523
  return array(
524
    'region' => array(
525
      'title' => t('Panel region'),
526
      'preview' => 'panels_stylizer_region_preview',
527
      'theme variables' => array(
528
        'settings' => NULL,
529
        'class' => NULL,
530
        'content' => NULL,
531
      ),
532
    ),
533
    'pane' => array(
534
      'title' => t('Panel pane'),
535
      'preview' => 'panels_stylizer_pane_preview',
536
      'theme variables' => array(
537
        'settings' => NULL,
538
        'content' => NULL,
539
        'pane' => NULL,
540
        'display' => NULL,
541
      ),
542
    ),
543
  );
544
}
545

    
546
/**
547
 * Generates Lorem Ipsum.
548
 *
549
 * @return string
550
 *   Lorem ipsum string.
551
 */
552
function panels_stylizer_lipsum() {
553
  return <<<LIPSUM
554
    <p>
555
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus at 
556
      velit dolor. Donec egestas tellus sit amet urna rhoncus adipiscing. Proin 
557
      nec porttitor sem. Maecenas aliquam, purus nec tempus dignissim, nulla arcu
558
      aliquam diam, non tincidunt massa ante vel dolor. Aliquam sapien sapien,
559
      tincidunt id tristique at, pretium sagittis libero.
560
    </p>
561
    <p>
562
      Nulla facilisi. Curabitur lacinia, tellus sed tristique consequat, diam
563
      lorem scelerisque felis, at dictum purus augue facilisis lorem. Duis
564
      pharetra dignissim rutrum. Curabitur ac elit id dui dapibus tincidunt.
565
      Nulla eget sem quam, non eleifend eros. Cras porttitor tempus lectus ac
566
      scelerisque. Curabitur vehicula bibendum lorem, vitae ornare ligula
567
      venenatis ut.
568
    </p>
569
LIPSUM;
570
}
571

    
572
/**
573
 * Generate a preview given the current settings.
574
 */
575
function panels_stylizer_region_preview($plugin, $settings) {
576
  ctools_stylizer_add_css($plugin, $settings);
577
  return theme(
578
    $plugin['theme'],
579
    array(
580
      'settings' => $settings,
581
      'class' => ctools_stylizer_get_css_class($plugin, $settings),
582
      'content' => panels_stylizer_lipsum(),
583
    )
584
  );
585
}
586

    
587
/**
588
 * Generate a preview given the current settings.
589
 */
590
function panels_stylizer_pane_preview($plugin, $settings) {
591
  ctools_stylizer_add_css($plugin, $settings);
592
  $pane = new stdClass();
593

    
594
  $content = new stdClass();
595
  $content->title = t('Lorem ipsum');
596
  $content->content = panels_stylizer_lipsum();
597
  $content->type = 'dummy';
598
  $content->subtype = 'dummy';
599

    
600
  $content->css_class = ctools_stylizer_get_css_class($plugin, $settings);
601

    
602
  $display = new panels_display();
603

    
604
  if (!empty($plugin['theme'])) {
605
    return theme(
606
      $plugin['theme'],
607
      array(
608
        'settings' => $settings,
609
        'content' => $content,
610
        'pane' => $pane,
611
        'display' => $display,
612
      )
613
    );
614
  }
615
  else {
616
    return theme(
617
      'panels_pane',
618
      array(
619
        'content' => $content,
620
        'pane' => $pane,
621
        'display' => $display,
622
      )
623
    );
624
  }
625
}
626

    
627
/**
628
 * Panels display editing.
629
 */
630

    
631
/**
632
 * @defgroup mainapi Functions comprising the main panels API.
633
 */
634

    
635
/**
636
 * Main API entry point to edit a panel display.
637
 *
638
 * Sample implementations utiltizing the the complex $destination behavior can
639
 * be found in panels_page_edit_content() and, in a separate contrib module,
640
 * OG Blueprints (http://drupal.org/project/og_blueprints),
641
 * og_blueprints_blueprint_edit().
642
 *
643
 * @param object $display
644
 *   Instanceof panels_display.
645
 *
646
 *   A fully loaded panels $display object, as returned from
647
 *   panels_load_display(). Merely passing a did is NOT sufficient.
648
 *   Note that 'fully loaded' means the $display must already be loaded with
649
 *   any contexts the caller wishes to have set for the display.
650
 * @param mixed $destination
651
 *   The redirect destination that the user should be taken to on form
652
 *   submission or cancellation. With panels_edit, $destination has complex
653
 *   effects on the return values of panels_edit() once the form has been
654
 *   submitted. See the explanation of the return value below to understand the
655
 *   different types of values returned by panels_edit() at different stages of
656
 *   FAPI. Under most circumstances, simply passing in drupal_get_destination()
657
 *   is all that's necessary.
658
 * @param array $content_types
659
 *   An associative array of allowed content types, typically as returned from
660
 *   panels_common_get_allowed_types(). Note that context partially governs
661
 *   available content types, so you will want to create any relevant contexts
662
 *   using panels_create_context() or panels_create_context_empty() to make sure
663
 *   all the appropriate content types are available.
664
 *
665
 * @return mixed
666
 *   Because the functions called by panels_edit() invoke the form API,
667
 *   this function returns different values depending on the stage of form
668
 *   submission we're at. In Drupal 5, the phase of form submission is indicated
669
 *   by the contents of $_POST['op']. Here is what you'll get at different
670
 *   stages:
671
 *     -  If !$_POST['op']: then we're on on the initial passthrough and the
672
 *        form is being rendered, so it's the $form itself that's being
673
 *        returned. Because negative margins, a common CSS technique, bork the
674
 *        display editor's ajax drag-and-drop, it's important that the $output
675
 *        be printed, not returned. Use this syntax in the caller function:
676
 *        print theme('page', panels_edit($display, $destination, $content_types), FALSE);
677
 *     -  If $_POST['op'] == t('Cancel'): form submission has been cancelled.
678
 *        If empty($destination) == FALSE, then there is no return value and the
679
 *        panels API takes care of redirecting to $destination.
680
 *        If empty($destination) == TRUE, then there's still no return value,
681
 *        but the caller function has to take care of form redirection.
682
 *     -  If $_POST['op'] == ('Save'): the form has been submitted successfully
683
 *        and has run through panels_edit_display_submit().
684
 *        $output depends on the value of $destination:
685
 *     -  If empty($destination) == TRUE: $output contains the modified $display
686
 *        object, and no redirection will occur. This option is useful if the
687
 *        caller needs to perform additional operations on or with the modified
688
 *        $display before the page request is complete. Using hook_form_alter()
689
 *        to add an additional submit handler is typically the preferred method
690
 *        for something like this, but there are certain use cases where that is
691
 *        infeasible and $destination = NULL should be used instead. If this
692
 *        method is employed, the caller will need to handle form redirection.
693
 *        Note that having $_REQUEST['destination'] set, whether via
694
 *        drupal_get_destination() or some other method, will NOT interfere with
695
 *        this functionality; consequently, you can use drupal_get_destination()
696
 *        to safely store your desired redirect in the caller function, then
697
 *        simply use drupal_goto() once panels_edit() has done its business.
698
 *     -  If empty($destination) == FALSE: the form will redirect to the URL
699
 *        string given in $destination and NO value will be returned.
700
 *
701
 * @ingroup mainapi
702
 */
703
function panels_edit($display, $destination = NULL, $content_types = NULL, $title = FALSE) {
704
  ctools_include('display-edit', 'panels');
705
  ctools_include('ajax');
706
  ctools_include('plugins', 'panels');
707
  return _panels_edit($display, $destination, $content_types, $title);
708
}
709

    
710
/**
711
 * API entry point for selecting a layout for a given display.
712
 *
713
 * Layout selection is nothing more than a list of radio items encompassing the
714
 * available layouts for this display, as defined by .inc files in the
715
 * panels/layouts subdirectory. The only real complexity occurs when a user
716
 * attempts to change the layout of a display that has some content in it.
717
 *
718
 * @param object $display
719
 *   A fully loaded panels $display object, as returned from
720
 *   panels_load_display(). Merely passing a did is NOT sufficient.
721
 * @param string $finish
722
 *   A string that will be used for the text of the form submission button. If
723
 *   no value is provided, then the form submission button will default to
724
 *   t('Save').
725
 * @param mixed $destination
726
 *   Basic usage is a string containing the URL that the form should redirect to
727
 *   upon submission. For a discussion of advanced usages, see panels_edit().
728
 * @param mixed $allowed_layouts
729
 *   Allowed layouts has three different behaviors that depend on which of three
730
 *   value types are passed in by the caller:
731
 *     #- if $allowed_layouts instanceof panels_allowed_layouts
732
 *        (includes subclasses): the most complex use of the API. The caller is
733
 *        passing in a loaded panels_allowed_layouts object that the client
734
 *        module previously created and stored somewhere using a custom storage
735
 *        mechanism.
736
 *     #- if is_string($allowed_layouts): the string will be used in a call to
737
 *        variable_get() which will call the
738
 *        $allowed_layouts . '_allowed_layouts' var. If the data was stored
739
 *        properly in the system var, the $allowed_layouts object will be
740
 *        unserialized and recreated.
741
 *     #- if is_null($allowed_layouts): the default behavior, which also
742
 *        provides backwards compatibility for implementations of the Panels2
743
 *        API written before beta4. In this case, a dummy panels_allowed_layouts
744
 *        object is created which does not restrict any layouts. Subsequent
745
 *        behavior is indistinguishable from pre-beta4 behavior.
746
 *
747
 * @return mixed
748
 *   Can return nothing, or a modified $display object, or a redirection string;
749
 *   return values for the panels_edit* family of functions are quite complex.
750
 *   See panels_edit() for detailed discussion.
751
 *
752
 * @see panels_edit()
753
 * @see panels_common_set_allowed_layouts()
754
 */
755
function panels_edit_layout($display, $finish, $destination = NULL, $allowed_layouts = NULL) {
756
  ctools_include('display-layout', 'panels');
757
  ctools_include('plugins', 'panels');
758
  return _panels_edit_layout($display, $finish, $destination, $allowed_layouts);
759
}
760

    
761
/**
762
 * Panels database functions.
763
 */
764

    
765
/**
766
 * Forms the basis of a panel display.
767
 */
768
class panels_display {
769
  public $args = array();
770
  public $content = array();
771
  public $panels = array();
772
  public $incoming_content = NULL;
773
  public $css_id = NULL;
774
  public $context = array();
775
  public $did = 'new';
776
  public $renderer = 'standard';
777

    
778
  /**
779
   * Add a pane.
780
   */
781
  public function add_pane(&$pane, $location = NULL) {
782
    // If no location specified, use what's set in the pane.
783
    if (empty($location)) {
784
      $location = $pane->panel;
785
    }
786
    else {
787
      $pane->panel = $location;
788
    }
789

    
790
    // Generate a permanent uuid for this pane, and use
791
    // it as a temporary pid.
792
    $pane->uuid = ctools_uuid_generate();
793
    $pane->pid = 'new-' . $pane->uuid;
794

    
795
    // Add the pane to the appropriate spots.
796
    $this->content[$pane->pid] = &$pane;
797
    $this->panels[$location][] = $pane->pid;
798
  }
799

    
800
  /**
801
   * Duplicate a pane.
802
   */
803
  public function duplicate_pane($pid, $location = FALSE) {
804
    $pane = $this->clone_pane($pid);
805
    $this->add_pane($pane, $location);
806
  }
807

    
808
  /**
809
   * Get the title from a display.
810
   *
811
   * The display must have already been rendered, or the setting to set the
812
   * display's title from a pane's title will not have worked.
813
   *
814
   * @return mixed
815
   *   The title to use. If NULL, this means to let any default title that may
816
   *   be in use pass through. i.e, do not actually set the title.
817
   */
818
  public function get_title() {
819
    switch ($this->hide_title) {
820
      case PANELS_TITLE_NONE:
821
        return '';
822

    
823
      case PANELS_TITLE_PANE:
824
        return isset($this->stored_pane_title) ? $this->stored_pane_title : '';
825

    
826
      case PANELS_TITLE_FIXED:
827
      case FALSE;
828
        // For old exported panels that are not in the database.
829
        if (!empty($this->title)) {
830
          return filter_xss_admin(ctools_context_keyword_substitute($this->title, array(), $this->context));
831
        }
832
        return NULL;
833
    }
834
  }
835

    
836
  /**
837
   * Render this panels display.
838
   *
839
   * After checking to ensure the designated layout plugin is valid, a
840
   * display renderer object is spawned and runs its rendering logic.
841
   *
842
   * @param mixed $renderer
843
   *    An instantiated display renderer object, or the name of a display
844
   *    renderer plugin+class to be fetched. Defaults to NULL. When NULL, the
845
   *    predesignated display renderer will be used.
846
   *
847
   * @return mixed
848
   *    NULL or output of render function.
849
   */
850
  public function render($renderer = NULL) {
851
    $layout = panels_get_layout($this->layout);
852
    if (!$layout) {
853
      return NULL;
854
    }
855

    
856
    // If we were not given a renderer object, load it.
857
    if (!is_object($renderer)) {
858
      // If the renderer was not specified, default to $this->renderer
859
      // which is either standard or was already set for us.
860
      $renderer = panels_get_renderer_handler(!empty($renderer) ? $renderer : $this->renderer, $this);
861
      if (!$renderer) {
862
        return NULL;
863
      }
864
    }
865

    
866
    $output = '';
867
    // Let modules act just prior to render.
868
    foreach (module_implements('panels_pre_render') as $module) {
869
      $function = $module . '_panels_pre_render';
870
      $output .= $function($this, $renderer);
871
    }
872

    
873
    $output .= $renderer->render();
874

    
875
    // Let modules act just after render.
876
    foreach (module_implements('panels_post_render') as $module) {
877
      $function = $module . '_panels_post_render';
878
      $output .= $function($this, $renderer);
879
    }
880
    return $output;
881
  }
882

    
883
  /**
884
   * Determine if the given user can perform the requested operation.
885
   *
886
   * @param string $op
887
   *   An operation like: create, read, update, or delete.
888
   * @param object $account
889
   *   (optional) The account to check access for.
890
   *
891
   * @return bool
892
   *   TRUE if access is granted; otherwise FALSE.
893
   */
894
  public function access($op, $account = NULL) {
895
    global $user;
896

    
897
    if (!$account) {
898
      $account = $user;
899
    }
900

    
901
    // Even administrators need to go through the access system. However, to
902
    // support legacy plugins, user 1 gets full access no matter what.
903
    if ($account->uid == 1) {
904
      return TRUE;
905
    }
906

    
907
    if (!in_array($op, array('create', 'read', 'update', 'delete', 'change layout'))) {
908
      return FALSE;
909
    }
910

    
911
    if (empty($this->storage_type) || empty($this->storage_id)) {
912
      return FALSE;
913
    }
914

    
915
    if ($this->storage_type == 'unknown') {
916
      return FALSE;
917
    }
918

    
919
    $storage_plugin = panels_get_panels_storage_plugin($this->storage_type);
920
    if (!$storage_plugin) {
921
      return FALSE;
922
    }
923

    
924
    $access_callback = panels_plugin_get_function('panels_storage', $storage_plugin, 'access callback');
925
    if (!$access_callback) {
926
      return FALSE;
927
    }
928

    
929
    return $access_callback($this->storage_type, $this->storage_id, $op, $account);
930
  }
931

    
932
}
933

    
934
/**
935
 * End of 'defgroup mainapi', although other functions are specifically added later.
936
 */
937

    
938
/**
939
 * Creates a new display, setting the ID to our magic new id.
940
 */
941
function panels_new_display() {
942
  ctools_include('export');
943
  $display = ctools_export_new_object('panels_display', FALSE);
944
  $display->did = 'new';
945
  return $display;
946
}
947

    
948
/**
949
 * Create a new pane.
950
 *
951
 * @todo -- use schema API for some of this?
952
 */
953
function panels_new_pane($type, $subtype, $set_defaults = FALSE) {
954
  ctools_include('export');
955
  $pane = ctools_export_new_object('panels_pane', FALSE);
956
  $pane->pid = 'new';
957
  $pane->type = $type;
958
  $pane->subtype = $subtype;
959
  if ($set_defaults) {
960
    ctools_include('content');
961
    $content_type = ctools_get_content_type($type);
962
    $content_subtype = ctools_content_get_subtype($content_type, $subtype);
963
    $pane->configuration = ctools_content_get_defaults($content_type, $content_subtype);
964
  }
965
  drupal_alter('panels_new_pane', $pane);
966

    
967
  return $pane;
968
}
969

    
970
/**
971
 * Load and fill the requested $display object(s).
972
 *
973
 * Helper function primarily for for panels_load_display().
974
 *
975
 * @param array $dids
976
 *   An indexed array of dids to be loaded from the database.
977
 *
978
 * @return array
979
 *   An array of displays, keyed by their display dids.
980
 *
981
 * @todo schema API can drasticly simplify this code.
982
 */
983
function panels_load_displays($dids) {
984
  $displays = array();
985
  if (empty($dids) || !is_array($dids)) {
986
    return $displays;
987
  }
988

    
989
  $result = db_query(
990
    "SELECT * FROM {panels_display} WHERE did IN (:dids)",
991
    array(':dids' => $dids)
992
  );
993

    
994
  ctools_include('export');
995
  foreach ($result as $obj) {
996
    $displays[$obj->did] = ctools_export_unpack_object('panels_display', $obj);
997
    // Modify the hide_title field to go from a bool to an int if necessary.
998
  }
999

    
1000
  $result = db_query(
1001
    "SELECT * FROM {panels_pane} WHERE did IN (:dids) ORDER BY did, panel, position",
1002
    array(':dids' => $dids)
1003
  );
1004
  foreach ($result as $obj) {
1005
    $pane = ctools_export_unpack_object('panels_pane', $obj);
1006

    
1007
    $displays[$pane->did]->panels[$pane->panel][] = $pane->pid;
1008
    $displays[$pane->did]->content[$pane->pid] = $pane;
1009
  }
1010
  return $displays;
1011
}
1012

    
1013
/**
1014
 * Load a single display.
1015
 *
1016
 * @param int $did
1017
 *   The display id (did) of the display to be loaded.
1018
 *
1019
 * @return object $display
1020
 *   Returns a partially-loaded panels_display object. $display objects returned
1021
 *   from this function have only the following data:
1022
 *    - $display->did (the display id)
1023
 *    - $display->name (the 'name' of the display, where applicable - it often isn't)
1024
 *    - $display->layout (a string with the system name of the display's layout)
1025
 *    - $display->panel_settings (custom layout style settings contained in an associative array; NULL if none)
1026
 *    - $display->layout_settings (panel size and configuration settings for Flexible layouts; NULL if none)
1027
 *    - $display->css_id (the special css_id that has been assigned to this display, if any; NULL if none)
1028
 *    - $display->content (an array of pane objects, keyed by pane id (pid))
1029
 *    - $display->panels (an associative array of panel regions, each an indexed array of pids in the order they appear in that region)
1030
 *    - $display->cache (any relevant data from panels_simple_cache)
1031
 *    - $display->args
1032
 *    - $display->incoming_content
1033
 *   While all of these members are defined, $display->context is NEVER defined in the returned $display;
1034
 *   it must be set using one of the ctools_context_create() functions.
1035
 *
1036
 * @ingroup mainapi
1037
 */
1038
function panels_load_display($did) {
1039
  $displays = panels_load_displays(array($did));
1040
  if (!empty($displays)) {
1041
    return array_shift($displays);
1042
  }
1043
}
1044

    
1045
/**
1046
 * Save a display object.
1047
 *
1048
 * Note that a new $display only receives a real did once it is run through
1049
 * this function, and likewise for the pid of any new pane.
1050
 *
1051
 * Until then, a new display uses a string placeholder, 'new', in place of
1052
 * a real did, and a new pane (whether on a new $display or not) appends a
1053
 * universally-unique identifier (which is stored permanently in the 'uuid'
1054
 * field). This format is also used in place of the real pid for exports.
1055
 *
1056
 * @param object $display
1057
 *   The display object to be saved. Passed by reference so the caller need not
1058
 *   use the return value for any reason except convenience.
1059
 *
1060
 * @return object $display
1061
 *   This display panel display object to return.
1062
 *
1063
 * @ingroup mainapi
1064
 */
1065
function panels_save_display(&$display) {
1066
  $update = (isset($display->did) && is_numeric($display->did)) ? array('did') : array();
1067
  if (empty($display->uuid) || !ctools_uuid_is_valid($display->uuid)) {
1068
    $display->uuid = ctools_uuid_generate();
1069
  }
1070
  drupal_write_record('panels_display', $display, $update);
1071

    
1072
  $pids = array();
1073
  if ($update) {
1074
    // Get a list of all panes currently in the database for this display so we
1075
    // can know if there are panes that need to be deleted. (i.e, aren't
1076
    // currently in our list of panes).
1077
    $result = db_query(
1078
      "SELECT pid FROM {panels_pane} WHERE did = :did",
1079
      array(':did' => $display->did)
1080
    );
1081
    foreach ($result as $pane) {
1082
      $pids[$pane->pid] = $pane->pid;
1083
    }
1084
  }
1085

    
1086
  // Update all the panes.
1087
  ctools_include('plugins', 'panels');
1088
  ctools_include('content');
1089

    
1090
  foreach ($display->panels as $id => $panes) {
1091
    $position = 0;
1092
    $new_panes = array();
1093
    foreach ((array) $panes as $pid) {
1094
      if (!isset($display->content[$pid])) {
1095
        continue;
1096
      }
1097
      $pane = $display->content[$pid];
1098
      $type = ctools_get_content_type($pane->type);
1099

    
1100
      $pane->position = $position++;
1101
      $pane->did = $display->did;
1102

    
1103
      $old_pid = $pane->pid;
1104

    
1105
      if (empty($pane->uuid) || !ctools_uuid_is_valid($pane->uuid)) {
1106
        $pane->uuid = ctools_uuid_generate();
1107
      }
1108

    
1109
      drupal_write_record('panels_pane', $pane, is_numeric($pid) ? array('pid') : array());
1110

    
1111
      // Allow other modules to take action after a pane is saved.
1112
      if ($pane->pid == $old_pid) {
1113
        module_invoke_all('panels_pane_update', $pane);
1114
      }
1115
      else {
1116
        module_invoke_all('panels_pane_insert', $pane);
1117
      }
1118

    
1119
      if ($pane->pid != $old_pid) {
1120
        // Remove the old new-* entry from the displays content.
1121
        unset($display->content[$pid]);
1122

    
1123
        // Put it back so our pids and positions can be used.
1124
        $display->content[$pane->pid] = $pane;
1125

    
1126
        // If the title pane was one of our panes that just got its ID changed,
1127
        // we need to change it in the database, too.
1128
        if (isset($display->title_pane) && $display->title_pane == $old_pid) {
1129
          $display->title_pane = $pane->pid;
1130
          // Do a simple update query to write it so we don't have to rewrite
1131
          // the whole record. We can't just save writing the whole record here
1132
          // because it was needed to get the did. Chicken, egg, more chicken.
1133
          db_update('panels_display')
1134
            ->fields(array(
1135
              'title_pane' => $pane->pid,
1136
            ))
1137
            ->condition('did', $display->did)
1138
            ->execute();
1139
        }
1140
      }
1141

    
1142
      // re-add this to the list of content for this panel.
1143
      $new_panes[] = $pane->pid;
1144

    
1145
      // Remove this from the list of panes scheduled for deletion.
1146
      if (isset($pids[$pane->pid])) {
1147
        unset($pids[$pane->pid]);
1148
      }
1149
    }
1150

    
1151
    $display->panels[$id] = $new_panes;
1152
  }
1153
  if (!empty($pids)) {
1154
    // Allow other modules to take action before a panes are deleted.
1155
    module_invoke_all('panels_pane_delete', $pids);
1156
    db_delete('panels_pane')->condition('pid', $pids)->execute();
1157
  }
1158

    
1159
  // Clear any cached content for this display.
1160
  panels_clear_cached_content($display);
1161

    
1162
  // Allow other modules to take action when a display is saved.
1163
  module_invoke_all('panels_display_save', $display);
1164

    
1165
  // Log the change to watchdog, using the same style as node.module.
1166
  $watchdog_args = array('%did' => $display->did);
1167
  if (!empty($display->title)) {
1168
    $watchdog_args['%title'] = $display->title;
1169
    watchdog('content', 'Panels: saved display "%title" with display id %did', $watchdog_args, WATCHDOG_NOTICE);
1170
  }
1171
  else {
1172
    watchdog('content', 'Panels: saved display with id %did', $watchdog_args, WATCHDOG_NOTICE);
1173
  }
1174

    
1175
  // To be nice, even though we have a reference.
1176
  return $display;
1177
}
1178

    
1179
/**
1180
 * Delete a display.
1181
 */
1182
function panels_delete_display($display) {
1183
  if (is_object($display)) {
1184
    $did = $display->did;
1185
  }
1186
  else {
1187
    $did = $display;
1188
  }
1189
  module_invoke_all('panels_delete_display', $did);
1190
  db_delete('panels_display')->condition('did', $did)->execute();
1191
  db_delete('panels_pane')->condition('did', $did)->execute();
1192
}
1193

    
1194
/**
1195
 * Exports the provided display into portable code.
1196
 *
1197
 * This function is primarily intended as a mechanism for cloning displays.
1198
 * It generates an exact replica (in code) of the provided $display, with
1199
 * the exception that it replaces all ids (dids and pids) with place-holder
1200
 * values (consisting of the display or pane's uuid, with a 'new-' prefix).
1201
 *
1202
 * Only once panels_save_display() is called on the code version of $display
1203
 * will the exported display be written to the database and permanently saved.
1204
 *
1205
 * @param object $display
1206
 *   This export function does no loading of additional data about the provided
1207
 *   display. Consequently, the caller should make sure that all the desired
1208
 *   data has been loaded into the $display before calling this function.
1209
 * @param string $prefix
1210
 *   A string prefix that is prepended to each line of exported code. This is
1211
 *   primarily used for prepending a double space when exporting so that the
1212
 *   code indents and lines up nicely.
1213
 *
1214
 * @return string $output
1215
 *   The passed-in $display expressed as code, ready to be imported. Import by
1216
 *   running eval($output) in the caller function; doing so will create a new
1217
 *   $display variable with all the exported values. Note that if you have
1218
 *   already defined a $display variable in the same scope as where you eval(),
1219
 *   your existing $display variable WILL be overwritten.
1220
 *
1221
 * @see panels_page_export() or _panels_page_fetch_display() for samples.
1222
 *
1223
 * @ingroup mainapi
1224
 */
1225
function panels_export_display($display, $prefix = '') {
1226
  ctools_include('export');
1227
  if (empty($display->uuid) || !ctools_uuid_is_valid($display->uuid)) {
1228
    $display->uuid = ctools_uuid_generate();
1229
  }
1230
  $display->did = 'new-' . $display->uuid;
1231
  $output = ctools_export_object('panels_display', $display, $prefix);
1232

    
1233
  // Initialize empty properties.
1234
  $output .= $prefix . '$display->content = array()' . ";\n";
1235
  $output .= $prefix . '$display->panels = array()' . ";\n";
1236
  $panels = array();
1237

    
1238
  $title_pid = 0;
1239
  if (!empty($display->content)) {
1240
    $region_counters = array();
1241
    foreach ($display->content as $pane) {
1242

    
1243
      if (!isset($pane->uuid) || !ctools_uuid_is_valid($pane->uuid)) {
1244
        $pane->uuid = ctools_uuid_generate();
1245
      }
1246
      $pid = 'new-' . $pane->uuid;
1247

    
1248
      if ($pane->pid == $display->title_pane) {
1249
        $title_pid = $pid;
1250
      }
1251
      $pane->pid = $pid;
1252
      $output .= ctools_export_object('panels_pane', $pane, $prefix);
1253
      $output .= $prefix . '$display->content[\'' . $pane->pid . '\'] = $pane' . ";\n";
1254
      if (!isset($region_counters[$pane->panel])) {
1255
        $region_counters[$pane->panel] = 0;
1256
      }
1257
      $output .= $prefix . '$display->panels[\'' . $pane->panel . '\'][' . $region_counters[$pane->panel]++ . '] = \'' . $pane->pid . "';\n";
1258
    }
1259
  }
1260
  $output .= $prefix . '$display->hide_title = ';
1261
  switch ($display->hide_title) {
1262
    case PANELS_TITLE_FIXED:
1263
      $output .= 'PANELS_TITLE_FIXED';
1264
      break;
1265

    
1266
    case PANELS_TITLE_NONE:
1267
      $output .= 'PANELS_TITLE_NONE';
1268
      break;
1269

    
1270
    case PANELS_TITLE_PANE:
1271
      $output .= 'PANELS_TITLE_PANE';
1272
      break;
1273
  }
1274
  $output .= ";\n";
1275

    
1276
  $output .= $prefix . '$display->title_pane =' . " '$title_pid';\n";
1277
  return $output;
1278
}
1279

    
1280
/**
1281
 * Panels Render Display.
1282
 *
1283
 * Render a display by loading the content into an appropriate
1284
 * array and then passing through to panels_render_layout.
1285
 *
1286
 * if $incoming_content is NULL, default content will be applied. Use
1287
 * an empty string to indicate no content.
1288
 *
1289
 * @ingroup hook_invocations
1290
 */
1291
function panels_render_display(&$display, $renderer = NULL) {
1292
  ctools_include('plugins', 'panels');
1293
  ctools_include('context');
1294

    
1295
  if (!empty($display->context)) {
1296
    if ($form_context = ctools_context_get_form($display->context)) {
1297
      $form_context->form['#theme'] = 'panels_render_display_form';
1298
      if (empty($form_context->form['#theme_wrappers']) || !in_array('form', $form_context->form['#theme_wrappers'])) {
1299
        $form_context->form['#theme_wrappers'][] = 'form';
1300
      }
1301
      $form_context->form['#display'] = &$display;
1302
      return $form_context->form;
1303
    }
1304
  }
1305
  return $display->render($renderer);
1306
}
1307

    
1308
/**
1309
 * Theme function to render our panel as a form.
1310
 *
1311
 * When rendering a display as a form, the entire display needs to be
1312
 * inside the <form> tag so that the form can be spread across the
1313
 * panes. This sets up the form system to be the main caller and we
1314
 * then operate as a theme function of the form.
1315
 */
1316
function theme_panels_render_display_form($vars) {
1317
  return $vars['element']['#display']->render();
1318
}
1319

    
1320
/**
1321
 * Panels layout icon function.
1322
 */
1323
function panels_print_layout_icon($id, $layout, $title = NULL) {
1324
  ctools_add_css('panels_admin', 'panels');
1325
  $file = $layout['path'] . '/' . $layout['icon'];
1326
  return theme(
1327
    'panels_layout_icon',
1328
    array(
1329
      'id' => $id,
1330
      'image' => theme(
1331
        'image',
1332
        array(
1333
          'path' => $file,
1334
          'alt' => strip_tags($layout['title']),
1335
          'title' => strip_tags($layout['description']),
1336
        )
1337
      ),
1338
      'title' => $title,
1339
    )
1340
  );
1341
}
1342

    
1343
/**
1344
 * Theme the layout icon image.
1345
 *
1346
 * @todo move to theme.inc
1347
 */
1348
function theme_panels_layout_icon($vars) {
1349
  $id = $vars['id'];
1350
  $image = $vars['image'];
1351
  $title = $vars['title'];
1352

    
1353
  $output = '<div class="layout-icon">';
1354
  $output .= $image;
1355
  if ($title) {
1356
    $output .= '<div class="caption">' . $title . '</div>';
1357
  }
1358
  $output .= '</div>';
1359
  return $output;
1360
}
1361

    
1362
/**
1363
 * Theme the layout link image.
1364
 *
1365
 * @layout
1366
 *
1367
 * @todo Why isn't this a template at this point?
1368
 * @todo Why does this take 4 arguments but only makes use of two?
1369
 */
1370
function theme_panels_layout_link($vars) {
1371
  $output = '<div class="' . implode(' ', $vars['class']) . '">';
1372
  $output .= $vars['image'];
1373
  $output .= '<div>' . $vars['title'] . '</div>';
1374
  $output .= '</div>';
1375
  return $output;
1376
}
1377

    
1378
/**
1379
 * Print the layout link. Sends out to a theme function.
1380
 *
1381
 * @layout
1382
 */
1383
function panels_print_layout_link($id, $layout, $link, $options = array(), $current_layout = FALSE) {
1384
  if (isset($options['query']['q'])) {
1385
    unset($options['query']['q']);
1386
  }
1387

    
1388
  // Setup classes for layout link, including current-layout information.
1389
  $class = array('layout-link');
1390
  if ($current_layout == $id) {
1391
    $options['attributes']['class'][] = 'current-layout-link';
1392
    $class[] = 'current-layout';
1393
  }
1394

    
1395
  ctools_add_css('panels_admin', 'panels');
1396
  $file = $layout['path'] . '/' . $layout['icon'];
1397
  $image = l(
1398
    theme('image', array('path' => $file)),
1399
    $link,
1400
    array('html' => TRUE) + $options
1401
  );
1402
  $title = l($layout['title'], $link, $options);
1403
  return theme(
1404
    'panels_layout_link',
1405
    array(
1406
      'title' => $title,
1407
      'image' => $image,
1408
      'class' => $class,
1409
    )
1410
  );
1411
}
1412

    
1413

    
1414
/**
1415
 * Panels Get legacy state.
1416
 *
1417
 * Gateway to the PanelsLegacyState class/object, which does all legacy state
1418
 * checks and provides information about the cause of legacy states as needed.
1419
 *
1420
 * @return PanelsLegacyState $legacy
1421
 *   Returns a legacy panels state.
1422
 */
1423
function panels_get_legacy_state() {
1424
  static $legacy = NULL;
1425
  if (!isset($legacy)) {
1426
    ctools_include('legacy', 'panels');
1427
    $legacy = new PanelsLegacyState();
1428
  }
1429
  return $legacy;
1430
}
1431

    
1432
/**
1433
 * Get the display that is currently being rendered as a page.
1434
 *
1435
 * Unlike in previous versions of this, this only returns the display,
1436
 * not the page itself, because there are a number of different ways
1437
 * to get to this point. It is hoped that the page data isn't needed
1438
 * at this point. If it turns out there is, we will do something else to
1439
 * get that functionality.
1440
 */
1441
function panels_get_current_page_display($change = NULL) {
1442
  static $display = NULL;
1443
  if ($change) {
1444
    $display = $change;
1445
  }
1446

    
1447
  return $display;
1448
}
1449

    
1450
/**
1451
 * Clean up the panel pane variables for the template.
1452
 */
1453
function template_preprocess_panels_pane(&$vars) {
1454
  $content = &$vars['content'];
1455

    
1456
  $vars['contextual_links'] = array();
1457
  $vars['classes_array'] = array();
1458
  $vars['admin_links'] = '';
1459

    
1460
  if (module_exists('contextual') && user_access('access contextual links')) {
1461
    $links = array();
1462
    // These are specified by the content.
1463
    if (!empty($content->admin_links)) {
1464
      $links += $content->admin_links;
1465
    }
1466

    
1467
    // Take any that may have been in the render array we were given and
1468
    // move them up so they appear outside the pane properly.
1469
    if (is_array($content->content) && isset($content->content['#contextual_links'])) {
1470
      $element = array(
1471
        '#type' => 'contextual_links',
1472
        '#contextual_links' => $content->content['#contextual_links'],
1473
      );
1474
      unset($content->content['#contextual_links']);
1475

    
1476
      // Add content to $element array.
1477
      if (is_array($content->content)) {
1478
        $element['#element'] = $content->content;
1479
      }
1480

    
1481
      $element = contextual_pre_render_links($element);
1482
      if (!empty($element['#links'])) {
1483
        $links += $element['#links'];
1484
      }
1485
    }
1486

    
1487
    if ($links) {
1488
      $build = array(
1489
        '#prefix' => '<div class="contextual-links-wrapper">',
1490
        '#suffix' => '</div>',
1491
        '#theme' => 'links__contextual',
1492
        '#links' => $links,
1493
        '#attributes' => array('class' => array('contextual-links')),
1494
        '#attached' => array(
1495
          'library' => array(array('contextual', 'contextual-links')),
1496
        ),
1497
      );
1498
      $vars['classes_array'][] = 'contextual-links-region';
1499
      $vars['admin_links'] = drupal_render($build);
1500
    }
1501
  }
1502

    
1503
  // Basic classes.
1504
  $vars['classes_array'][] = 'panel-pane';
1505
  $vars['id'] = '';
1506

    
1507
  // Add some usable classes based on type/subtype.
1508
  ctools_include('cleanstring');
1509
  $type_class = $content->type ? 'pane-' . ctools_cleanstring($content->type, array('lower case' => TRUE)) : '';
1510
  $subtype_class = $content->subtype ? 'pane-' . ctools_cleanstring($content->subtype, array('lower case' => TRUE)) : '';
1511

    
1512
  // Sometimes type and subtype are the same. Avoid redundant classes.
1513
  $vars['classes_array'][] = $type_class;
1514
  if ($type_class != $subtype_class) {
1515
    $vars['classes_array'][] = $subtype_class;
1516
  }
1517

    
1518
  // Add id and custom class if sent in.
1519
  if (!empty($content->content)) {
1520
    if (!empty($content->css_id)) {
1521
      $vars['id'] = ' id="' . $content->css_id . '"';
1522
    }
1523
    if (!empty($content->css_class)) {
1524
      $vars['classes_array'][] = $content->css_class;
1525
    }
1526
  }
1527

    
1528
  // Set up some placeholders for constructing template file names.
1529
  $base = 'panels_pane';
1530
  $delimiter = '__';
1531

    
1532
  // Add template file suggestion for content type and sub-type.
1533
  $vars['theme_hook_suggestions'][] = $base . $delimiter . $content->type;
1534
  $vars['theme_hook_suggestions'][] = $base . $delimiter . strtr(ctools_cleanstring($content->type, array('lower case' => TRUE)), '-', '_') . $delimiter . strtr(ctools_cleanstring($content->subtype, array('lower case' => TRUE)), '-', '_');
1535

    
1536
  $vars['pane_prefix'] = !empty($content->pane_prefix) ? $content->pane_prefix : '';
1537
  $vars['pane_suffix'] = !empty($content->pane_suffix) ? $content->pane_suffix : '';
1538

    
1539
  $vars['title'] = !empty($content->title) ? $content->title : '';
1540
  $vars['title_heading'] = !empty($content->title_heading) ? $content->title_heading : variable_get('override_title_heading', 'h2');
1541
  $vars['title_attributes_array']['class'][] = 'pane-title';
1542

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

    
1545
  $vars['links'] = !empty($content->links) ? theme('links', array('links' => $content->links)) : '';
1546
  $vars['more'] = '';
1547
  if (!empty($content->more)) {
1548
    if (empty($content->more['title'])) {
1549
      $content->more['title'] = t('more');
1550
    }
1551
    $vars['more'] = l($content->more['title'], $content->more['href'], $content->more);
1552
  }
1553

    
1554
  if (!empty($content->attributes)) {
1555
    $vars['attributes_array'] = array_merge($vars['attributes_array'], $content->attributes);
1556
  }
1557

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

    
1560
}
1561

    
1562
/**
1563
 * Route Panels' AJAX calls to the correct object.
1564
 *
1565
 * Panels' AJAX is controlled mostly by renderer objects. This menu callback
1566
 * accepts the incoming request, figures out which object should handle the
1567
 * request, and attempts to route it. If no object can be found, the default
1568
 * Panels editor object is used.
1569
 *
1570
 * Calls are routed via the ajax_* method space. For example, if visiting
1571
 * panels/ajax/add-pane then $renderer::ajax_add_pane() will be called.
1572
 * This means commands can be added without having to create new callbacks.
1573
 *
1574
 * The first argument *must always* be the cache key so that a cache object
1575
 * can be passed through. Other arguments will be passed through untouched
1576
 * so that the method can do whatever it needs to do.
1577
 */
1578
function panels_ajax_router() {
1579
  $args = func_get_args();
1580
  if (count($args) < 3) {
1581
    return MENU_NOT_FOUND;
1582
  }
1583

    
1584
  ctools_include('display-edit', 'panels');
1585
  ctools_include('plugins', 'panels');
1586
  ctools_include('ajax');
1587
  ctools_include('modal');
1588
  ctools_include('context');
1589
  ctools_include('content');
1590

    
1591
  $plugin_name = array_shift($args);
1592
  $method = array_shift($args);
1593
  $cache_key = array_shift($args);
1594

    
1595
  $plugin = panels_get_display_renderer($plugin_name);
1596
  if (!$plugin) {
1597
    // This is the default renderer for handling AJAX commands.
1598
    $plugin = panels_get_display_renderer('editor');
1599
  }
1600

    
1601
  $cache = panels_edit_cache_get($cache_key);
1602
  if (empty($cache)) {
1603
    return MENU_ACCESS_DENIED;
1604
  }
1605

    
1606
  $renderer = panels_get_renderer_handler($plugin, $cache->display);
1607
  if (!$renderer) {
1608
    return MENU_ACCESS_DENIED;
1609
  }
1610

    
1611
  $method = 'ajax_' . str_replace('-', '_', $method);
1612
  if (!method_exists($renderer, $method)) {
1613
    return MENU_NOT_FOUND;
1614
  }
1615

    
1616
  $renderer->cache = &$cache;
1617
  ctools_include('cleanstring');
1618
  $renderer->clean_key = ctools_cleanstring($cache_key);
1619

    
1620
  $op = $renderer->get_panels_storage_op_for_ajax($method);
1621
  if (!$cache->display->access($op)) {
1622
    return MENU_ACCESS_DENIED;
1623
  }
1624

    
1625
  $output = call_user_func_array(array($renderer, $method), $args);
1626

    
1627
  if (empty($output) && !empty($renderer->commands)) {
1628
    return array(
1629
      '#type' => 'ajax',
1630
      '#commands' => $renderer->commands,
1631
    );
1632
  }
1633
  else {
1634
    return $output;
1635
  }
1636
}
1637

    
1638
/**
1639
 * Panels caching functions and callbacks.
1640
 *
1641
 * When editing displays and the like, Panels has a caching system that relies
1642
 * on a callback to determine where to get the actual cache.
1643
 *
1644
 * @todo This system needs to be better documented so that it can be better used.
1645
 */
1646

    
1647
/**
1648
 * Get an object from cache.
1649
 */
1650
function panels_cache_get($obj, $did, $skip_cache = FALSE) {
1651
  ctools_include('object-cache');
1652
  // We often store contexts in cache, so let's just make sure we can load them.
1653
  ctools_include('context');
1654
  return ctools_object_cache_get($obj, 'panels_display:' . $did, $skip_cache);
1655
}
1656

    
1657
/**
1658
 * Save the edited object into the cache.
1659
 */
1660
function panels_cache_set($obj, $did, $cache) {
1661
  ctools_include('object-cache');
1662
  return ctools_object_cache_set($obj, 'panels_display:' . $did, $cache);
1663
}
1664

    
1665
/**
1666
 * Clear a object from the cache; used if the editing is aborted.
1667
 */
1668
function panels_cache_clear($obj, $did) {
1669
  ctools_include('object-cache');
1670
  return ctools_object_cache_clear($obj, 'panels_display:' . $did);
1671
}
1672

    
1673
/**
1674
 * Create the default cache for editing panel displays.
1675
 *
1676
 * If an application is using the Panels display editor without having
1677
 * specified a cache key, this method can be used to create the default
1678
 * cache.
1679
 */
1680
function panels_edit_cache_get_default(&$display, $content_types = NULL, $title = FALSE) {
1681
  if (empty($content_types)) {
1682
    $content_types = ctools_content_get_available_types();
1683
  }
1684

    
1685
  $display->cache_key = $display->did;
1686
  panels_cache_clear('display', $display->did);
1687

    
1688
  $cache = new stdClass();
1689
  $cache->display = &$display;
1690
  $cache->content_types = $content_types;
1691
  $cache->display_title = $title;
1692

    
1693
  panels_edit_cache_set($cache);
1694
  return $cache;
1695
}
1696

    
1697
/**
1698
 * Panels Editor Cache Get.
1699
 *
1700
 * Method to allow modules to provide their own caching mechanism for the
1701
 * display editor.
1702
 */
1703
function panels_edit_cache_get($cache_key) {
1704
  if (strpos($cache_key, ':') !== FALSE) {
1705
    list($module, $argument) = explode(':', $cache_key, 2);
1706
    return module_invoke($module, 'panels_cache_get', $argument);
1707
  }
1708

    
1709
  // Fall back to our normal method.
1710
  return panels_cache_get('display', $cache_key);
1711
}
1712

    
1713
/**
1714
 * Panels Editor Cache Set.
1715
 *
1716
 * Method to allow modules to provide their own caching mechanism for the
1717
 * display editor.
1718
 */
1719
function panels_edit_cache_set($cache) {
1720
  $cache_key = $cache->display->cache_key;
1721
  if (strpos($cache_key, ':') !== FALSE) {
1722
    list($module, $argument) = explode(':', $cache_key, 2);
1723
    return module_invoke($module, 'panels_cache_set', $argument, $cache);
1724
  }
1725

    
1726
  // Fall back to our normal method.
1727
  return panels_cache_set('display', $cache_key, $cache);
1728
}
1729

    
1730
/**
1731
 * Panels Editor Cache Save.
1732
 *
1733
 * Method to allow modules to provide their own mechanism to write the
1734
 * cache used in the display editor.
1735
 */
1736
function panels_edit_cache_save($cache) {
1737
  $cache_key = $cache->display->cache_key;
1738
  if (strpos($cache_key, ':') !== FALSE) {
1739
    list($module, $argument) = explode(':', $cache_key, 2);
1740
    if (function_exists($module . '_panels_cache_save')) {
1741
      return module_invoke($module, 'panels_cache_save', $argument, $cache);
1742
    }
1743
  }
1744

    
1745
  // Fall back to our normal method.
1746
  return panels_save_display($cache->display);
1747
}
1748

    
1749
/**
1750
 * Panels Editor Cache Clear.
1751
 *
1752
 * Method to allow modules to provide their own mechanism to clear the
1753
 * cache used in the display editor.
1754
 */
1755
function panels_edit_cache_clear($cache) {
1756
  $cache_key = $cache->display->cache_key;
1757
  if (strpos($cache_key, ':') !== FALSE) {
1758
    list($module, $argument) = explode(':', $cache_key, 2);
1759
    if (function_exists($module . '_panels_cache_clear')) {
1760
      return module_invoke($module, 'panels_cache_clear', $argument, $cache);
1761
    }
1762
  }
1763

    
1764
  // Fall back to our normal method.
1765
  return panels_cache_clear('display', $cache_key);
1766
}
1767

    
1768
/**
1769
 * Method to allow modules to provide a mechanism to break locks.
1770
 */
1771
function panels_edit_cache_break_lock($cache) {
1772
  if (empty($cache->locked)) {
1773
    return;
1774
  }
1775

    
1776
  $cache_key = $cache->display->cache_key;
1777
  if (strpos($cache_key, ':') !== FALSE) {
1778
    list($module, $argument) = explode(':', $cache_key, 2);
1779
    if (function_exists($module . '_panels_cache_break_lock')) {
1780
      return module_invoke($module, 'panels_cache_break_lock', $argument, $cache);
1781
    }
1782
  }
1783

    
1784
  // Normal panel display editing has no locks, so we do nothing if there is
1785
  // no fallback.
1786
}
1787

    
1788
/**
1789
 * Callbacks on behalf of the panel_context plugin.
1790
 *
1791
 * The panel_context plugin lets Panels be used in page manager. These
1792
 * callbacks allow the display editing system to use the page manager
1793
 * cache rather than the default display cache. They are routed by the cache
1794
 * key via panels_edit_cache_* functions.
1795
 */
1796

    
1797
/**
1798
 * Get display edit cache on behalf of panel context.
1799
 *
1800
 * The key is the second half of the key in this form:
1801
 * panel_context:TASK_NAME::HANDLER_NAME::args::url;
1802
 */
1803
function panel_context_panels_cache_get($key) {
1804
  ctools_include('common', 'panels');
1805
  ctools_include('context');
1806
  ctools_include('context-task-handler');
1807
  // This loads the panel context inc even if we don't use the plugin.
1808
  $plugin = page_manager_get_task_handler('panel_context');
1809

    
1810
  list($task_name, $handler_name, $args, $q) = explode('::', $key, 4);
1811
  $page = page_manager_get_page_cache($task_name);
1812
  if (isset($page->display_cache[$handler_name])) {
1813
    return $page->display_cache[$handler_name];
1814
  }
1815

    
1816
  if ($handler_name) {
1817
    $handler = &$page->handlers[$handler_name];
1818
  }
1819
  else {
1820
    $handler = &$page->new_handler;
1821
  }
1822
  $cache = new stdClass();
1823

    
1824
  $task = page_manager_get_task($page->task_id);
1825
  $arguments = array();
1826
  if ($args) {
1827
    $arguments = explode('\\', $args);
1828
    $contexts = ctools_context_handler_get_task_contexts($task, $page->subtask, $arguments);
1829
    $contexts = ctools_context_handler_get_handler_contexts($contexts, $handler);
1830
  }
1831
  else {
1832
    $contexts = ctools_context_handler_get_all_contexts($page->task, $page->subtask, $handler);
1833
  }
1834

    
1835
  $cache->display = &panels_panel_context_get_display($handler);
1836
  $cache->display->context = $contexts;
1837
  $cache->display->cache_key = 'panel_context:' . $key;
1838
  $cache->content_types = panels_common_get_allowed_types('panels_page', $cache->display->context);
1839
  $cache->display_title = TRUE;
1840
  $cache->locked = $page->locked;
1841

    
1842
  return $cache;
1843
}
1844

    
1845
/**
1846
 * Get the Page Manager cache for the panel_context plugin.
1847
 */
1848
function _panel_context_panels_cache_get_page_cache($key, $cache) {
1849
  list($task_name, $handler_name, $args, $q) = explode('::', $key, 4);
1850
  $page = page_manager_get_page_cache($task_name);
1851
  $page->display_cache[$handler_name] = $cache;
1852
  if ($handler_name) {
1853
    $page->handlers[$handler_name]->conf['display'] = $cache->display;
1854
    $page->handler_info[$handler_name]['changed'] |= PAGE_MANAGER_CHANGED_CACHED;
1855
  }
1856
  else {
1857
    $page->new_handler->conf['display'] = $cache->display;
1858
  }
1859

    
1860
  return $page;
1861
}
1862

    
1863
/**
1864
 * Store a display edit in progress in the page cache.
1865
 */
1866
function panel_context_panels_cache_set($key, $cache) {
1867
  $page = _panel_context_panels_cache_get_page_cache($key, $cache);
1868
  page_manager_set_page_cache($page);
1869
}
1870

    
1871
/**
1872
 * Save all changes made to a display using the Page Manager page cache.
1873
 */
1874
function panel_context_panels_cache_clear($key, $cache) {
1875
  $page = _panel_context_panels_cache_get_page_cache($key, $cache);
1876
  page_manager_clear_page_cache($page->task_name);
1877
}
1878

    
1879
/**
1880
 * Save all changes made to a display using the Page Manager page cache.
1881
 */
1882
function panel_context_panels_cache_save($key, $cache) {
1883
  $page = _panel_context_panels_cache_get_page_cache($key, $cache);
1884
  page_manager_save_page_cache($page);
1885
}
1886

    
1887
/**
1888
 * Break the lock on a page manager page.
1889
 */
1890
function panel_context_panels_cache_break_lock($key, $cache) {
1891
  $page = _panel_context_panels_cache_get_page_cache($key, $cache);
1892
  ctools_object_cache_clear_all('page_manager_page', $page->task_name);
1893
}
1894

    
1895
/**
1896
 * Callbacks on behalf of the panels page wizards.
1897
 *
1898
 * The page wizards are a pluggable set of 'wizards' to make it easy to create
1899
 * specific types of pages based upon whatever someone felt like putting
1900
 * together. Since they will very often have content editing, we provide
1901
 * a generic mechanism to allow them to store their editing cache in the
1902
 * wizard cache.
1903
 *
1904
 * For them to use this mechanism, they just need to use:
1905
 * $cache = panels_edit_cache_get('panels_page_wizard:' . $plugin['name']);.
1906
 */
1907

    
1908
/**
1909
 * Get display edit cache for the panels mini export UI.
1910
 *
1911
 * The key is the second half of the key in this form:
1912
 * panels_page_wizard:TASK_NAME:HANDLER_NAME;
1913
 */
1914
function panels_page_wizard_panels_cache_get($key) {
1915
  ctools_include('page-wizard');
1916
  ctools_include('context');
1917
  $wizard_cache = page_manager_get_wizard_cache($key);
1918
  if (isset($wizard_cache->display_cache)) {
1919
    return $wizard_cache->display_cache;
1920
  }
1921

    
1922
  ctools_include('common', 'panels');
1923
  $cache = new stdClass();
1924
  $cache->display = $wizard_cache->display;
1925
  $cache->display->context = !empty($wizard_cache->context) ? $wizard_cache->context : array();
1926
  $cache->display->cache_key = 'panels_page_wizard:' . $key;
1927
  $cache->content_types = panels_common_get_allowed_types('panels_page', $cache->display->context);
1928
  $cache->display_title = TRUE;
1929

    
1930
  return $cache;
1931
}
1932

    
1933
/**
1934
 * Store a display edit in progress in the page cache.
1935
 */
1936
function panels_page_wizard_panels_cache_set($key, $cache) {
1937
  ctools_include('page-wizard');
1938
  $wizard_cache = page_manager_get_wizard_cache($key);
1939
  $wizard_cache->display_cache = $cache;
1940
  page_manager_set_wizard_cache($wizard_cache);
1941
}
1942

    
1943
/**
1944
 * Implements hook_default_page_manager_handlers_alter().
1945
 *
1946
 * If a default Panels display has no storage type, set it.
1947
 */
1948
function panels_default_page_manager_handlers_alter(&$handlers) {
1949
  foreach ($handlers as &$handler) {
1950
    if ($handler->handler == 'panel_context') {
1951
      $display =& $handler->conf['display'];
1952
      if (empty($display->storage_type)) {
1953
        $display->storage_type = 'page_manager';
1954
        $display->storage_id = $handler->name;
1955
      }
1956
    }
1957
  }
1958
}
1959

    
1960
/**
1961
 * Implements hook_default_page_manager_pages_alter().
1962
 */
1963
function panels_default_page_manager_pages_alter(&$pages) {
1964
  foreach ($pages as &$page) {
1965
    panels_default_page_manager_handlers_alter($page->default_handlers);
1966
  }
1967
}
1968

    
1969
/**
1970
 * General utility functions.
1971
 */
1972

    
1973
/**
1974
 * Perform a drupal_goto on a destination that may be an array like url().
1975
 */
1976
function panels_goto($destination) {
1977
  if (!is_array($destination)) {
1978
    return drupal_goto($destination);
1979
  }
1980
  else {
1981
    // Prevent notices by adding defaults.
1982
    $destination += array(
1983
      'query' => NULL,
1984
      'fragment' => NULL,
1985
      'http_response_code' => NULL,
1986
    );
1987

    
1988
    return drupal_goto(
1989
      $destination['path'],
1990
      $destination['query'],
1991
      $destination['fragment'],
1992
      $destination['http_response_code']
1993
    );
1994
  }
1995
}
1996

    
1997

    
1998
/**
1999
 * For external use: Given a layout ID and $content array, return panel display.
2000
 *
2001
 * The content array is filled in based upon the content available in the
2002
 * layout. If it's a two column with a content array defined like.
2003
 * @code
2004
 *   array(
2005
 *    'left' => t('Left side'),
2006
 *    'right' => t('Right side')
2007
 *  ),
2008
 *
2009
 * Then the $content array should be
2010
 * @code
2011
 * array(
2012
 *   'left' => $output_left,
2013
 *   'right' => $output_right,
2014
 * )
2015
 *
2016
 * The output within each panel region can be either a single rendered
2017
 * HTML string or an array of rendered HTML strings as though they were
2018
 * panes. They will simply be concatenated together without separators.
2019
 */
2020
function panels_print_layout($layout, $content, $meta = 'standard') {
2021
  ctools_include('plugins', 'panels');
2022

    
2023
  // Create a temporary display for this.
2024
  $display = panels_new_display();
2025
  $display->layout = is_array($layout) ? $layout['name'] : $layout;
2026
  $display->content = $content;
2027

    
2028
  // Get our simple renderer.
2029
  $renderer = panels_get_renderer_handler('simple', $display);
2030
  $renderer->meta_location = $meta;
2031

    
2032
  return $renderer->render();
2033
}
2034

    
2035
/**
2036
 * Filter callback for array_filter to remove builders from a list of layouts.
2037
 */
2038
function _panels_builder_filter($layout) {
2039
  return empty($layout['builder']);
2040
}
2041

    
2042
/**
2043
 * Implements hook_get_pane_links_alter().
2044
 */
2045
function panels_get_pane_links_alter(&$links, $pane, $content_type) {
2046
  // Add links to the Panels pane dropdown menu.
2047
  if ($pane->type === "block") {
2048
    $prefixed_name = $pane->subtype;
2049

    
2050
    // Breakup the subtype string into parts.
2051
    $exploded_subtype = explode('-', $pane->subtype);
2052

    
2053
    // Get the first part of the string.
2054
    $subtype_prefix = $exploded_subtype[0];
2055

    
2056
    // Get the first part of the string and add a hyphen.
2057
    $subtype_prefix_hyphen = $exploded_subtype[0] . '-';
2058

    
2059
    // Remove the prefix block- to get the name.
2060
    $name_of_block = ltrim($prefixed_name, $subtype_prefix_hyphen);
2061

    
2062
    // Check for user added menus created at /admin/structure/menu/add
2063
    // menus of that type have a subtype that is prefixed with menu-menu-.
2064
    if (substr($prefixed_name, 0, 10) === "menu-menu-") {
2065
      // Remove the first prefix menu- from menu-menu- to get the name.
2066
      $name_of_block = substr($prefixed_name, 5);
2067

    
2068
      $links['top'][] = array(
2069
        'title' => t('Edit block'),
2070
        'href' => url('admin/structure/block/manage/' . $subtype_prefix . '/' . $name_of_block . '/configure', array('absolute' => TRUE)),
2071
        'attributes' => array('target' => array('_blank')),
2072
      );
2073

    
2074
      $links['top'][] = array(
2075
        'title' => t('Edit menu links'),
2076
        'href' => url('admin/structure/menu/manage/' . $name_of_block, array('absolute' => TRUE)),
2077
        'attributes' => array('target' => array('_blank')),
2078
      );
2079
    }
2080

    
2081
    // Check for module provided menu blocks like Devels or Features
2082
    // menus of that type have a subtype that is prefixed with menu-.
2083
    elseif (substr($prefixed_name, 0, 5) === "menu-") {
2084
      // Remove the first prefix menu- to get the name.
2085
      $name_of_block = substr($prefixed_name, 5);
2086

    
2087
      $links['top'][] = array(
2088
        'title' => t('Edit block'),
2089
        'href' => url('admin/structure/block/manage/' . $subtype_prefix . '/' . $name_of_block . '/configure', array('absolute' => TRUE)),
2090
        'attributes' => array('target' => array('_blank')),
2091
      );
2092

    
2093
      $links['top'][] = array(
2094
        'title' => t('Edit menu links'),
2095
        'href' => url('admin/structure/menu/manage/' . $name_of_block, array('absolute' => TRUE)),
2096
        'attributes' => array('target' => array('_blank')),
2097
      );
2098
    }
2099

    
2100
    // Check for system blocks with menu links.
2101
    elseif (substr($prefixed_name, 0, 7) === "system-") {
2102
      // Remove the first prefix system- to get the name.
2103
      $name_of_block = substr($prefixed_name, 7);
2104

    
2105
      $names_of_system_menus = menu_list_system_menus();
2106

    
2107
      $links['top'][] = array(
2108
        'title' => t('Edit block'),
2109
        'href' => url('admin/structure/block/manage/' . $subtype_prefix . '/' . $name_of_block . '/configure', array('absolute' => TRUE)),
2110
        'attributes' => array('target' => array('_blank')),
2111
      );
2112

    
2113
      if (array_key_exists($name_of_block, $names_of_system_menus)) {
2114
        $links['top'][] = array(
2115
          'title' => t('Edit menu links'),
2116
          'href' => url('admin/structure/menu/manage/' . $name_of_block, array('absolute' => TRUE)),
2117
          'attributes' => array('target' => array('_blank')),
2118
        );
2119
      }
2120
    }
2121

    
2122
    // For all other blocks without menus.
2123
    else {
2124
      $links['top'][] = array(
2125
        'title' => t('Edit block'),
2126
        'href' => url('admin/structure/block/manage/' . $subtype_prefix . '/' . $name_of_block . '/configure', array('absolute' => TRUE)),
2127
        'attributes' => array('target' => array('_blank')),
2128
      );
2129
    }
2130
  }
2131
}
2132

    
2133
/**
2134
 * Deprecated functions.
2135
 *
2136
 * Everything below this line will eventually go away.
2137
 */
2138

    
2139
/**
2140
 * Panels path helper function.
2141
 */
2142
function panels_get_path($file, $base_path = FALSE, $module = 'panels') {
2143
  $output = $base_path ? base_path() : '';
2144
  return $output . drupal_get_path('module', $module) . '/' . $file;
2145
}
2146

    
2147
/**
2148
 * Remove default sidebar related body classes and provide own css classes.
2149
 */
2150
function panels_preprocess_html(&$vars) {
2151
  $panel_body_css = &drupal_static('panel_body_css');
2152
  if (!empty($panel_body_css['body_classes_to_remove'])) {
2153
    $classes_to_remove = array_filter(explode(' ', $panel_body_css['body_classes_to_remove']), 'strlen');
2154
    foreach ($vars['classes_array'] as $key => $css_class) {
2155
      if (in_array($css_class, $classes_to_remove)) {
2156
        unset($vars['classes_array'][$key]);
2157
      }
2158
    }
2159
  }
2160
  if (!empty($panel_body_css['body_classes_to_add'])) {
2161
    $vars['classes_array'][] = check_plain($panel_body_css['body_classes_to_add']);
2162
  }
2163
}