Remove older unneeded file
[project/panels.git] / panels.module
CommitLineData
9f3609c9
EM
1<?php
2// $Id$
3
b76f0bd4
SB
4/**
5 * @file panels.module
6 *
7 * Core functionality for the Panels engine.
8 */
9
2f354acb 10define('PANELS_REQUIRED_CTOOLS_API', '2.0-alpha');
26e7b0ef
SB
11
12define('PANELS_TITLE_FIXED', 0); // Hide title use to be true/false. So false remains old behavior.
13define('PANELS_TITLE_NONE', 1); // And true meant no title.
14define('PANELS_TITLE_PANE', 2); // And this is the new behavior, where the title field will pick from a pane.
4d371f78 15
9f3609c9 16/**
f2e5a0c5
EM
17 * Returns the API version of Panels. This didn't exist in 1.
18 *
2f354acb
SB
19 * @todo -- this should work more like the CTools API version.
20 *
f2e5a0c5 21 * @return An array with the major and minor versions
9f3609c9 22 */
f2e5a0c5 23function panels_api_version() {
946024d2 24 return array(3, 1);
f2e5a0c5
EM
25}
26
946024d2
SB
27// --------------------------------------------------------------------------
28// Core Drupal hook implementations
29
26e7b0ef
SB
30/**
31 * Implementation of hook_theme()
32 */
f2e5a0c5 33function panels_theme() {
26e7b0ef
SB
34 // Safety: go away if CTools is not at an appropriate version.
35 if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
36 return array();
37 }
38
f2e5a0c5
EM
39 $theme = array();
40 $theme['panels_layout_link'] = array(
2f354acb 41 'variables' => array('title' => NULL, 'id' => NULL, 'image' => NULL, 'link' => NULL),
f2e5a0c5
EM
42 );
43 $theme['panels_layout_icon'] = array(
2f354acb 44 'variables' => array('id' => NULL, 'image' => NULL, 'title' => NULL),
f2e5a0c5 45 );
f2e5a0c5 46 $theme['panels_pane'] = array(
2f354acb 47 'variables' => array('output' => array(), 'pane' => array(), 'display' => array()),
26e7b0ef
SB
48 'path' => drupal_get_path('module', 'panels') . '/templates',
49 'template' => 'panels-pane',
f2e5a0c5
EM
50 );
51 $theme['panels_common_content_list'] = array(
2f354acb 52 'variables' => array('display' => NULL),
f2e5a0c5
EM
53 'file' => 'includes/common.inc',
54 );
26e7b0ef 55 $theme['panels_render_display_form'] = array(
2f354acb 56 'variables' => array('form' => NULL),
f2e5a0c5 57 );
26e7b0ef
SB
58
59 $theme['panels_dashboard'] = array(
2f354acb 60 'variables' => array(),
26e7b0ef
SB
61 'path' => drupal_get_path('module', 'panels') . '/templates',
62 'file' => '../includes/callbacks.inc',
63 'template' => 'panels-dashboard',
b76f0bd4 64 );
f2e5a0c5 65
946024d2 66 $theme['panels_dashboard_link'] = array(
2f354acb 67 'variables' => array('link' => array()),
946024d2
SB
68 'path' => drupal_get_path('module', 'panels') . '/templates',
69 'file' => '../includes/callbacks.inc',
70 'template' => 'panels-dashboard-link',
71 );
72
73 $theme['panels_dashboard_block'] = array(
2f354acb 74 'variables' => array('block' => array()),
946024d2
SB
75 'path' => drupal_get_path('module', 'panels') . '/templates',
76 'file' => '../includes/callbacks.inc',
77 'template' => 'panels-dashboard-block',
78 );
79
80 // We don't need layout and style themes in maintenance mode.
81 if (defined('MAINTENANCE_MODE')) {
82 return $theme;
83 }
84
f2e5a0c5 85 // Register layout and style themes on behalf of all of these items.
946024d2 86 ctools_include('plugins', 'panels');
f2e5a0c5
EM
87
88 // No need to worry about files; the plugin has to already be loaded for us
89 // to even know what the theme function is, so files will be auto included.
90 $layouts = panels_get_layouts();
91 foreach ($layouts as $name => $data) {
26e7b0ef
SB
92 foreach (array('theme', 'admin theme') as $callback) {
93 if (!empty($data[$callback])) {
94 $theme[$data[$callback]] = array(
2f354acb 95 'variables' => array('css_id' => NULL, 'content' => NULL, 'settings' => NULL, 'display' => NULL, 'layout' => NULL, 'renderer' => NULL),
26e7b0ef 96 'path' => $data['path'],
946024d2 97 'file' => $data['file'],
26e7b0ef
SB
98 );
99
100 // if no theme function exists, assume template.
101 if (!function_exists("theme_$data[theme]")) {
102 $theme[$data[$callback]]['template'] = str_replace('_', '-', $data[$callback]);
103 $theme[$data[$callback]]['file'] = $data['file']; // for preprocess.
104 }
fc8f8fa8 105 }
f2e5a0c5 106 }
9f3609c9 107 }
f2e5a0c5
EM
108
109 $styles = panels_get_styles();
110 foreach ($styles as $name => $data) {
111 if (!empty($data['render pane'])) {
112 $theme[$data['render pane']] = array(
2f354acb 113 'variables' => array('content' => NULL, 'pane' => NULL, 'display' => NULL, 'style' => NULL, 'settings' => NULL),
946024d2
SB
114 'path' => $data['path'],
115 'file' => $data['file'],
f2e5a0c5
EM
116 );
117 }
946024d2
SB
118 if (!empty($data['render region'])) {
119 $theme[$data['render region']] = array(
2f354acb 120 'variables' => array('display' => NULL, 'owner_id' => NULL, 'panes' => NULL, 'settings' => NULL, 'region_id' => NULL, 'style' => NULL),
946024d2
SB
121 'path' => $data['path'],
122 'file' => $data['file'],
f2e5a0c5
EM
123 );
124 }
125
26e7b0ef
SB
126 if (!empty($data['hook theme'])) {
127 if (is_array($data['hook theme'])) {
128 $theme += $data['hook theme'];
129 }
130 else if (function_exists($data['hook theme'])) {
131 $data['hook theme']($theme, $data);
132 }
133 }
f2e5a0c5
EM
134 }
135
136 return $theme;
9f3609c9
EM
137}
138
139/**
f2e5a0c5 140 * Implementation of hook_menu
9f3609c9 141 */
f2e5a0c5 142function panels_menu() {
26e7b0ef
SB
143 // Safety: go away if CTools is not at an appropriate version.
144 if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
145 return array();
146 }
f2e5a0c5
EM
147 $items = array();
148
946024d2
SB
149 // Base AJAX router callback.
150 $items['panels/ajax'] = array(
f2e5a0c5 151 'access arguments' => array('access content'),
946024d2 152 'page callback' => 'panels_ajax_router',
f2e5a0c5 153 'type' => MENU_CALLBACK,
f2e5a0c5
EM
154 );
155
26e7b0ef
SB
156 $admin_base = array(
157 'file' => 'includes/callbacks.inc',
158 'access arguments' => array('use panels dashboard'),
159 );
f2e5a0c5 160 // Provide a nice location for a panels admin panel.
2f354acb 161 $items['admin/structure/panels'] = array(
17a90678 162 'title' => 'Panels',
ade117f2 163 'page callback' => 'panels_admin_page',
946024d2 164 'description' => 'Get a bird\'s eye view of items related to Panels.',
26e7b0ef
SB
165 ) + $admin_base;
166
2f354acb 167 $items['admin/structure/panels/dashboard'] = array(
26e7b0ef
SB
168 'title' => 'Dashboard',
169 'page callback' => 'panels_admin_page',
170 'type' => MENU_DEFAULT_LOCAL_TASK,
171 'weight' => -10,
172 ) + $admin_base;
173
2f354acb 174 $items['admin/structure/panels/settings'] = array(
26e7b0ef
SB
175 'title' => 'Settings',
176 'page callback' => 'drupal_get_form',
177 'page arguments' => array('panels_admin_settings_page'),
178 'type' => MENU_LOCAL_TASK,
179 ) + $admin_base;
180
2f354acb 181 $items['admin/structure/panels/settings/general'] = array(
26e7b0ef
SB
182 'title' => 'General',
183 'page callback' => 'drupal_get_form',
184 'page arguments' => array('panels_admin_settings_page'),
185 'access arguments' => array('administer page manager'),
186 'type' => MENU_DEFAULT_LOCAL_TASK,
187 'weight' => -10,
188 ) + $admin_base;
189
190 if (module_exists('page_manager')) {
2f354acb 191 $items['admin/structure/panels/settings/panel-page'] = array(
26e7b0ef
SB
192 'title' => 'Panel pages',
193 'page callback' => 'panels_admin_panel_context_page',
194 'type' => MENU_LOCAL_TASK,
195 'weight' => -10,
196 ) + $admin_base;
197 }
198
946024d2 199 ctools_include('plugins', 'panels');
26e7b0ef
SB
200 $layouts = panels_get_layouts();
201 foreach ($layouts as $name => $data) {
202 if (!empty($data['hook menu'])) {
203 if (is_array($data['hook menu'])) {
204 $items += $data['hook menu'];
205 }
206 else if (function_exists($data['hook menu'])) {
207 $data['hook menu']($items, $data);
208 }
209 }
210 }
f2e5a0c5 211
946024d2 212
f2e5a0c5 213 return $items;
9f3609c9
EM
214}
215
216/**
26e7b0ef
SB
217 * Menu loader function to load a cache item for Panels AJAX.
218 *
219 * This load all of the includes needed to perform AJAX, and loads the
220 * cache object and makes sure it is valid.
221 */
222function panels_edit_cache_load($cache_key) {
946024d2
SB
223 ctools_include('display-edit', 'panels');
224 ctools_include('plugins', 'panels');
26e7b0ef
SB
225 ctools_include('ajax');
226 ctools_include('modal');
227 ctools_include('context');
228
229 return panels_edit_cache_get($cache_key);
230}
231
232/**
f2e5a0c5 233 * Implementation of hook_init()
9f3609c9 234 */
f2e5a0c5 235function panels_init() {
26e7b0ef
SB
236 // Safety: go away if CTools is not at an appropriate version.
237 if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
946024d2
SB
238 if (user_access('administer site configuration')) {
239 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');
240 }
26e7b0ef
SB
241 return;
242 }
243
946024d2
SB
244 ctools_add_css('panels', 'panels');
245 ctools_add_js('panels', 'panels');
9f3609c9
EM
246}
247
f2e5a0c5 248/**
2f354acb
SB
249 * Implementation of hook_permission().
250 *
251 * @todo Almost all of these need to be moved into pipelines.
f2e5a0c5 252 */
2f354acb 253function panels_permission() {
f2e5a0c5 254 return array(
2f354acb
SB
255 'use panels dashboard' => array(
256 'title' => t("Use Panels Dashboard"),
257 'description' => t("Allows a user to access the !link.", array('!link' => l('Panels Dashboard', 'admin/structure/panels'))),
258 ),
259 'view pane admin links' => array( // @todo
260 'title' => t("View administrative links on Panel panes"),
261 'description' => t(""),
262 ),
263 'administer pane access' => array( // @todo should we really have a global perm for this, or should it be moved into a pipeline question?
264 'title' => t("Configure access settings on Panel panes"),
265 '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."),
266 ),
267 'use panels in place editing' => array(
268 'title' => t("Use the Panels In-Place Editor"),
269 'description' => t("Allows a user to utilize Panels' In-Place Editor."),
270 ),
271 'administer advanced pane settings' => array(
272 'title' => t("Configure advanced settings on Panel panes"),
273 'description' => t(""),
274 ),
275 'administer panels layouts' => array(
276 'title' => t("Administer Panels layouts"),
277 'description' => t("Allows a user to administer exported Panels layout plugins & instances."),
278 ),
279 'use panels caching features' => array(
280 'title' => t("Configure caching settings on Panels"),
281 'description' => t("Allows a user to configure caching on Panels displays and panes."),
282 ),
f2e5a0c5
EM
283 );
284}
9f3609c9
EM
285
286/**
946024d2
SB
287 * Implementation of hook_flush_caches().
288 *
289 * We implement this so that we can be sure our legacy rendering state setting
290 * in $conf is updated whenever caches are cleared.
9f3609c9 291 */
2f354acb
SB
292//function panels_flush_caches() {
293// $legacy = panels_get_legacy_state();
294// $legacy->determineStatus();
295//}
9f3609c9 296
946024d2
SB
297// ---------------------------------------------------------------------------
298// CTools hook implementations
299//
300// These aren't core Drupal hooks but they are just as important.
301
9f3609c9 302/**
946024d2
SB
303 * Implementation of hook_ctools_plugin_directory() to let the system know
304 * we implement task and task_handler plugins.
9f3609c9 305 */
946024d2
SB
306function panels_ctools_plugin_directory($module, $plugin) {
307 // Safety: go away if CTools is not at an appropriate version.
308 if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
309 return;
310 }
2f354acb 311 if ($module == 'page_manager' || $module == 'panels' || $module == 'ctools' || $module == 'stylizer') {
946024d2
SB
312 return 'plugins/' . $plugin;
313 }
9f3609c9
EM
314}
315
f2e5a0c5 316/**
2f354acb
SB
317 * Implements hook_ctools_plugin_type().
318 *
319 * Register layout, style, cache, and display_renderer plugin types, declaring
320 * relevant plugin type information as necessary.
f2e5a0c5 321 */
2f354acb 322function panels_ctools_plugin_type() {
946024d2 323 return array(
2f354acb
SB
324 'layouts' => array(
325 'load themes' => TRUE, // Can define layouts in themes
326 'process' => 'panels_layout_process',
327 'child plugins' => TRUE,
328 ),
329 'styles' => array(
330 'load themes' => TRUE,
331 'process' => 'panels_plugin_styles_process',
332 'child plugins' => TRUE,
333 ),
334 'cache' => array(),
335 'display_renderers' => array(
336 'classes' => array('renderer'),
337 ),
946024d2
SB
338 );
339}
340
341/**
342 * Ensure a layout has a minimal set of data.
343 */
344function panels_layout_process(&$plugin) {
345 $plugin += array(
346 'category' => t('Miscellaneous'),
347 'description' => '',
348 );
349}
350
351/**
946024d2
SB
352 * Implementation of hook_ctools_plugin_api().
353 *
354 * Inform CTools about version information for various plugins implemented by
355 * Panels.
356 *
357 * @param string $owner
358 * The system name of the module owning the API about which information is
359 * being requested.
360 * @param string $api
361 * The name of the API about which information is being requested.
362 */
363function panels_ctools_plugin_api($owner, $api) {
364 if ($owner == 'panels' && $api == 'styles') {
365 // As of 6.x-3.6, Panels has a slightly new system for style plugins.
366 return array('version' => 2.0);
367 }
368
369 if ($owner == 'panels' && $api == 'pipelines') {
370 return array(
371 'version' => 1,
372 'path' => drupal_get_path('module', 'panels') . '/includes',
373 );
374 }
375}
376
377/**
378 * Implementation of hook_views_api().
379 */
380function panels_views_api() {
381 return array(
382 'api' => 2,
383 'path' => drupal_get_path('module', 'panels') . '/plugins/views',
384 );
385}
386
387/**
388 * Perform additional processing on a style plugin.
389 *
390 * Currently this is only being used to apply versioning information to style
391 * plugins in order to ensure the legacy renderer passes the right type of
392 * parameters to a style plugin in a hybrid environment of both new and old
393 * plugins.
394 *
395 * @see _ctools_process_data()
396 *
397 * @param array $plugin
398 * The style plugin that is being processed.
399 * @param array $info
400 * The style plugin type info array.
401 */
402function panels_plugin_styles_process(&$plugin, $info) {
403 $plugin += array(
404 'weight' => 0,
405 );
406
407 $compliant_modules = ctools_plugin_api_info('panels', 'styles', 2.0, 2.0);
408 $plugin['version'] = empty($compliant_modules[$plugin['module']]) ? 1.0 : $compliant_modules[$plugin['module']]['version'];
409}
410
411/**
412 * Declare what style types Panels uses.
413 */
414function panels_ctools_style_base_types() {
415 return array(
416 'region' => array(
417 'title' => t('Panel region'),
418 'preview' => 'panels_stylizer_region_preview',
2f354acb 419 'theme variables' => array('settings' => NULL, 'class' => NULL, 'content' => NULL),
946024d2
SB
420 ),
421 'pane' => array(
422 'title' => t('Panel pane'),
423 'preview' => 'panels_stylizer_pane_preview',
2f354acb 424 'theme variables' => array('settings' => NULL, 'content' => NULL, 'pane' => NULL, 'display' => NULL),
946024d2
SB
425 ),
426 );
427}
428
429function panels_stylizer_lipsum() {
430 return "
431 <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>
432
433 <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>
434 ";
435}
436
437/**
438 * Generate a preview given the current settings.
439 */
440function panels_stylizer_region_preview($plugin, $settings) {
441 ctools_stylizer_add_css($plugin, $settings);
2f354acb 442 return theme($plugin['theme'], array('settings' => $settings, 'class' => ctools_stylizer_get_css_class($plugin, $settings), 'content' => panels_stylizer_lipsum()));
946024d2
SB
443}
444
445/**
446 * Generate a preview given the current settings.
447 */
448function panels_stylizer_pane_preview($plugin, $settings) {
449 ctools_stylizer_add_css($plugin, $settings);
450 $pane = new stdClass();
451
452 $content = new stdClass;
453 $content->title = t('Lorem ipsum');
454 $content->content = panels_stylizer_lipsum();
455 $content->type = 'dummy';
456 $content->subtype = 'dummy';
457
458 $content->css_class = ctools_stylizer_get_css_class($plugin, $settings);
459
460 $display = new panels_display();
461
462 if (!empty($plugin['theme'])) {
2f354acb 463 return theme($plugin['theme'], array('settings' => $settings, 'content' => $content, 'pane' => $pane, 'display' => $display));
946024d2
SB
464 }
465 else {
2f354acb 466 return theme('panels_pane', array('output' => $content, 'pane' => $pane, 'display' => $display));
946024d2 467 }
f2e5a0c5 468}
9f3609c9 469
9f3609c9 470// ---------------------------------------------------------------------------
946024d2 471// Panels display editing
9f3609c9
EM
472
473/**
f2e5a0c5
EM
474 * @defgroup mainapi Functions comprising the main panels API
475 * @{
9f3609c9 476 */
9f3609c9 477
f2e5a0c5
EM
478/**
479 * Main API entry point to edit a panel display.
480 *
481 * Sample implementations utiltizing the the complex $destination behavior can be found
482 * in panels_page_edit_content() and, in a separate contrib module, OG Blueprints
483 * (http://drupal.org/project/og_blueprints), og_blueprints_blueprint_edit().
484 *
485 * @ingroup mainapi
486 *
487 * @param object $display instanceof panels_display \n
488 * A fully loaded panels $display object, as returned from panels_load_display().
489 * Merely passing a did is NOT sufficient. \n
490 * Note that 'fully loaded' means the $display must already be loaded with any contexts
491 * the caller wishes to have set for the display.
492 * @param mixed $destination \n
493 * The redirect destination that the user should be taken to on form submission or
494 * cancellation. With panels_edit, $destination has complex effects on the return
495 * values of panels_edit() once the form has been submitted. See the explanation of
496 * the return value below to understand the different types of values returned by panels_edit()
497 * at different stages of FAPI. Under most circumstances, simply passing in
498 * drupal_get_destination() is all that's necessary.
499 * @param array $content_types \n
500 * An associative array of allowed content types, typically as returned from
501 * panels_common_get_allowed_types(). Note that context partially governs available content types,
502 * so you will want to create any relevant contexts using panels_create_context() or
503 * panels_create_context_empty() to make sure all the appropriate content types are available.
504 *
505 * @return
506 * Because the functions called by panels_edit() invoke the form API, this function
507 * returns different values depending on the stage of form submission we're at. In Drupal 5,
508 * the phase of form submission is indicated by the contents of $_POST['op']. Here's what you'll
509 * get at different stages:
510 * -# If !$_POST['op']: then we're on on the initial passthrough and the form is being
511 * rendered, so it's the $form itself that's being returned. Because negative margins,
512 * a common CSS technique, bork the display editor's ajax drag-and-drop, it's important
513 * that the $output be printed, not returned. Use this syntax in the caller function: \n
514 * print theme('page', panels_edit($display, $destination, $content_types), FALSE); \n
515 * -# If $_POST['op'] == t('Cancel'): form submission has been cancelled. If empty($destination) == FALSE,
516 * then there is no return value and the panels API takes care of redirecting to $destination.
517 * If empty($destination) == TRUE, then there's still no return value, but the caller function
518 * has to take care of form redirection.
519 * -# If $_POST['op'] == ('Save'): the form has been submitted successfully and has run through
520 * panels_edit_display_submit(). $output depends on the value of $destination:
521 * - If empty($destination) == TRUE: $output contains the modified $display
522 * object, and no redirection will occur. This option is useful if the caller
523 * needs to perform additional operations on or with the modified $display before
524 * the page request is complete. Using hook_form_alter() to add an additional submit
525 * handler is typically the preferred method for something like this, but there
526 * are certain use cases where that is infeasible and $destination = NULL should
527 * be used instead. If this method is employed, the caller will need to handle form
528 * redirection. Note that having $_REQUEST['destination'] set, whether via
529 * drupal_get_destination() or some other method, will NOT interfere with this
530 * functionality; consequently, you can use drupal_get_destination() to safely store
531 * your desired redirect in the caller function, then simply use drupal_goto() once
532 * panels_edit() has done its business.
533 * - If empty($destination) == FALSE: the form will redirect to the URL string
534 * given in $destination and NO value will be returned.
9f3609c9 535 */
26e7b0ef 536function panels_edit($display, $destination = NULL, $content_types = NULL, $title = FALSE) {
946024d2 537 ctools_include('display-edit', 'panels');
26e7b0ef 538 ctools_include('ajax');
946024d2 539 ctools_include('plugins', 'panels');
26e7b0ef 540 return _panels_edit($display, $destination, $content_types, $title);
f2e5a0c5 541}
9f3609c9 542
f2e5a0c5
EM
543/**
544 * API entry point for selecting a layout for a given display.
545 *
546 * Layout selection is nothing more than a list of radio items encompassing the available
547 * layouts for this display, as defined by .inc files in the panels/layouts subdirectory.
548 * The only real complexity occurs when a user attempts to change the layout of a display
549 * that has some content in it.
550 *
551 * @param object $display instanceof panels_display \n
552 * A fully loaded panels $display object, as returned from panels_load_display().
553 * Merely passing a did is NOT sufficient.
554 * @param string $finish
555 * A string that will be used for the text of the form submission button. If no value is provided,
556 * then the form submission button will default to t('Save').
557 * @param mixed $destination
558 * Basic usage is a string containing the URL that the form should redirect to upon submission.
559 * For a discussion of advanced usages, see panels_edit().
560 * @param mixed $allowed_layouts
561 * Allowed layouts has three different behaviors that depend on which of three value types
562 * are passed in by the caller:
563 * #- if $allowed_layouts instanceof panels_allowed_layouts (includes subclasses): the most
564 * complex use of the API. The caller is passing in a loaded panels_allowed_layouts object
565 * that the client module previously created and stored somewhere using a custom storage
566 * mechanism.
567 * #- if is_string($allowed_layouts): the string will be used in a call to variable_get() which
568 * will call the $allowed_layouts . '_allowed_layouts' var. If the data was stored properly
569 * in the system var, the $allowed_layouts object will be unserialized and recreated.
570 * @see panels_common_set_allowed_layouts()
571 * #- if is_null($allowed_layouts): the default behavior, which also provides backwards
572 * compatibility for implementations of the Panels2 API written before beta4. In this case,
573 * a dummy panels_allowed_layouts object is created which does not restrict any layouts.
574 * Subsequent behavior is indistinguishable from pre-beta4 behavior.
575 *
576 * @return
577 * Can return nothing, or a modified $display object, or a redirection string; return values for the
578 * panels_edit* family of functions are quite complex. See panels_edit() for detailed discussion.
579 * @see panels_edit()
580 */
581function panels_edit_layout($display, $finish, $destination = NULL, $allowed_layouts = NULL) {
946024d2
SB
582 ctools_include('display-layout', 'panels');
583 ctools_include('plugins', 'panels');
f2e5a0c5 584 return _panels_edit_layout($display, $finish, $destination, $allowed_layouts);
9f3609c9
EM
585}
586
f2e5a0c5 587// ---------------------------------------------------------------------------
946024d2 588// Panels database functions
f2e5a0c5 589
9f3609c9 590/**
f2e5a0c5
EM
591 * Forms the basis of a panel display
592 *
9f3609c9 593 */
f2e5a0c5
EM
594class panels_display {
595 var $args = array();
596 var $content = array();
597 var $panels = array();
598 var $incoming_content = NULL;
599 var $css_id = NULL;
600 var $context = array();
26e7b0ef 601 var $did = 'new';
2f354acb 602 var $renderer = 'standard';
946024d2
SB
603
604 function add_pane(&$pane, $location = NULL) {
605 // If no location specified, use what's set in the pane.
606 if (empty($location)) {
607 $location = $pane->panel;
9f3609c9 608 }
f2e5a0c5 609 else {
946024d2 610 $pane->panel = $location;
f2e5a0c5 611 }
946024d2
SB
612
613 // Get a temporary pid for this pane.
614 $pane->pid = "new-" . $this->next_new_pid();
615
616 // Add the pane to the approprate spots.
617 $this->content[$pane->pid] = &$pane;
618 $this->panels[$location][] = $pane->pid;
9f3609c9
EM
619 }
620
f2e5a0c5
EM
621 function duplicate_pane($pid, $location = FALSE) {
622 $pane = $this->clone_pane($pid);
623 $this->add_pane($pane, $location);
9f3609c9
EM
624 }
625
f2e5a0c5
EM
626 function clone_pane($pid) {
627 $pane = drupal_clone($this->content[$pid]);
f2e5a0c5
EM
628 return $pane;
629 }
9f3609c9 630
f2e5a0c5 631 function next_new_pid() {
946024d2
SB
632 // We don't use static vars to record the next new pid because
633 // temporary pids can last for years in exports and in caching
634 // during editing.
f2e5a0c5
EM
635 $id = array(0);
636 foreach (array_keys($this->content) as $pid) {
637 if (!is_numeric($pid)) {
638 $id[] = substr($pid, 4);
639 }
640 }
26e7b0ef 641 $next_id = max($id);
f2e5a0c5 642 return ++$next_id;
9f3609c9 643 }
f2e5a0c5 644
946024d2
SB
645 /**
646 * Get the title from a display.
647 *
648 * The display must have already been rendered, or the setting to set the
649 * display's title from a pane's title will not have worked.
650 *
651 * @return
652 * The title to use. If NULL, this means to let any default title that may be in use
653 * pass through. i.e, do not actually set the title.
654 */
655 function get_title() {
656 switch ($this->hide_title) {
657 case PANELS_TITLE_NONE:
658 return '';
659
660 case PANELS_TITLE_PANE:
661 return isset($this->stored_pane_title) ? $this->stored_pane_title : '';
662
663 case PANELS_TITLE_FIXED:
664 case FALSE; // For old exported panels that are not in the database.
665 if (!empty($this->title)) {
666 return filter_xss_admin(ctools_context_keyword_substitute($this->title, array(), $this->context));
667 }
668 return NULL;
669 }
f2e5a0c5 670 }
9f3609c9 671
946024d2
SB
672 /**
673 * Render this panels display.
674 *
675 * After checking to ensure the designated layout plugin is valid, a
676 * display renderer object is spawned and runs its rendering logic.
677 *
678 * @param mixed $renderer
679 * An instantiated display renderer object, or the name of a display
680 * renderer plugin+class to be fetched. Defaults to NULL. When NULL, the
681 * predesignated display renderer will be used.
682 */
683 function render($renderer = NULL) {
684 $layout = panels_get_layout($this->layout);
685 if (!$layout) {
686 return NULL;
687 }
9f3609c9 688
946024d2
SB
689 // If we were not given a renderer object, load it.
690 if (!is_object($renderer)) {
691 // If the renderer was not specified, default to $this->renderer
692 // which is either standard or was already set for us.
693 $renderer = panels_get_renderer_handler(!empty($renderer) ? $renderer : $this->renderer, $this);
694 if (!$renderer) {
695 return NULL;
696 }
697 }
698
699 $output = '';
700 // Let modules act just prior to render.
701 foreach (module_implements('panels_pre_render') as $module) {
702 $function = $module . '_panels_pre_render';
703 $output .= $function($this, $renderer);
704 }
705
706 $output .= $renderer->render();
9f3609c9 707
946024d2
SB
708 // Let modules act just after render.
709 foreach (module_implements('panels_post_render') as $module) {
710 $function = $module . '_panels_post_render';
711 $output .= $function($this, $renderer);
712 }
713 return $output;
9f3609c9 714 }
f2e5a0c5 715}
9f3609c9 716
f2e5a0c5 717/**
946024d2
SB
718 * }@ End of 'defgroup mainapi', although other functions are specifically added later
719 */
720
721/**
f2e5a0c5
EM
722 * Creates a new display, setting the ID to our magic new id.
723 */
724function panels_new_display() {
26e7b0ef
SB
725 ctools_include('export');
726 $display = ctools_export_new_object('panels_display', FALSE);
f2e5a0c5
EM
727 $display->did = 'new';
728 return $display;
729}
9f3609c9 730
26e7b0ef
SB
731/**
732 * Create a new pane.
733 *
734 * @todo -- use schema API for some of this?
735 */
946024d2 736function panels_new_pane($type, $subtype, $set_defaults = FALSE) {
26e7b0ef
SB
737 ctools_include('export');
738 $pane = ctools_export_new_object('panels_pane', FALSE);
f2e5a0c5
EM
739 $pane->pid = 'new';
740 $pane->type = $type;
741 $pane->subtype = $subtype;
946024d2
SB
742 if ($set_defaults) {
743 $content_type = ctools_get_content_type($type);
744 $content_subtype = ctools_content_get_subtype($content_type, $subtype);
745 $pane->configuration = ctools_content_get_defaults($content_type, $content_subtype);
746 }
747
f2e5a0c5
EM
748 return $pane;
749}
9f3609c9 750
f2e5a0c5
EM
751/**
752 * Load and fill the requested $display object(s).
753 *
754 * Helper function primarily for for panels_load_display().
755 *
756 * @param array $dids
757 * An indexed array of dids to be loaded from the database.
758 *
759 * @return $displays
760 * An array of displays, keyed by their display dids.
26e7b0ef
SB
761 *
762 * @todo schema API can drasticly simplify this code.
f2e5a0c5
EM
763 */
764function panels_load_displays($dids) {
765 $displays = array();
766 if (empty($dids) || !is_array($dids)) {
767 return $displays;
768 }
9f3609c9 769
2f354acb 770 $result = db_query("SELECT * FROM {panels_display} WHERE did IN (:dids)", array(':dids' => $dids));
9f3609c9 771
26e7b0ef 772 ctools_include('export');
2f354acb 773 foreach ($result as $obj) {
26e7b0ef
SB
774 $displays[$obj->did] = ctools_export_unpack_object('panels_display', $obj);
775 // Modify the hide_title field to go from a bool to an int if necessary.
f2e5a0c5
EM
776 }
777
2f354acb
SB
778 $result = db_query("SELECT * FROM {panels_pane} WHERE did IN (:dids) ORDER BY did, panel, position", array(':dids' => $dids));
779 foreach ($result as $obj) {
26e7b0ef 780 $pane = ctools_export_unpack_object('panels_pane', $obj);
f2e5a0c5
EM
781
782 $displays[$pane->did]->panels[$pane->panel][] = $pane->pid;
783 $displays[$pane->did]->content[$pane->pid] = $pane;
784 }
26e7b0ef 785
f2e5a0c5 786 return $displays;
9f3609c9
EM
787}
788
789/**
f2e5a0c5
EM
790 * Load a single display.
791 *
792 * @ingroup mainapi
793 *
794 * @param int $did
795 * The display id (did) of the display to be loaded.
796 *
797 * @return object $display instanceof panels_display \n
798 * Returns a partially-loaded panels_display object. $display objects returned from
799 * from this function have only the following data:
800 * - $display->did (the display id)
801 * - $display->name (the 'name' of the display, where applicable - it often isn't)
802 * - $display->layout (a string with the system name of the display's layout)
803 * - $display->panel_settings (custom layout style settings contained in an associative array; NULL if none)
804 * - $display->layout_settings (panel size and configuration settings for Flexible layouts; NULL if none)
805 * - $display->css_id (the special css_id that has been assigned to this display, if any; NULL if none)
806 * - $display->content (an array of pane objects, keyed by pane id (pid))
807 * - $display->panels (an associative array of panel regions, each an indexed array of pids in the order they appear in that region)
808 * - $display->cache (any relevant data from panels_simple_cache)
809 * - $display->args
810 * - $display->incoming_content
811 *
812 * While all of these members are defined, $display->context is NEVER defined in the returned $display;
26e7b0ef 813 * it must be set using one of the ctools_context_create() functions.
9f3609c9 814 */
f2e5a0c5
EM
815function panels_load_display($did) {
816 $displays = panels_load_displays(array($did));
817 if (!empty($displays)) {
818 return array_shift($displays);
819 }
820}
9f3609c9 821
f2e5a0c5
EM
822/**
823 * Save a display object.
824 *
825 * @ingroup mainapi
826 *
827 * Note a new $display only receives a real did once it is run through this function.
828 * Until then, it uses a string placeholder, 'new', in place of a real did. The same
829 * applies to all new panes (whether on a new $display or not); in addition,
830 * panes have sequential numbers appended, of the form 'new-1', 'new-2', etc.
831 *
832 * @param object $display instanceof panels_display \n
833 * The display object to be saved. Passed by reference so the caller need not use
834 * the return value for any reason except convenience.
835 *
836 * @return object $display instanceof panels_display \n
837 */
838function panels_save_display(&$display) {
26e7b0ef
SB
839 $update = (isset($display->did) && is_numeric($display->did)) ? array('did') : array();
840 drupal_write_record('panels_display', $display, $update);
841
842 $pids = array();
843 if ($update) {
f2e5a0c5
EM
844 // Get a list of all panes currently in the database for this display so we can know if there
845 // are panes that need to be deleted. (i.e, aren't currently in our list of panes).
2f354acb
SB
846 $result = db_query("SELECT pid FROM {panels_pane} WHERE did = :did", array(':did' => $display->did));
847 foreach ($result as $pane) {
f2e5a0c5 848 $pids[$pane->pid] = $pane->pid;
9f3609c9 849 }
f2e5a0c5 850 }
9f3609c9 851
f2e5a0c5 852 // update all the panes
946024d2 853 ctools_include('plugins', 'panels');
26e7b0ef 854 ctools_include('content');
9f3609c9 855
26e7b0ef 856 foreach ($display->panels as $id => $panes) {
f2e5a0c5
EM
857 $position = 0;
858 $new_panes = array();
859 foreach ((array) $panes as $pid) {
26e7b0ef
SB
860 if (!isset($display->content[$pid])) {
861 continue;
862 }
f2e5a0c5 863 $pane = $display->content[$pid];
26e7b0ef 864 $type = ctools_get_content_type($pane->type);
f2e5a0c5 865
26e7b0ef
SB
866 $pane->position = $position++;
867 $pane->did = $display->did;
868
869 $old_pid = $pane->pid;
870 drupal_write_record('panels_pane', $pane, is_numeric($pid) ? array('pid') : array());
871
872 if ($pane->pid != $old_pid) {
873 // and put it back so our pids and positions can be used
874 unset($display->content[$id]);
875 $display->content[$pane->pid] = $pane;
876
877 // If the title pane was one of our panes that just got its ID changed,
878 // we need to change it in the database, too.
879 if (isset($display->title_pane) && $display->title_pane == $old_pid) {
880 $display->title_pane = $pane->pid;
881 // Do a simple update query to write it so we don't have to rewrite
882 // the whole record. We can't just save writing the whole record here
883 // because it was needed to get the did. Chicken, egg, more chicken.
2f354acb
SB
884 db_update('panels_display')
885 ->fields(array(
886 'title_pane' => $pane->pid
887 ))
888 ->condition('did', $display->did)
889 ->execute();
26e7b0ef 890 }
9f3609c9 891 }
9f3609c9 892
26e7b0ef 893 // re-add this to the list of content for this panel.
f2e5a0c5 894 $new_panes[] = $pane->pid;
26e7b0ef
SB
895
896 // Remove this from the list of panes scheduled for deletion.
f2e5a0c5
EM
897 if (isset($pids[$pane->pid])) {
898 unset($pids[$pane->pid]);
9f3609c9
EM
899 }
900 }
9f3609c9 901
f2e5a0c5
EM
902 $display->panels[$id] = $new_panes;
903 }
f6c1a273 904 if (!empty($pids)) {
2f354acb 905 db_delete('panels_pane')->condition('pid', $pids)->execute();
9f3609c9 906 }
9f3609c9 907
f2e5a0c5
EM
908 // Clear any cached content for this display.
909 panels_clear_cached_content($display);
9f3609c9 910
946024d2
SB
911 // Allow other modules to take action when a display is saved.
912 module_invoke_all('panels_display_save', $display);
913
914 // Log the change to watchdog, using the same style as node.module
915 $watchdog_args = array('%did' => $display->did);
916 if (!empty($display->title)) {
917 $watchdog_args['%title'] = $display->title;
918 watchdog('content', 'Panels: saved display "%title" with display id %did', $watchdog_args, WATCHDOG_NOTICE);
919 }
920 else {
921 watchdog('content', 'Panels: saved display with id %did', $watchdog_args, WATCHDOG_NOTICE);
922 }
923
f2e5a0c5
EM
924 // to be nice, even tho we have a reference.
925 return $display;
9f3609c9
EM
926}
927
928/**
f2e5a0c5 929 * Delete a display.
9f3609c9 930 */
f2e5a0c5
EM
931function panels_delete_display($display) {
932 if (is_object($display)) {
933 $did = $display->did;
934 }
935 else {
936 $did = $display;
937 }
2f354acb
SB
938 db_delete('panels_display')->condition('did', $did)->execute();
939 db_delete('panels_pane')->condition('did', $did)->execute();
9f3609c9
EM
940}
941
942/**
f2e5a0c5
EM
943 * Exports the provided display into portable code.
944 *
945 * This function is primarily intended as a mechanism for cloning displays.
946 * It generates an exact replica (in code) of the provided $display, with
947 * the exception that it replaces all ids (dids and pids) with 'new-*' values.
948 * Only once panels_save_display() is called on the code version of $display will
949 * the exported display written to the database and permanently saved.
950 *
951 * @see panels_page_export() or _panels_page_fetch_display() for sample implementations.
952 *
953 * @ingroup mainapi
954 *
955 * @param object $display instanceof panels_display \n
956 * This export function does no loading of additional data about the provided
957 * display. Consequently, the caller should make sure that all the desired data
958 * has been loaded into the $display before calling this function.
959 * @param string $prefix
960 * A string prefix that is prepended to each line of exported code. This is primarily
961 * used for prepending a double space when exporting so that the code indents and lines up nicely.
962 *
963 * @return string $output
964 * The passed-in $display expressed as code, ready to be imported. Import by running
965 * eval($output) in the caller function; doing so will create a new $display variable
966 * with all the exported values. Note that if you have already defined a $display variable in
967 * the same scope as where you eval(), your existing $display variable WILL be overwritten.
9f3609c9 968 */
f2e5a0c5 969function panels_export_display($display, $prefix = '') {
26e7b0ef
SB
970 ctools_include('export');
971 $output = ctools_export_object('panels_display', $display, $prefix);
9f3609c9 972
26e7b0ef 973 // Initialize empty properties.
f2e5a0c5
EM
974 $output .= $prefix . '$display->content = array()' . ";\n";
975 $output .= $prefix . '$display->panels = array()' . ";\n";
976 $panels = array();
977
26e7b0ef 978 $title_pid = 0;
f2e5a0c5
EM
979 if (!empty($display->content)) {
980 $pid_counter = 0;
981 $region_counters = array();
982 foreach ($display->content as $pane) {
26e7b0ef
SB
983 $pid = 'new-' . ++$pid_counter;
984 if ($pane->pid == $display->title_pane) {
985 $title_pid = $pid;
986 }
987 $pane->pid = $pid;
988 $output .= ctools_export_object('panels_pane', $pane, $prefix . ' ');
f2e5a0c5
EM
989 $output .= "$prefix " . '$display->content[\'' . $pane->pid . '\'] = $pane' . ";\n";
990 if (!isset($region_counters[$pane->panel])) {
991 $region_counters[$pane->panel] = 0;
9f3609c9 992 }
f2e5a0c5 993 $output .= "$prefix " . '$display->panels[\'' . $pane->panel . '\'][' . $region_counters[$pane->panel]++ .'] = \'' . $pane->pid . "';\n";
9f3609c9
EM
994 }
995 }
26e7b0ef
SB
996 $output .= $prefix . '$display->hide_title = ';
997 switch ($display->hide_title) {
998 case PANELS_TITLE_FIXED:
999 $output .= 'PANELS_TITLE_FIXED';
1000 break;
1001 case PANELS_TITLE_NONE:
1002 $output .= 'PANELS_TITLE_NONE';
1003 break;
1004 case PANELS_TITLE_PANE:
1005 $output .= 'PANELS_TITLE_PANE';
1006 break;
9f3609c9 1007 }
26e7b0ef 1008 $output .= ";\n";
9f3609c9 1009
26e7b0ef 1010 $output .= $prefix . '$display->title_pane =' . " '$title_pid';\n";
9f3609c9
EM
1011 return $output;
1012}
1013
f2e5a0c5
EM
1014/**
1015 * Render a display by loading the content into an appropriate
1016 * array and then passing through to panels_render_layout.
1017 *
1018 * if $incoming_content is NULL, default content will be applied. Use
1019 * an empty string to indicate no content.
f2e5a0c5
EM
1020 * @ingroup hook_invocations
1021 */
946024d2 1022function panels_render_display(&$display, $renderer = NULL) {
946024d2 1023 ctools_include('plugins', 'panels');
26e7b0ef
SB
1024 ctools_include('context');
1025
1026 if (!empty($display->context)) {
1027 if ($form_context = ctools_context_get_form($display->context)) {
1028 $form_context->form['#theme'] = 'panels_render_display_form';
1029 $form_context->form['#display'] = &$display;
1030 $form_context->form['#form_context_id'] = $form_context->id;
1031 return drupal_render_form($form_context->form_id, $form_context->form);
1032 }
1033 }
946024d2 1034 return $display->render($renderer);
9f3609c9
EM
1035}
1036
1037/**
26e7b0ef
SB
1038 * Theme function to render our panel as a form.
1039 *
1040 * When rendering a display as a form, the entire display needs to be
1041 * inside the <form> tag so that the form can be spread across the
1042 * panes. This sets up the form system to be the main caller and we
1043 * then operate as a theme function of the form.
1044 */
2f354acb
SB
1045function theme_panels_render_display_form($vars) {
1046 // @todo this is probably broken in D7
1047 $vars['form']['#children'] = $vars['form']['#display']->render();
1048 render($vars['form']);
1049 return theme('form', $vars);
26e7b0ef
SB
1050}
1051
f2e5a0c5
EM
1052// @layout
1053function panels_print_layout_icon($id, $layout, $title = NULL) {
946024d2 1054 ctools_add_css('panels_admin', 'panels');
fc8f8fa8 1055 $file = $layout['path'] . '/' . $layout['icon'];
2f354acb 1056 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));
9f3609c9
EM
1057}
1058
f2e5a0c5
EM
1059/**
1060 * Theme the layout icon image
1061 * @layout
1062 * @todo move to theme.inc
1063 */
2f354acb
SB
1064function theme_panels_layout_icon($vars) {
1065 $id = $vars['id'];
1066 $image = $vars['image'];
1067 $title = $vars['title'];
1068
14996ee1 1069 $output = '<div class="layout-icon">';
f2e5a0c5
EM
1070 $output .= $image;
1071 if ($title) {
1072 $output .= '<div class="caption">' . $title . '</div>';
9f3609c9 1073 }
f2e5a0c5
EM
1074 $output .= '</div>';
1075 return $output;
9f3609c9
EM
1076}
1077
1078/**
f2e5a0c5
EM
1079 * Theme the layout link image
1080 * @layout
2f354acb
SB
1081 *
1082 * @todo Why isn't this a template at this point?
1083 * @todo Why does this take 4 arguments but only makes use of two?
9f3609c9 1084 */
2f354acb
SB
1085function theme_panels_layout_link($vars) {
1086 $title = $vars['title'];
1087 $image = $vars['image'];
1088
14996ee1 1089 $output = '<div class="layout-link">';
2f354acb
SB
1090 $output .= $vars['image'];
1091 $output .= '<div>' . $vars['title'] . '</div>';
f2e5a0c5
EM
1092 $output .= '</div>';
1093 return $output;
9f3609c9 1094}
aa3e4818
EM
1095
1096/**
1097 * Print the layout link. Sends out to a theme function.
1098 * @layout
1099 */
26e7b0ef
SB
1100function panels_print_layout_link($id, $layout, $link, $options = array()) {
1101 if (isset($options['query']['q'])) {
1102 unset($options['query']['q']);
1103 }
1104
946024d2 1105 ctools_add_css('panels_admin', 'panels');
aa3e4818 1106 $file = $layout['path'] . '/' . $layout['icon'];
2f354acb 1107 $image = l(theme('image', array('path' => $file)), $link, array('html' => true) + $options);
26e7b0ef 1108 $title = l($layout['title'], $link, $options);
2f354acb 1109 return theme('panels_layout_link', array('title' => $title, 'image' => $image));
aa3e4818
EM
1110}
1111
26e7b0ef
SB
1112
1113/**
946024d2
SB
1114 * Gateway to the PanelsLegacyState class/object, which does all legacy state
1115 * checks and provides information about the cause of legacy states as needed.
1116 *
1117 * @return PanelsLegacyState $legacy
26e7b0ef 1118 */
946024d2
SB
1119function panels_get_legacy_state() {
1120 static $legacy = NULL;
1121 if (!isset($legacy)) {
1122 ctools_include('legacy', 'panels');
1123 $legacy = new PanelsLegacyState();
1124 }
1125 return $legacy;
26e7b0ef
SB
1126}
1127
1128/**
1129 * Get the display that is currently being rendered as a page.
1130 *
1131 * Unlike in previous versions of this, this only returns the display,
1132 * not the page itself, because there are a number of different ways
1133 * to get to this point. It is hoped that the page data isn't needed
1134 * at this point. If it turns out there is, we will do something else to
1135 * get that functionality.
1136 */
1137function panels_get_current_page_display($change = NULL) {
1138 static $display = NULL;
1139 if ($change) {
1140 $display = $change;
1141 }
1142
1143 return $display;
1144}
1145
1146/**
946024d2
SB
1147 * Clean up the panel pane variables for the template.
1148 */
2f354acb
SB
1149function template_preprocess_panels_pane(&$vars) {
1150 $content = &$vars['content'];
1151
1152 $vars['contextual_links'] = array();
1153 $vars['classes_array'] = array();
1154 $vars['admin_links'] = '';
1155
1156 if (user_access('access contextual links')) {
1157 $links = array();
1158 // These are specified by the content.
1159 if (!empty($content->admin_links)) {
1160 $links += $content->admin_links;
1161 }
1162
1163 // Take any that may have been in the render array we were given and
1164 // move them up so they appear outside the pane properly.
1165 if (is_array($content->content) && isset($content->content['#contextual_links'])) {
1166 $element = array(
1167 '#type' => 'contextual_links',
1168 '#contextual_links' => $content->content['#contextual_links'],
1169 );
1170 unset($content->content['#contextual_links']);
1171
1172 $element = contextual_pre_render_links($element);
1173 $links += $element['#links'];
1174 }
1175
1176 if ($links) {
1177 $build = array(
1178 '#prefix' => '<div class="contextual-links-wrapper">',
1179 '#suffix' => '</div>',
1180 '#theme' => 'links__contextual',
1181 '#links' => $links,
1182 '#attributes' => array('class' => array('contextual-links')),
1183 '#attached' => array(
1184 'library' => array(array('contextual', 'contextual-links')),
1185 ),
1186 );
1187 $vars['classes_array'][] = 'contextual-links-region';
1188 $vars['admin_links'] = drupal_render($build);
1189 }
1190 }
1191
946024d2
SB
1192 // basic classes
1193 $vars['classes'] = 'panel-pane';
1194 $vars['id'] = '';
1195
1196 // Add some usable classes based on type/subtype
1197 ctools_include('cleanstring');
1198 $type_class = $content->type ? 'pane-'. ctools_cleanstring($content->type, array('lower case' => TRUE)) : '';
1199 $subtype_class = $content->subtype ? 'pane-'. ctools_cleanstring($content->subtype, array('lower case' => TRUE)) : '';
1200
1201 // Sometimes type and subtype are the same. Avoid redudant classes.
1202 if ($type_class != $subtype_class) {
1203 $vars['classes'] .= " $type_class $subtype_class";
1204 }
1205 else {
1206 $vars['classes'] .= " $type_class";
1207 }
1208
1209 // Add id and custom class if sent in.
1210 if (!empty($content->content)) {
1211 if (!empty($content->css_id)) {
1212 $vars['id'] = ' id="' . $content->css_id . '"';
1213 }
1214 if (!empty($content->css_class)) {
1215 $vars['classes'] .= ' ' . $content->css_class;
1216 }
1217 }
1218
946024d2
SB
1219 $vars['title'] = !empty($content->title) ? $content->title : '';
1220
1221 $vars['feeds'] = !empty($content->feeds) ? implode(' ', $content->feeds) : '';
946024d2 1222
2f354acb 1223 $vars['links'] = !empty($content->links) ? theme('links', array('links' => $content->links)) : '';
946024d2
SB
1224 $vars['more'] = '';
1225 if (!empty($content->more)) {
1226 if (empty($content->more['title'])) {
1227 $content->more['title'] = t('more');
1228 }
1229 $vars['more'] = l($content->more['title'], $content->more['href'], $content->more);
1230 }
2f354acb
SB
1231
1232 $vars['content'] = !empty($content->content) ? $content->content : '';
1233
946024d2
SB
1234}
1235
1236/**
1237 * Route Panels' AJAX calls to the correct object.
1238 *
1239 * Panels' AJAX is controlled mostly by renderer objects. This menu callback
1240 * accepts the incoming request, figures out which object should handle the
1241 * request, and attempts to route it. If no object can be found, the default
1242 * Panels editor object is used.
1243 *
1244 * Calls are routed via the ajax_* method space. For example, if visiting
1245 * panels/ajax/add-pane then $renderer::ajax_add_pane() will be called.
1246 * This means commands can be added without having to create new callbacks.
1247 *
1248 * The first argument *must always* be the cache key so that a cache object
1249 * can be passed through. Other arguments will be passed through untouched
1250 * so that the method can do whatever it needs to do.
1251 */
1252function panels_ajax_router() {
1253 $args = func_get_args();
1254 if (count($args) < 3) {
1255 return MENU_NOT_FOUND;
1256 }
1257
1258 ctools_include('display-edit', 'panels');
1259 ctools_include('plugins', 'panels');
1260 ctools_include('ajax');
1261 ctools_include('modal');
1262 ctools_include('context');
1263 ctools_include('content');
1264
1265 $plugin_name = array_shift($args);
1266 $method = array_shift($args);
1267 $cache_key = array_shift($args);
1268
1269 $plugin = panels_get_display_renderer($plugin_name);
1270 if (!$plugin) {
1271 // This is the default renderer for handling AJAX commands.
1272 $plugin = panels_get_display_renderer('editor');
1273 }
1274
1275 $cache = panels_edit_cache_get($cache_key);
1276 if (empty($cache)) {
1277 return MENU_ACCESS_DENIED;
1278 }
1279
1280 $renderer = panels_get_renderer_handler($plugin, $cache->display);
1281 if (!$renderer) {
1282 return MENU_ACCESS_DENIED;
1283 }
1284
1285 $method = 'ajax_' . str_replace('-', '_', $method);
1286 if (!method_exists($renderer, $method)) {
1287 return MENU_NOT_FOUND;
1288 }
1289
1290 $renderer->cache = &$cache;
1291 ctools_include('cleanstring');
1292 $renderer->clean_key = ctools_cleanstring($cache_key);
1293
1294 $output = call_user_func_array(array($renderer, $method), $args);
2f354acb 1295
946024d2 1296 if (empty($output) && !empty($renderer->commands)) {
2f354acb
SB
1297 print ajax_render($renderer->commands);
1298 ajax_footer();
946024d2
SB
1299 }
1300 return $output;
1301}
1302
1303// --------------------------------------------------------------------------
1304// Panels caching functions and callbacks
1305//
1306// When editing displays and the like, Panels has a caching system that relies
1307// on a callback to determine where to get the actual cache.
1308
1309// @todo This system needs to be better documented so that it can be
1310// better used.
1311
1312/**
1313 * Get an object from cache.
1314 */
1315function panels_cache_get($obj, $did, $skip_cache = FALSE) {
1316 ctools_include('object-cache');
1317 // we often store contexts in cache, so let's just make sure we can load
1318 // them.
1319 ctools_include('context');
1320 return ctools_object_cache_get($obj, 'panels_display:' . $did, $skip_cache);
1321}
1322
1323/**
1324 * Save the edited object into the cache.
1325 */
1326function panels_cache_set($obj, $did, $cache) {
1327 ctools_include('object-cache');
1328 return ctools_object_cache_set($obj, 'panels_display:' . $did, $cache);
1329}
1330
1331/**
1332 * Clear a object from the cache; used if the editing is aborted.
1333 */
1334function panels_cache_clear($obj, $did) {
1335 ctools_include('object-cache');
1336 return ctools_object_cache_clear($obj, 'panels_display:' . $did);
1337}
1338
1339/**
1340 * Create the default cache for editing panel displays.
1341 *
1342 * If an application is using the Panels display editor without having
1343 * specified a cache key, this method can be used to create the default
1344 * cache.
1345 */
1346function panels_edit_cache_get_default(&$display, $content_types = NULL, $title = FALSE) {
1347 if (empty($content_types)) {
1348 $content_types = ctools_content_get_available_types();
1349 }
1350
1351 $display->cache_key = $display->did;
1352 panels_cache_clear('display', $display->did);
1353
1354 $cache = new stdClass();
1355 $cache->display = &$display;
1356 $cache->content_types = $content_types;
1357 $cache->display_title = $title;
1358
1359 panels_edit_cache_set($cache);
1360 return $cache;
1361}
1362
1363/**
1364 * Method to allow modules to provide their own caching mechanism for the
1365 * display editor.
1366 */
1367function panels_edit_cache_get($cache_key) {
1368 if (strpos($cache_key, ':') !== FALSE) {
1369 list($module, $argument) = explode(':', $cache_key, 2);
1370 return module_invoke($module, 'panels_cache_get', $argument);
1371 }
1372
1373 // Fall back to our normal method:
1374 return panels_cache_get('display', $cache_key);
1375}
1376
1377/**
1378 * Method to allow modules to provide their own caching mechanism for the
1379 * display editor.
1380 */
1381function panels_edit_cache_set($cache) {
1382 $cache_key = $cache->display->cache_key;
1383 if (strpos($cache_key, ':') !== FALSE) {
1384 list($module, $argument) = explode(':', $cache_key, 2);
1385 return module_invoke($module, 'panels_cache_set', $argument, $cache);
1386 }
1387
1388 // Fall back to our normal method:
1389 return panels_cache_set('display', $cache_key, $cache);
1390}
1391
1392/**
1393 * Method to allow modules to provide their own mechanism to write the
1394 * cache used in the display editor.
1395 */
1396function panels_edit_cache_save($cache) {
1397 $cache_key = $cache->display->cache_key;
1398 if (strpos($cache_key, ':') !== FALSE) {
1399 list($module, $argument) = explode(':', $cache_key, 2);
1400 if (function_exists($module . '_panels_cache_save')) {
1401 return module_invoke($module, 'panels_cache_save', $argument, $cache);
1402 }
1403 }
1404
1405 // Fall back to our normal method:
1406 return panels_save_display($cache->display);
1407}
1408
1409/**
1410 * Method to allow modules to provide their own mechanism to clear the
1411 * cache used in the display editor.
1412 */
1413function panels_edit_cache_clear($cache) {
1414 $cache_key = $cache->display->cache_key;
1415 if (strpos($cache_key, ':') !== FALSE) {
1416 list($module, $argument) = explode(':', $cache_key, 2);
1417 if (function_exists($module . '_panels_cache_clear')) {
1418 return module_invoke($module, 'panels_cache_clear', $argument, $cache);
1419 }
1420 }
1421
1422 // Fall back to our normal method:
1423 return panels_cache_clear('display', $cache_key);
1424}
1425
1426/**
1427 * Method to allow modules to provide a mechanism to break locks.
1428 */
1429function panels_edit_cache_break_lock($cache) {
1430 if (empty($cache->locked)) {
1431 return;
1432 }
1433
1434 $cache_key = $cache->display->cache_key;
1435 if (strpos($cache_key, ':') !== FALSE) {
1436 list($module, $argument) = explode(':', $cache_key, 2);
1437 if (function_exists($module . '_panels_cache_break_lock')) {
1438 return module_invoke($module, 'panels_cache_break_lock', $argument, $cache);
1439 }
1440 }
1441
1442 // Normal panel display editing has no locks, so we do nothing if there is
1443 // no fallback.
1444 return;
1445}
1446
1447// --------------------------------------------------------------------------
1448// Callbacks on behalf of the panel_context plugin.
1449//
1450// The panel_context plugin lets Panels be used in page manager. These
1451// callbacks allow the display editing system to use the page manager
1452// cache rather than the default display cache. They are routed by the cache
1453// key via panels_edit_cache_* functions.
1454
1455/**
26e7b0ef
SB
1456 * Get display edit cache on behalf of panel context.
1457 *
1458 * The key is the second half of the key in this form:
1459 * panel_context:TASK_NAME:HANDLER_NAME;
1460 */
1461function panel_context_panels_cache_get($key) {
946024d2 1462 ctools_include('common', 'panels');
26e7b0ef
SB
1463 ctools_include('context');
1464 ctools_include('context-task-handler');
1465 // this loads the panel context inc even if we don't use the plugin.
1466 $plugin = page_manager_get_task_handler('panel_context');
1467
1468 list($task_name, $handler_name) = explode(':', $key, 2);
1469 $page = page_manager_get_page_cache($task_name);
1470 if (isset($page->display_cache[$handler_name])) {
1471 return $page->display_cache[$handler_name];
1472 }
1473
1474 if ($handler_name) {
1475 $handler = &$page->handlers[$handler_name];
1476 }
1477 else {
1478 $handler = &$page->new_handler;
1479 }
1480 $cache = new stdClass();
1481
1482 $cache->display = &panels_panel_context_get_display($handler);
1483 $cache->display->context = ctools_context_handler_get_all_contexts($page->task, $page->subtask, $handler);
1484 $cache->display->cache_key = 'panel_context:' . $key;
1485 $cache->content_types = panels_common_get_allowed_types('panels_page', $cache->display->context);
1486 $cache->display_title = TRUE;
946024d2 1487 $cache->locked = $page->locked;
26e7b0ef
SB
1488
1489 return $cache;
1490}
1491
1492/**
946024d2 1493 * Get the Page Manager cache for the panel_context plugin.
26e7b0ef 1494 */
946024d2 1495function _panel_context_panels_cache_get_page_cache($key, $cache) {
26e7b0ef
SB
1496 list($task_name, $handler_name) = explode(':', $key, 2);
1497 $page = page_manager_get_page_cache($task_name);
1498 $page->display_cache[$handler_name] = $cache;
1499 if ($handler_name) {
1500 $page->handlers[$handler_name]->conf['display'] = $cache->display;
1501 $page->handler_info[$handler_name]['changed'] |= PAGE_MANAGER_CHANGED_CACHED;
1502 }
1503 else {
1504 $page->new_handler->conf['display'] = $cache->display;
1505 }
946024d2
SB
1506
1507 return $page;
1508}
1509
1510/**
1511 * Store a display edit in progress in the page cache.
1512 */
1513function panel_context_panels_cache_set($key, $cache) {
1514 $page = _panel_context_panels_cache_get_page_cache($key, $cache);
26e7b0ef
SB
1515 page_manager_set_page_cache($page);
1516}
1517
1518/**
946024d2 1519 * Save all changes made to a display using the Page Manager page cache.
26e7b0ef 1520 */
946024d2
SB
1521function panel_context_panels_cache_clear($key, $cache) {
1522 $page = _panel_context_panels_cache_get_page_cache($key, $cache);
1523 page_manager_clear_page_cache($page->task_name);
1524}
26e7b0ef 1525
946024d2
SB
1526/**
1527 * Save all changes made to a display using the Page Manager page cache.
1528 */
1529function panel_context_panels_cache_save($key, $cache) {
1530 $page = _panel_context_panels_cache_get_page_cache($key, $cache);
1531 page_manager_save_page_cache($page);
1532}
26e7b0ef 1533
946024d2
SB
1534/**
1535 * Break the lock on a page manager page.
1536 */
1537function panel_context_panels_cache_break_lock($key, $cache) {
1538 $page = _panel_context_panels_cache_get_page_cache($key, $cache);
1539 ctools_object_cache_clear_all('page_manager_page', $page->task_name);
1540}
1541
1542// --------------------------------------------------------------------------
1543// Callbacks on behalf of the panels page wizards
1544//
1545// The page wizards are a pluggable set of 'wizards' to make it easy to create
1546// specific types of pages based upon whatever someone felt like putting
1547// together. Since they will very often have content editing, we provide
1548// a generic mechanism to allow them to store their editing cache in the
1549// wizard cache.
1550//
1551// For them to use this mechanism, they just need to use:
1552// $cache = panels_edit_cache_get('panels_page_wizard:' . $plugin['name']);
1553
1554/**
1555 * Get display edit cache for the panels mini export UI
1556 *
1557 * The key is the second half of the key in this form:
1558 * panels_page_wizard:TASK_NAME:HANDLER_NAME;
1559 */
1560function panels_page_wizard_panels_cache_get($key) {
1561 ctools_include('page-wizard');
1562 ctools_include('context');
1563 $wizard_cache = page_manager_get_wizard_cache($key);
1564 if (isset($wizard_cache->display_cache)) {
1565 return $wizard_cache->display_cache;
26e7b0ef 1566 }
946024d2
SB
1567
1568 ctools_include('common', 'panels');
1569 $cache = new stdClass();
1570 $cache->display = $wizard_cache->display;
1571 $cache->display->context = !empty($wizard_cache->context) ? $wizard_cache->context : array();
1572 $cache->display->cache_key = 'panels_page_wizard:' . $key;
1573 $cache->content_types = panels_common_get_allowed_types('panels_page', $cache->display->context);
1574 $cache->display_title = TRUE;
1575
1576 return $cache;
1577}
1578
1579/**
1580 * Store a display edit in progress in the page cache.
1581 */
1582function panels_page_wizard_panels_cache_set($key, $cache) {
1583 ctools_include('page-wizard');
1584 $wizard_cache = page_manager_get_wizard_cache($key);
1585 $wizard_cache->display_cache = $cache;
1586 page_manager_set_wizard_cache($wizard_cache);
1587}
1588
1589// --------------------------------------------------------------------------
1590// General utility functions
1591
1592/**
1593 * Perform a drupal_goto on a destination that may be an array like url().
1594 */
1595function panels_goto($destination) {
1596 if (!is_array($destination)) {
1597 return drupal_goto($destination);
1598 }
1599 else {
1600 // Prevent notices by adding defaults
1601 $destination += array(
1602 'query' => NULL,
1603 'fragment' => NULL,
1604 'http_response_code' => NULL,
1605 );
1606
1607 return drupal_goto($destination['path'], $destination['query'], $destination['fragment'], $destination['http_response_code']);
26e7b0ef 1608 }
946024d2 1609}
26e7b0ef 1610
946024d2
SB
1611
1612/**
1613 * For external use: Given a layout ID and a $content array, return the
1614 * panel display.
1615 *
1616 * The content array is filled in based upon the content available in the
1617 * layout. If it's a two column with a content array defined like
1618 * @code
1619 * array(
1620 * 'left' => t('Left side'),
1621 * 'right' => t('Right side')
1622 * ),
1623 * @endcode
1624 *
1625 * Then the $content array should be
1626 * @code
1627 * array(
1628 * 'left' => $output_left,
1629 * 'right' => $output_right,
1630 * )
1631 * @endcode
1632 *
1633 * The output within each panel region can be either a single rendered
1634 * HTML string or an array of rendered HTML strings as though they were
1635 * panes. They will simply be concatenated together without separators.
1636 */
1637function panels_print_layout($layout, $content, $meta = 'standard') {
1638 ctools_include('plugins', 'panels');
1639
1640 // Create a temporary display for this.
1641 $display = panels_new_display();
1642 $display->layout = is_array($layout) ? $layout['name'] : $layout;
1643 $display->content = $content;
1644
1645 // Get our simple renderer
1646 $renderer = panels_get_renderer_handler('simple', $display);
1647 $renderer->meta_location = $meta;
1648
1649 return $renderer->render();
1650}
1651
1652// --------------------------------------------------------------------------
1653// Deprecated functions
1654//
1655// Everything below this line will eventually go away.
1656
1657/**
946024d2
SB
1658 * panels path helper function
1659 */
1660function panels_get_path($file, $base_path = FALSE, $module = 'panels') {
1661 $output = $base_path ? base_path() : '';
1662 return $output . drupal_get_path('module', $module) . '/' . $file;
1663}