Fix minor cut and paste error.
[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
40ab7380 10define('PANELS_REQUIRED_CTOOLS_API', '1.1.1');
ee0e6fa9 11
b76f0bd4 12/**
f2e5a0c5
EM
13 * Returns the API version of Panels. This didn't exist in 1.
14 *
15 * @return An array with the major and minor versions
9f3609c9 16 */
f2e5a0c5 17function panels_api_version() {
5cf19555 18 return array(3, 0);
f2e5a0c5
EM
19}
20
5cf19555
EM
21/**
22 * Implementation of hook_theme()
23 */
f2e5a0c5 24function panels_theme() {
ee0e6fa9
EM
25 // Safety: go away if CTools is not at an appropriate version.
26 if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
ab4648f3
EM
27 return array();
28 }
29
f2e5a0c5
EM
30 $theme = array();
31 $theme['panels_layout_link'] = array(
32 'arguments' => array('title', 'id', 'image', 'link'),
33 );
34 $theme['panels_layout_icon'] = array(
35 'arguments' => array('id', 'image', 'title' => NULL),
36 );
f2e5a0c5
EM
37 $theme['panels_edit_display_form'] = array(
38 'arguments' => array('form'),
39 'file' => 'includes/display-edit.inc',
40 );
41 $theme['panels_edit_layout_form_choose'] = array(
42 'arguments' => array('form'),
43 'file' => 'includes/display-edit.inc',
44 );
f2e5a0c5
EM
45 $theme['panels_pane'] = array(
46 'arguments' => array('content', 'pane', 'display'),
6d7f8ba7 47 'file' => 'includes/display-render.inc',
f2e5a0c5
EM
48 );
49 $theme['panels_common_content_list'] = array(
50 'arguments' => array('display'),
51 'file' => 'includes/common.inc',
52 );
d75b98c7
EM
53 $theme['panels_render_display_form'] = array(
54 'arguments' => array('form' => NULL),
55 );
f2e5a0c5 56
287328f3
EM
57 $theme['panels_dashboard'] = array(
58 'arguments' => array(),
59 'path' => drupal_get_path('module', 'panels') . '/templates',
60 'file' => '../includes/callbacks.inc',
61 'template' => 'panels-dashboard',
62 );
ee0e6fa9 63
f2e5a0c5
EM
64 // Register layout and style themes on behalf of all of these items.
65 panels_load_include('plugins');
66
67 // No need to worry about files; the plugin has to already be loaded for us
68 // to even know what the theme function is, so files will be auto included.
69 $layouts = panels_get_layouts();
70 foreach ($layouts as $name => $data) {
efcf7fcf
EM
71 foreach (array('theme', 'admin theme') as $callback) {
72 if (!empty($data[$callback])) {
73 $theme[$data[$callback]] = array(
74 'arguments' => array('css_id' => NULL, 'content' => NULL, 'settings' => NULL, 'display' => NULL),
75 'path' => $data['path'],
76 );
77
78 // if no theme function exists, assume template.
79 if (!function_exists("theme_$data[theme]")) {
80 $theme[$data[$callback]]['template'] = str_replace('_', '-', $data[$callback]);
81 $theme[$data[$callback]]['file'] = $data['file']; // for preprocess.
82 }
fc8f8fa8 83 }
f2e5a0c5 84 }
9f3609c9 85 }
f2e5a0c5
EM
86
87 $styles = panels_get_styles();
88 foreach ($styles as $name => $data) {
89 if (!empty($data['render pane'])) {
90 $theme[$data['render pane']] = array(
91 'arguments' => array('content' => NULL, 'pane' => NULL, 'display' => NULL),
92 );
93 }
94 if (!empty($data['render panel'])) {
95 $theme[$data['render panel']] = array(
96 'arguments' => array('display' => NULL, 'panel_id' => NULL, 'panes' => NULL, 'settings' => NULL),
97 );
98 }
99
efcf7fcf
EM
100 if (!empty($data['hook theme'])) {
101 if (is_array($data['hook theme'])) {
102 $theme += $data['hook theme'];
103 }
104 else if (function_exists($data['hook theme'])) {
105 $data['hook theme']($theme, $data);
106 }
107 }
f2e5a0c5
EM
108 }
109
110 return $theme;
9f3609c9
EM
111}
112
113/**
f2e5a0c5 114 * Implementation of hook_menu
9f3609c9 115 */
f2e5a0c5 116function panels_menu() {
ee0e6fa9
EM
117 // Safety: go away if CTools is not at an appropriate version.
118 if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
ab4648f3
EM
119 return array();
120 }
f2e5a0c5
EM
121 $items = array();
122
123 // Provide some common options to reduce code repetition.
124 // By using array addition and making sure these are the rightmost
125 // value, they won't override anything already set.
126 $base = array(
127 'access arguments' => array('access content'),
128 'type' => MENU_CALLBACK,
129 'file' => 'includes/display-edit.inc',
026b6377 130 'page arguments' => array(3),
f2e5a0c5
EM
131 );
132
026b6377 133 $items['panels/ajax/add-pane/%panels_edit_cache'] = array(
f2e5a0c5
EM
134 'page callback' => 'panels_ajax_add_pane_choose',
135 ) + $base;
026b6377 136 $items['panels/ajax/add-pane-config/%panels_edit_cache'] = array(
f2e5a0c5
EM
137 'page callback' => 'panels_ajax_add_pane_config',
138 ) + $base;
026b6377 139 $items['panels/ajax/configure/%panels_edit_cache'] = array(
f2e5a0c5
EM
140 'page callback' => 'panels_ajax_configure_pane',
141 ) + $base;
026b6377 142 $items['panels/ajax/show/%panels_edit_cache'] = array(
f2e5a0c5 143 'page callback' => 'panels_ajax_toggle_shown',
026b6377 144 'page arguments' => array('show', 3),
f2e5a0c5 145 ) + $base;
026b6377 146 $items['panels/ajax/hide/%panels_edit_cache'] = array(
f2e5a0c5 147 'page callback' => 'panels_ajax_toggle_shown',
026b6377 148 'page arguments' => array('hide', 3),
f2e5a0c5 149 ) + $base;
026b6377 150 $items['panels/ajax/cache-method/%panels_edit_cache'] = array(
f2e5a0c5
EM
151 'page callback' => 'panels_ajax_cache_method',
152 ) + $base;
026b6377 153 $items['panels/ajax/cache-settings/%panels_edit_cache'] = array(
f2e5a0c5
EM
154 'page callback' => 'panels_ajax_cache_settings',
155 ) + $base;
026b6377 156 $items['panels/ajax/display-settings/%panels_edit_cache'] = array(
efcf7fcf
EM
157 'page callback' => 'panels_ajax_display_settings',
158 ) + $base;
026b6377 159 $items['panels/ajax/style-type/%/%panels_edit_cache'] = array(
efcf7fcf 160 'page callback' => 'panels_ajax_style_type',
026b6377 161 'page arguments' => array(3, 4),
efcf7fcf 162 ) + $base;
026b6377 163 $items['panels/ajax/style-settings/%/%panels_edit_cache'] = array(
efcf7fcf 164 'page callback' => 'panels_ajax_style_settings',
026b6377 165 'page arguments' => array(3, 4),
efcf7fcf 166 ) + $base;
026b6377 167 $items['panels/ajax/pane-css/%panels_edit_cache'] = array(
efcf7fcf
EM
168 'page callback' => 'panels_ajax_configure_pane_css',
169 ) + $base;
026b6377 170 $items['panels/ajax/access-settings/%panels_edit_cache'] = array(
efcf7fcf
EM
171 'page callback' => 'panels_ajax_configure_access_settings',
172 ) + $base;
026b6377 173 $items['panels/ajax/access-test/%panels_edit_cache'] = array(
efcf7fcf
EM
174 'page callback' => 'panels_ajax_configure_access_test',
175 ) + $base;
026b6377 176 $items['panels/ajax/access-add/%panels_edit_cache'] = array(
efcf7fcf
EM
177 'page callback' => 'panels_ajax_add_access_test',
178 ) + $base;
026b6377 179 $items['panels/ajax/preview/%panels_edit_cache'] = array(
66b7e481 180 'page callback' => 'panels_ajax_preview',
f2e5a0c5 181 ) + $base;
f2e5a0c5 182
b2313b92
EM
183 $admin_base = array(
184 'file' => 'includes/callbacks.inc',
c5792555 185 'access arguments' => array('use panels dashboard'),
b2313b92
EM
186
187 );
f2e5a0c5 188 // Provide a nice location for a panels admin panel.
287328f3 189 $items['admin/build/panels'] = array(
17a90678 190 'title' => 'Panels',
ade117f2 191 'page callback' => 'panels_admin_page',
17a90678 192 'description' => 'Administer items related to the Panels module.',
b2313b92 193 ) + $admin_base;
f2e5a0c5 194
287328f3
EM
195 $items['admin/build/panels/dashboard'] = array(
196 'title' => 'Dashboard',
287328f3 197 'page callback' => 'panels_admin_page',
287328f3
EM
198 'type' => MENU_DEFAULT_LOCAL_TASK,
199 'weight' => -10,
b2313b92 200 ) + $admin_base;
287328f3
EM
201
202 $items['admin/build/panels/settings'] = array(
203 'title' => 'Settings',
287328f3
EM
204 'page callback' => 'drupal_get_form',
205 'page arguments' => array('panels_admin_settings_page'),
287328f3 206 'type' => MENU_LOCAL_TASK,
b2313b92 207 ) + $admin_base;
287328f3
EM
208
209 $items['admin/build/panels/settings/general'] = array(
210 'title' => 'General',
287328f3
EM
211 'page callback' => 'drupal_get_form',
212 'page arguments' => array('panels_admin_settings_page'),
6a04891d 213 'access arguments' => array('administer page manager'),
287328f3
EM
214 'type' => MENU_DEFAULT_LOCAL_TASK,
215 'weight' => -10,
b2313b92
EM
216 ) + $admin_base;
217
6a04891d 218 if (module_exists('page_manager')) {
b2313b92
EM
219 $items['admin/build/panels/settings/panel-page'] = array(
220 'title' => 'Panel pages',
221 'page callback' => 'panels_admin_panel_context_page',
222 'type' => MENU_LOCAL_TASK,
223 'weight' => -10,
224 ) + $admin_base;
225 }
ec7ad513 226
a606b3ba 227 panels_load_include('plugins');
efcf7fcf
EM
228 $layouts = panels_get_layouts();
229 foreach ($layouts as $name => $data) {
230 if (!empty($data['hook menu'])) {
231 if (is_array($data['hook menu'])) {
232 $items += $data['hook menu'];
233 }
234 else if (function_exists($data['hook menu'])) {
235 $data['hook menu']($items, $data);
236 }
237 }
238 }
ab4648f3 239
f2e5a0c5 240 return $items;
9f3609c9
EM
241}
242
243/**
026b6377
EM
244 * Menu loader function to load a cache item for Panels AJAX.
245 *
246 * This load all of the includes needed to perform AJAX, and loads the
247 * cache object and makes sure it is valid.
248 */
249function panels_edit_cache_load($cache_key) {
250 panels_load_include('display-edit');
251 panels_load_include('plugins');
252 ctools_include('ajax');
253 ctools_include('modal');
254 ctools_include('context');
255
256 return panels_edit_cache_get($cache_key);
257}
258
259/**
f2e5a0c5 260 * Implementation of hook_init()
9f3609c9 261 */
f2e5a0c5 262function panels_init() {
ee0e6fa9
EM
263 // Safety: go away if CTools is not at an appropriate version.
264 if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
265 return;
266 }
267
f2e5a0c5
EM
268 drupal_add_css(panels_get_path('css/panels.css'));
269 drupal_add_js(panels_get_path('js/panels.js'));
9f3609c9
EM
270}
271
272/**
f2e5a0c5 273 * Load a panels include file.
9f3609c9 274 */
f2e5a0c5 275function panels_load_include($include, $path = 'includes/') {
0f4c2cf6
SB
276 static $loaded = array();
277 if (empty($loaded["$path$include.inc"])) {
278 require_once './' . panels_get_path("$path$include.inc");
279 $loaded["$path$include.inc"] = TRUE;
280 }
9f3609c9
EM
281}
282
283/**
284 * panels path helper function
285 */
f2e5a0c5
EM
286function panels_get_path($file, $base_path = FALSE, $module = 'panels') {
287 $output = $base_path ? base_path() : '';
9f3609c9
EM
288 return $output . drupal_get_path('module', $module) . '/' . $file;
289}
290
f2e5a0c5
EM
291/**
292 * Implementation of hook_perm
293 */
294function panels_perm() {
295 return array(
296 'view all panes',
297 'view pane admin links',
298 'administer pane visibility',
299 'administer pane access',
300 'administer advanced pane settings',
3c7f35d6 301 'use panels caching features',
c5792555 302 'use panels dashboard',
f2e5a0c5
EM
303 );
304}
9f3609c9
EM
305
306/**
f2e5a0c5 307 * Get an object from cache.
9f3609c9 308 */
f2e5a0c5 309function panels_cache_get($obj, $did, $skip_cache = FALSE) {
5cf19555 310 ctools_include('object-cache');
d84ff3ec
EM
311 // we often store contexts in cache, so let's just make sure we can load
312 // them.
313 ctools_include('context');
6a04891d 314 return ctools_object_cache_get($obj, 'panels_display:' . $did, $skip_cache);
9f3609c9
EM
315}
316
317/**
f2e5a0c5 318 * Save the edited object into the cache.
9f3609c9 319 */
f2e5a0c5 320function panels_cache_set($obj, $did, $cache) {
5cf19555 321 ctools_include('object-cache');
6a04891d 322 return ctools_object_cache_set($obj, 'panels_display:' . $did, $cache);
9f3609c9
EM
323}
324
f2e5a0c5
EM
325/**
326 * Clear a object from the cache; used if the editing is aborted.
327 */
328function panels_cache_clear($obj, $did) {
5cf19555 329 ctools_include('object-cache');
6a04891d 330 return ctools_object_cache_clear($obj, 'panels_display:' . $did);
f2e5a0c5 331}
9f3609c9 332
9f3609c9 333// ---------------------------------------------------------------------------
f2e5a0c5 334// panels display editing
9f3609c9
EM
335
336/**
f2e5a0c5
EM
337 * @defgroup mainapi Functions comprising the main panels API
338 * @{
9f3609c9 339 */
9f3609c9 340
f2e5a0c5
EM
341/**
342 * Main API entry point to edit a panel display.
343 *
344 * Sample implementations utiltizing the the complex $destination behavior can be found
345 * in panels_page_edit_content() and, in a separate contrib module, OG Blueprints
346 * (http://drupal.org/project/og_blueprints), og_blueprints_blueprint_edit().
347 *
348 * @ingroup mainapi
349 *
350 * @param object $display instanceof panels_display \n
351 * A fully loaded panels $display object, as returned from panels_load_display().
352 * Merely passing a did is NOT sufficient. \n
353 * Note that 'fully loaded' means the $display must already be loaded with any contexts
354 * the caller wishes to have set for the display.
355 * @param mixed $destination \n
356 * The redirect destination that the user should be taken to on form submission or
357 * cancellation. With panels_edit, $destination has complex effects on the return
358 * values of panels_edit() once the form has been submitted. See the explanation of
359 * the return value below to understand the different types of values returned by panels_edit()
360 * at different stages of FAPI. Under most circumstances, simply passing in
361 * drupal_get_destination() is all that's necessary.
362 * @param array $content_types \n
363 * An associative array of allowed content types, typically as returned from
364 * panels_common_get_allowed_types(). Note that context partially governs available content types,
365 * so you will want to create any relevant contexts using panels_create_context() or
366 * panels_create_context_empty() to make sure all the appropriate content types are available.
367 *
368 * @return
369 * Because the functions called by panels_edit() invoke the form API, this function
370 * returns different values depending on the stage of form submission we're at. In Drupal 5,
371 * the phase of form submission is indicated by the contents of $_POST['op']. Here's what you'll
372 * get at different stages:
373 * -# If !$_POST['op']: then we're on on the initial passthrough and the form is being
374 * rendered, so it's the $form itself that's being returned. Because negative margins,
375 * a common CSS technique, bork the display editor's ajax drag-and-drop, it's important
376 * that the $output be printed, not returned. Use this syntax in the caller function: \n
377 * print theme('page', panels_edit($display, $destination, $content_types), FALSE); \n
378 * -# If $_POST['op'] == t('Cancel'): form submission has been cancelled. If empty($destination) == FALSE,
379 * then there is no return value and the panels API takes care of redirecting to $destination.
380 * If empty($destination) == TRUE, then there's still no return value, but the caller function
381 * has to take care of form redirection.
382 * -# If $_POST['op'] == ('Save'): the form has been submitted successfully and has run through
383 * panels_edit_display_submit(). $output depends on the value of $destination:
384 * - If empty($destination) == TRUE: $output contains the modified $display
385 * object, and no redirection will occur. This option is useful if the caller
386 * needs to perform additional operations on or with the modified $display before
387 * the page request is complete. Using hook_form_alter() to add an additional submit
388 * handler is typically the preferred method for something like this, but there
389 * are certain use cases where that is infeasible and $destination = NULL should
390 * be used instead. If this method is employed, the caller will need to handle form
391 * redirection. Note that having $_REQUEST['destination'] set, whether via
392 * drupal_get_destination() or some other method, will NOT interfere with this
393 * functionality; consequently, you can use drupal_get_destination() to safely store
394 * your desired redirect in the caller function, then simply use drupal_goto() once
395 * panels_edit() has done its business.
396 * - If empty($destination) == FALSE: the form will redirect to the URL string
397 * given in $destination and NO value will be returned.
9f3609c9 398 */
efcf7fcf 399function panels_edit($display, $destination = NULL, $content_types = NULL, $title = FALSE) {
f2e5a0c5 400 panels_load_include('display-edit');
95f6686a 401 ctools_include('ajax');
f2e5a0c5 402 panels_load_include('plugins');
efcf7fcf 403 return _panels_edit($display, $destination, $content_types, $title);
f2e5a0c5 404}
9f3609c9 405
f2e5a0c5
EM
406/**
407 * API entry point for selecting a layout for a given display.
408 *
409 * Layout selection is nothing more than a list of radio items encompassing the available
410 * layouts for this display, as defined by .inc files in the panels/layouts subdirectory.
411 * The only real complexity occurs when a user attempts to change the layout of a display
412 * that has some content in it.
413 *
414 * @param object $display instanceof panels_display \n
415 * A fully loaded panels $display object, as returned from panels_load_display().
416 * Merely passing a did is NOT sufficient.
417 * @param string $finish
418 * A string that will be used for the text of the form submission button. If no value is provided,
419 * then the form submission button will default to t('Save').
420 * @param mixed $destination
421 * Basic usage is a string containing the URL that the form should redirect to upon submission.
422 * For a discussion of advanced usages, see panels_edit().
423 * @param mixed $allowed_layouts
424 * Allowed layouts has three different behaviors that depend on which of three value types
425 * are passed in by the caller:
426 * #- if $allowed_layouts instanceof panels_allowed_layouts (includes subclasses): the most
427 * complex use of the API. The caller is passing in a loaded panels_allowed_layouts object
428 * that the client module previously created and stored somewhere using a custom storage
429 * mechanism.
430 * #- if is_string($allowed_layouts): the string will be used in a call to variable_get() which
431 * will call the $allowed_layouts . '_allowed_layouts' var. If the data was stored properly
432 * in the system var, the $allowed_layouts object will be unserialized and recreated.
433 * @see panels_common_set_allowed_layouts()
434 * #- if is_null($allowed_layouts): the default behavior, which also provides backwards
435 * compatibility for implementations of the Panels2 API written before beta4. In this case,
436 * a dummy panels_allowed_layouts object is created which does not restrict any layouts.
437 * Subsequent behavior is indistinguishable from pre-beta4 behavior.
438 *
439 * @return
440 * Can return nothing, or a modified $display object, or a redirection string; return values for the
441 * panels_edit* family of functions are quite complex. See panels_edit() for detailed discussion.
442 * @see panels_edit()
443 */
444function panels_edit_layout($display, $finish, $destination = NULL, $allowed_layouts = NULL) {
445 panels_load_include('display-layout');
446 panels_load_include('plugins');
447 return _panels_edit_layout($display, $finish, $destination, $allowed_layouts);
9f3609c9
EM
448}
449
f2e5a0c5
EM
450/**
451 * API entry point for configuring the layout settings for a given display.
452 *
453 * For all layouts except Flexible, the layout settings form allows the user to select styles,
454 * as defined by .inc files in the panels/styles subdirectory, for the panels in their display.
455 * For the Flexible layout, the layout settings form allows the user to provide dimensions
456 * for their flexible layout in addition to applying styles to panels.
457 *
458 * @param object $display instanceof panels_display \n
459 * A fully loaded panels $display object, as returned from panels_load_display().
460 * Merely passing a did is NOT sufficient.
461 * @param string $finish
462 * A string that will be used for the text of (one of) the form submission button(s). Note that
463 * panels will NOT wrap $finish in t() for you, so your caller should make sure to do so. \n
464 * The submit behavior of the form is primarily governed by the value of $destination (see
465 * below), but is secondarily governed by $finish as follows:
466 * -# If $finish != t('Save'), then two #submit buttons will be present: one with the button
467 * text t('Save'), and the other with the button text $finish. .
468 * - Clicking the 'Save' button will save any changes on the form to the $display object and
469 * keep the user on the same editing page.
470 * - Clicking the $finish button will also save the $display object, but the user will be
471 * redirected to the URL specified in $destination.
472 * -# If $finish == t('Save'), then there is only one button, still called t('Save'), but it
473 * mimics the behavior of the $finish button above by redirecting the user away from the form.
474 * @param mixed $destination
475 * Basic usage is a string containing the URL that the form should redirect to upon submission.
476 * For a discussion of advanced usages that rely on NULL values for $destination, see the
477 * panels_edit() documentation.
478 * @param mixed $title
479 * The $title variable has three modes of operation:
480 * -# If $title == FALSE (the default), then no widget will appear on the panels_edit_layout_settings form
481 * allowing the user to select a title, and other means for setting page titles will take precedent. If
482 * no other means are used to provide a title, then the title will be hidden when rendering the $display.
483 * -# If $title == TRUE, then two widgets will appear on the panels_edit_layout_settings form allowing the
484 * user to input a title specific to this $display, as well as a checkbox enabling the user to disable
485 * page titles entirely for this $display object.
486 * -# If $title == (string), then the behavior is very similar to mode 2, but the widget description
487 * on the title textfield will indicate that the $title string will be used as the default page title
488 * if none is provided on this form. When utilizing this option, note that the panels API can only
489 * provide the data for these values; you must implement the appropriate conditionals to make it true.
490 *
491 * @return
492 * Can return nothing, or a modified $display object, or a redirection string; return values for the
493 * panels_edit* family of functions are quite complex. See panels_edit() for detailed discussion.
494 * @see panels_edit()
9f3609c9 495 */
f2e5a0c5
EM
496function panels_edit_layout_settings($display, $finish, $destination = NULL, $title = FALSE) {
497 panels_load_include('display-layout-settings');
95f6686a 498 ctools_include('ajax');
f2e5a0c5
EM
499 panels_load_include('plugins');
500 return _panels_edit_layout_settings($display, $finish, $destination, $title);
9f3609c9
EM
501}
502
f2e5a0c5
EM
503
504// ---------------------------------------------------------------------------
505// panels database functions
506
9f3609c9 507/**
f2e5a0c5
EM
508 * Forms the basis of a panel display
509 *
9f3609c9 510 */
f2e5a0c5
EM
511class panels_display {
512 var $args = array();
513 var $content = array();
514 var $panels = array();
515 var $incoming_content = NULL;
516 var $css_id = NULL;
517 var $context = array();
dce7d3e4 518 var $did = 'new';
f2e5a0c5
EM
519
520 function add_pane($pane, $location = FALSE) {
521 $pane->pid = $this->next_new_pid();
522 if (!$location || !isset($this->panels[$location])) {
523 foreach ($this->panels as $panel_name => $panel) {
524 if (array_key_exists($pane->pid, $panel)) {
525 $this->panels[$panel_name][] = $pane->pid;
526 }
9f3609c9 527 }
9f3609c9 528 }
f2e5a0c5
EM
529 else {
530 $this->panels[$location][] = $pane->pid;
531 }
9f3609c9
EM
532 }
533
f2e5a0c5
EM
534 function duplicate_pane($pid, $location = FALSE) {
535 $pane = $this->clone_pane($pid);
536 $this->add_pane($pane, $location);
9f3609c9
EM
537 }
538
f2e5a0c5
EM
539 function clone_pane($pid) {
540 $pane = drupal_clone($this->content[$pid]);
541 foreach (array_keys($this->content) as $pidcheck) {
542 // necessary?
543 unset($pane->position);
544 }
545 return $pane;
546 }
9f3609c9 547
f2e5a0c5
EM
548 function next_new_pid() {
549 // necessary if/until we use this method and ONLY this method for adding temporary pids.
550 // then we can do it with a nice static var.
551 $id = array(0);
552 foreach (array_keys($this->content) as $pid) {
553 if (!is_numeric($pid)) {
554 $id[] = substr($pid, 4);
555 }
556 }
ae06d315 557 $next_id = max($id);
f2e5a0c5 558 return ++$next_id;
9f3609c9 559 }
9f3609c9
EM
560}
561
562/**
f2e5a0c5 563 * }@ End of 'defgroup mainapi', although other functions are specifically added later
9f3609c9 564 */
f2e5a0c5 565
9f3609c9 566/**
f2e5a0c5
EM
567 * Clean up a display object and add some required information, if missing.
568 *
569 * Currently a display object needs 'args', 'incoming content', 'context'
570 * and a 'css_id'.
571 *
572 * @param &$display
573 * The display object to be sanitized.
574 * @return
575 * The sanitized display object.
9f3609c9 576 */
f2e5a0c5 577function panels_sanitize_display(&$display) {
3aa2f787 578 return;
f2e5a0c5
EM
579 if (!isset($display->args)) {
580 $display->args = array();
581 }
9f3609c9 582
f2e5a0c5
EM
583 if (!isset($display->incoming_content)) {
584 $display->incoming_content = NULL;
585 }
9f3609c9 586
f2e5a0c5
EM
587 if (!isset($display->context)) {
588 $display->context = array();
589 }
9f3609c9 590
f2e5a0c5
EM
591 if (!isset($display->css_id)) {
592 $display->css_id = NULL;
9f3609c9 593 }
f2e5a0c5 594}
9f3609c9 595
f2e5a0c5
EM
596/**
597 * Creates a new display, setting the ID to our magic new id.
598 */
599function panels_new_display() {
9380e8a5
EM
600 ctools_include('export');
601 $display = ctools_export_new_object('panels_display', FALSE);
f2e5a0c5
EM
602 $display->did = 'new';
603 return $display;
604}
9f3609c9 605
5cf19555
EM
606/**
607 * Create a new pane.
608 *
609 * @todo -- use schema API for some of this?
610 */
f2e5a0c5 611function panels_new_pane($type, $subtype) {
9380e8a5
EM
612 ctools_include('export');
613 $pane = ctools_export_new_object('panels_pane', FALSE);
f2e5a0c5
EM
614 $pane->pid = 'new';
615 $pane->type = $type;
616 $pane->subtype = $subtype;
f2e5a0c5
EM
617 return $pane;
618}
9f3609c9 619
f2e5a0c5
EM
620/**
621 * Load and fill the requested $display object(s).
622 *
623 * Helper function primarily for for panels_load_display().
624 *
625 * @param array $dids
626 * An indexed array of dids to be loaded from the database.
627 *
628 * @return $displays
629 * An array of displays, keyed by their display dids.
5cf19555
EM
630 *
631 * @todo schema API can drasticly simplify this code.
f2e5a0c5
EM
632 */
633function panels_load_displays($dids) {
634 $displays = array();
635 if (empty($dids) || !is_array($dids)) {
636 return $displays;
637 }
9f3609c9 638
5cf19555 639 $result = db_query("SELECT * FROM {panels_display} WHERE did IN (" . db_placeholders($dids) . ")", $dids);
9f3609c9 640
9380e8a5
EM
641 ctools_include('export');
642 while ($obj = db_fetch_object($result)) {
643 $displays[$obj->did] = ctools_export_unpack_object('panels_display', $obj);
f2e5a0c5
EM
644 }
645
9380e8a5
EM
646 // @TODO
647 //
648 // This code clearly never worked ($content, $layout and $settings are all unset)
649 // where was this supposed to even have been?
650
651// foreach (module_implements('panels_layout_content_alter') as $module) {
652// $function = $module . '_panels_layout_content_alter';
653// $function($content, $layout, $settings);
654// }
9f3609c9 655
5cf19555 656 $result = db_query("SELECT * FROM {panels_pane} WHERE did IN (" . db_placeholders($dids) . ") ORDER BY did, panel, position", $dids);
9f3609c9 657
9380e8a5
EM
658 while ($obj = db_fetch_object($result)) {
659 $pane = ctools_export_unpack_object('panels_pane', $obj);
f2e5a0c5
EM
660
661 $displays[$pane->did]->panels[$pane->panel][] = $pane->pid;
662 $displays[$pane->did]->content[$pane->pid] = $pane;
663 }
9380e8a5 664
f2e5a0c5 665 return $displays;
9f3609c9
EM
666}
667
668/**
f2e5a0c5
EM
669 * Load a single display.
670 *
671 * @ingroup mainapi
672 *
673 * @param int $did
674 * The display id (did) of the display to be loaded.
675 *
676 * @return object $display instanceof panels_display \n
677 * Returns a partially-loaded panels_display object. $display objects returned from
678 * from this function have only the following data:
679 * - $display->did (the display id)
680 * - $display->name (the 'name' of the display, where applicable - it often isn't)
681 * - $display->layout (a string with the system name of the display's layout)
682 * - $display->panel_settings (custom layout style settings contained in an associative array; NULL if none)
683 * - $display->layout_settings (panel size and configuration settings for Flexible layouts; NULL if none)
684 * - $display->css_id (the special css_id that has been assigned to this display, if any; NULL if none)
685 * - $display->content (an array of pane objects, keyed by pane id (pid))
686 * - $display->panels (an associative array of panel regions, each an indexed array of pids in the order they appear in that region)
687 * - $display->cache (any relevant data from panels_simple_cache)
688 * - $display->args
689 * - $display->incoming_content
690 *
691 * While all of these members are defined, $display->context is NEVER defined in the returned $display;
b6a6c965 692 * it must be set using one of the ctools_context_create() functions.
9f3609c9 693 */
f2e5a0c5
EM
694function panels_load_display($did) {
695 $displays = panels_load_displays(array($did));
696 if (!empty($displays)) {
697 return array_shift($displays);
698 }
699}
9f3609c9 700
f2e5a0c5
EM
701/**
702 * Save a display object.
703 *
704 * @ingroup mainapi
705 *
706 * Note a new $display only receives a real did once it is run through this function.
707 * Until then, it uses a string placeholder, 'new', in place of a real did. The same
708 * applies to all new panes (whether on a new $display or not); in addition,
709 * panes have sequential numbers appended, of the form 'new-1', 'new-2', etc.
710 *
711 * @param object $display instanceof panels_display \n
712 * The display object to be saved. Passed by reference so the caller need not use
713 * the return value for any reason except convenience.
714 *
715 * @return object $display instanceof panels_display \n
716 */
717function panels_save_display(&$display) {
9380e8a5
EM
718 $update = (isset($display->did) && is_numeric($display->did)) ? array('did') : array();
719 drupal_write_record('panels_display', $display, $update);
720
721 $pids = array();
722 if ($update) {
f2e5a0c5
EM
723 // Get a list of all panes currently in the database for this display so we can know if there
724 // are panes that need to be deleted. (i.e, aren't currently in our list of panes).
725 $result = db_query("SELECT pid FROM {panels_pane} WHERE did = %d", $display->did);
726 while ($pane = db_fetch_object($result)) {
727 $pids[$pane->pid] = $pane->pid;
9f3609c9 728 }
f2e5a0c5 729 }
9f3609c9 730
f2e5a0c5
EM
731 // update all the panes
732 panels_load_include('plugins');
66f397be 733 ctools_include('content');
9f3609c9 734
9380e8a5 735 foreach ($display->panels as $id => $panes) {
f2e5a0c5
EM
736 $position = 0;
737 $new_panes = array();
738 foreach ((array) $panes as $pid) {
746ca3a2
EM
739 if (!isset($display->content[$pid])) {
740 continue;
741 }
f2e5a0c5 742 $pane = $display->content[$pid];
d49e01de 743 $type = ctools_get_content_type($pane->type);
f2e5a0c5 744
9380e8a5
EM
745 $pane->position = $position++;
746 $pane->did = $display->did;
f2e5a0c5 747
9380e8a5
EM
748 $old_pid = $pane->pid;
749 drupal_write_record('panels_pane', $pane, is_numeric($pid) ? array('pid') : array());
f2e5a0c5 750
9380e8a5
EM
751 if ($pane->pid != $old_pid) {
752 // and put it back so our pids and positions can be used
753 unset($display->content[$id]);
754 $display->content[$pane->pid] = $pane;
f2e5a0c5 755 }
9380e8a5
EM
756
757 // re-add this to the list of content for this panel.
f2e5a0c5 758 $new_panes[] = $pane->pid;
9380e8a5
EM
759
760 // Remove this from the list of panes scheduled for deletion.
f2e5a0c5
EM
761 if (isset($pids[$pane->pid])) {
762 unset($pids[$pane->pid]);
9f3609c9
EM
763 }
764 }
9f3609c9 765
f2e5a0c5
EM
766 $display->panels[$id] = $new_panes;
767 }
f6c1a273 768 if (!empty($pids)) {
f2e5a0c5 769 db_query("DELETE FROM {panels_pane} WHERE pid IN (" . db_placeholders($pids) . ")", $pids);
9f3609c9 770 }
9f3609c9 771
f2e5a0c5
EM
772 // Clear any cached content for this display.
773 panels_clear_cached_content($display);
9f3609c9 774
f2e5a0c5
EM
775 // to be nice, even tho we have a reference.
776 return $display;
9f3609c9
EM
777}
778
779/**
f2e5a0c5 780 * Delete a display.
9f3609c9 781 */
f2e5a0c5
EM
782function panels_delete_display($display) {
783 if (is_object($display)) {
784 $did = $display->did;
785 }
786 else {
787 $did = $display;
788 }
789 db_query("DELETE FROM {panels_display} WHERE did = %d", $did);
790 db_query("DELETE FROM {panels_pane} WHERE did = %d", $did);
9f3609c9
EM
791}
792
793/**
f2e5a0c5
EM
794 * Exports the provided display into portable code.
795 *
796 * This function is primarily intended as a mechanism for cloning displays.
797 * It generates an exact replica (in code) of the provided $display, with
798 * the exception that it replaces all ids (dids and pids) with 'new-*' values.
799 * Only once panels_save_display() is called on the code version of $display will
800 * the exported display written to the database and permanently saved.
801 *
802 * @see panels_page_export() or _panels_page_fetch_display() for sample implementations.
803 *
804 * @ingroup mainapi
805 *
806 * @param object $display instanceof panels_display \n
807 * This export function does no loading of additional data about the provided
808 * display. Consequently, the caller should make sure that all the desired data
809 * has been loaded into the $display before calling this function.
810 * @param string $prefix
811 * A string prefix that is prepended to each line of exported code. This is primarily
812 * used for prepending a double space when exporting so that the code indents and lines up nicely.
813 *
814 * @return string $output
815 * The passed-in $display expressed as code, ready to be imported. Import by running
816 * eval($output) in the caller function; doing so will create a new $display variable
817 * with all the exported values. Note that if you have already defined a $display variable in
818 * the same scope as where you eval(), your existing $display variable WILL be overwritten.
9f3609c9 819 */
f2e5a0c5 820function panels_export_display($display, $prefix = '') {
9380e8a5
EM
821 ctools_include('export');
822 $output = ctools_export_object('panels_display', $display, $prefix);
9f3609c9 823
f2e5a0c5
EM
824 $output .= $prefix . '$display->content = array()' . ";\n";
825 $output .= $prefix . '$display->panels = array()' . ";\n";
826 $panels = array();
827
828 if (!empty($display->content)) {
829 $pid_counter = 0;
830 $region_counters = array();
831 foreach ($display->content as $pane) {
832 $pane->pid = 'new-' . ++$pid_counter;
9380e8a5 833 $output .= ctools_export_object('panels_pane', $pane, $prefix . ' ');
f2e5a0c5
EM
834 $output .= "$prefix " . '$display->content[\'' . $pane->pid . '\'] = $pane' . ";\n";
835 if (!isset($region_counters[$pane->panel])) {
836 $region_counters[$pane->panel] = 0;
9f3609c9 837 }
f2e5a0c5 838 $output .= "$prefix " . '$display->panels[\'' . $pane->panel . '\'][' . $region_counters[$pane->panel]++ .'] = \'' . $pane->pid . "';\n";
9f3609c9
EM
839 }
840 }
f2e5a0c5 841 return $output;
9f3609c9
EM
842}
843
f2e5a0c5
EM
844/**
845 * Render a display by loading the content into an appropriate
846 * array and then passing through to panels_render_layout.
847 *
848 * if $incoming_content is NULL, default content will be applied. Use
849 * an empty string to indicate no content.
850 * @render
851 * @ingroup hook_invocations
852 */
853function panels_render_display(&$display) {
854 panels_load_include('display-render');
855 panels_load_include('plugins');
d75b98c7
EM
856 ctools_include('context');
857
858 if (!empty($display->context)) {
859 if ($form_context = ctools_context_get_form($display->context)) {
860 $form_context->form['#theme'] = 'panels_render_display_form';
861 $form_context->form['#display'] = &$display;
862 $form_context->form['#form_context_id'] = $form_context->id;
863 return drupal_render_form($form_context->form_id, $form_context->form);
864 }
865 }
f2e5a0c5 866 return _panels_render_display($display);
9f3609c9
EM
867}
868
869/**
d75b98c7
EM
870 * Theme function to render our panel as a form.
871 *
872 * When rendering a display as a form, the entire display needs to be
873 * inside the <form> tag so that the form can be spread across the
874 * panes. This sets up the form system to be the main caller and we
875 * then operate as a theme function of the form.
876 */
877function theme_panels_render_display_form($form) {
878 $form['#children'] = _panels_render_display($form['#display']);
879 drupal_render($form);
880 return theme('form', $form);
881}
882
d75b98c7 883/**
9f3609c9 884 * For external use: Given a layout ID and a $content array, return the
f2e5a0c5
EM
885 * panel display. The content array is filled in based upon the content
886 * available in the layout. If it's a two column with a content
887 * array defined like array('left' => t('Left side'), 'right' =>
888 * t('Right side')), then the $content array should be array('left' =>
889 * $output_left, 'right' => $output_right)
890 * @render
9f3609c9
EM
891 */
892function panels_print_layout($id, $content) {
f2e5a0c5
EM
893 panels_load_include('plugins');
894 return _panels_print_layout($id, $content);
9f3609c9
EM
895}
896
f2e5a0c5
EM
897// @layout
898function panels_print_layout_icon($id, $layout, $title = NULL) {
899 drupal_add_css(panels_get_path('css/panels_admin.css'));
fc8f8fa8 900 $file = $layout['path'] . '/' . $layout['icon'];
f2e5a0c5 901 return theme('panels_layout_icon', $id, theme('image', $file), $title);
9f3609c9
EM
902}
903
f2e5a0c5
EM
904/**
905 * Theme the layout icon image
906 * @layout
907 * @todo move to theme.inc
908 */
909function theme_panels_layout_icon($id, $image, $title = NULL) {
14996ee1 910 $output = '<div class="layout-icon">';
f2e5a0c5
EM
911 $output .= $image;
912 if ($title) {
913 $output .= '<div class="caption">' . $title . '</div>';
9f3609c9 914 }
f2e5a0c5
EM
915 $output .= '</div>';
916 return $output;
9f3609c9
EM
917}
918
919/**
f2e5a0c5
EM
920 * Theme the layout link image
921 * @layout
9f3609c9 922 */
f2e5a0c5 923function theme_panels_layout_link($title, $id, $image, $link) {
14996ee1 924 $output = '<div class="layout-link">';
f2e5a0c5
EM
925 $output .= $image;
926 $output .= '<div>' . $title . '</div>';
927 $output .= '</div>';
928 return $output;
9f3609c9 929}
aa3e4818
EM
930
931/**
932 * Print the layout link. Sends out to a theme function.
933 * @layout
934 */
a2329e43
EM
935function panels_print_layout_link($id, $layout, $link, $options = array()) {
936 if (isset($options['query']['q'])) {
937 unset($options['query']['q']);
938 }
939
aa3e4818
EM
940 drupal_add_css(panels_get_path('css/panels_admin.css'));
941 $file = $layout['path'] . '/' . $layout['icon'];
a2329e43
EM
942 $image = l(theme('image', $file), $link, array('html' => true) + $options);
943 $title = l($layout['title'], $link, $options);
aa3e4818
EM
944 return theme('panels_layout_link', $title, $id, $image, $link);
945}
946
b76f0bd4
SB
947/**
948 * Implementation of hook_ctools_plugin_directory() to let the system know
949 * we implement task and task_handler plugins.
950 */
b6a6c965 951function panels_ctools_plugin_directory($module, $plugin) {
2db8bc72
EM
952 // Safety: go away if CTools is not at an appropriate version.
953 if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
954 return;
955 }
6a04891d 956 if ($module == 'page_manager' || $module == 'panels') {
b76f0bd4
SB
957 return 'plugins/' . $plugin;
958 }
959}
050cfb22
EM
960
961/**
962 * Inform CTools that the layout plugin can be loaded from themes.
963 */
964function panels_ctools_plugin_layouts() {
965 return array(
966 'load themes' => TRUE,
967 );
968}
efcf7fcf
EM
969
970/**
971 * Inform CTools that the style plugin can be loaded from themes.
972 */
973function panels_ctools_plugin_styles() {
974 return array(
975 'load themes' => TRUE,
976 );
977}
978
2505a50c
EM
979/**
980 * Get the display that is currently being rendered as a page.
ee0e6fa9 981 *
2505a50c
EM
982 * Unlike in previous versions of this, this only returns the display,
983 * not the page itself, because there are a number of different ways
984 * to get to this point. It is hoped that the page data isn't needed
985 * at this point. If it turns out there is, we will do something else to
986 * get that functionality.
987 */
988function panels_get_current_page_display($change = NULL) {
989 static $display = NULL;
990 if ($change) {
991 $display = $change;
992 }
993
994 return $display;
995}
026b6377
EM
996
997/**
998 * Get display edit cache on behalf of panel context.
999 *
1000 * The key is the second half of the key in this form:
1001 * panel_context:TASK_NAME:HANDLER_NAME;
1002 */
1003function panel_context_panels_cache_get($key) {
1004 panels_load_include('common');
51ddcbf4 1005 ctools_include('context');
026b6377
EM
1006 ctools_include('context-task-handler');
1007 // this loads the panel context inc even if we don't use the plugin.
1008 $plugin = page_manager_get_task_handler('panel_context');
1009
1010 list($task_name, $handler_name) = explode(':', $key, 2);
1011 $page = page_manager_get_page_cache($task_name);
1012 if (isset($page->display_cache[$handler_name])) {
1013 return $page->display_cache[$handler_name];
1014 }
1015
51ddcbf4
EM
1016 if ($handler_name) {
1017 $handler = &$page->handlers[$handler_name];
1018 }
1019 else {
1020 $handler = &$page->new_handler;
1021 }
026b6377 1022 $cache = new stdClass();
40ab7380
EM
1023
1024 $cache->display = &panels_panel_context_get_display($handler);
026b6377
EM
1025 $cache->display->context = ctools_context_handler_get_all_contexts($page->task, $page->subtask, $handler);
1026 $cache->display->cache_key = 'panel_context:' . $key;
1027 $cache->content_types = panels_common_get_allowed_types('panels_page', $cache->display->context);
1028 $cache->display_title = TRUE;
1029
1030 return $cache;
1031}
1032
1033/**
1034 * Store a display edit in progress in the page cache.
1035 */
1036function panel_context_panels_cache_set($key, $cache) {
1037 list($task_name, $handler_name) = explode(':', $key, 2);
1038 $page = page_manager_get_page_cache($task_name);
1039 $page->display_cache[$handler_name] = $cache;
51ddcbf4
EM
1040 if ($handler_name) {
1041 $page->handlers[$handler_name]->conf['display'] = $cache->display;
1042 $page->handler_info[$handler_name]['changed'] |= PAGE_MANAGER_CHANGED_CACHED;
1043 }
1044 else {
1045 $page->new_handler->conf['display'] = $cache->display;
1046 }
026b6377
EM
1047 page_manager_set_page_cache($page);
1048}