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