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