4 * Implementation of hook_help()
6 function panels_help($section = '') {
8 case
'admin/modules#description':
9 return t('The panels module allows the creation of pages with flexible layouts.');
11 case
'admin/panels/list':
12 return t('<p>You may peruse a list of your current panels layouts and edit them, or click add to create a new page.</p>');
13 case
'admin/panels/add':
14 return t('<p>Choose a layout for your new page from the list below.</p>');
19 * Implementation of hook_perm()
21 function panels_perm() {
22 return array('create panels', 'access all panels');
26 * Determine if the specified user has access to a panel.
28 function panels_access($panel, $account = NULL
) {
34 // Administrator privileges
35 if (user_access('access all panels', $account)) {
39 // All views with an empty access setting are available to all roles.
40 if (!$panel->access
) {
44 // Otherwise, check roles
45 static
$roles = array();
46 if (!isset($roles[$account->uid
])) {
47 $roles[$account->uid
] = array_keys($account->roles
);
50 return array_intersect($panel->access
, $roles[$account->uid
]);
54 * Implementation of hook_menu()
56 function panels_menu($may_cache) {
58 $access = user_access('create panels');
60 'path' => 'admin/panels',
61 'title' => t('panels'),
63 'callback' => 'panels_list_page',
66 'path' => 'admin/panels/list',
69 'callback' => 'panels_list_page',
71 'type' => MENU_DEFAULT_LOCAL_TASK
,
74 'path' => 'admin/panels/add',
77 'callback' => 'panels_add_page',
78 'type' => MENU_LOCAL_TASK
,
81 'path' => 'admin/panels/add/layout',
84 'callback' => 'panels_add_layout_page',
85 'type' => MENU_LOCAL_TASK
,
88 'path' => 'admin/panels/edit',
89 'title' => t('edit panels'),
91 'callback' => 'panels_edit_page',
92 'type' => MENU_CALLBACK
,
95 'path' => 'admin/panels/delete',
96 'title' => t('delete panels'),
98 'callback' => 'panels_delete_page',
99 'type' => MENU_CALLBACK
,
103 'path' => 'panels/node/autocomplete',
104 'title' => t('autocomplete node'),
105 'callback' => 'panels_node_autocomplete',
106 'access' => user_access('access content'),
107 'type' => MENU_CALLBACK
110 // load panels from database
111 $result = db_query("SELECT * FROM {panels_info}");
112 // FIXME: Fow now we're making these all callbacks, but we
113 // should steal code from Views so they can be normal, tabs,
115 while ($panels = db_fetch_object($result)) {
116 $panels->access
= ($panels->access ?
explode(', ', $panels->access
) : array());
118 'path' => $panels->path
,
119 'title' => filter_xss_admin($panels->title
),
120 'access' => panels_access($panels),
121 'callback' => 'panels_panels_page',
122 'callback arguments' => array($panels->did
),
123 'type' => MENU_CALLBACK
131 * panels path helper function
133 function panels_get_file_path($module, $file, $base_path = true
) {
135 $output = base_path();
137 return $output .
drupal_get_path('module', $module) .
'/' .
$file;
140 // ---------------------------------------------------------------------------
141 // panels custom image button
144 * Custom form element to do our nice images.
146 function panels_elements() {
147 $type['panels_imagebutton'] = array('#input' => TRUE
, '#button_type' => 'submit',);
152 * Theme our image button.
154 function theme_panels_imagebutton($element) {
155 return '<input type="image" class="form-'.
$element['#button_type'] .
'" name="'.
$element['#name'] .
'" value="'.
check_plain($element['#default_value']) .
'" '.
drupal_attributes($element['#attributes']) .
' src="' .
$element['#image'] .
'" alt="' .
$element['#title'] .
'" title="' .
$element['#title'] .
"\" />\n";
158 function panels_imagebutton_value() {
159 // null function guarantees default_value doesn't get moved to #value.
163 * Add a single button to a form.
165 function panels_add_button($image, $name, $text) {
166 $module_path = base_path() .
drupal_get_path('module', 'panels');
169 '#type' => 'panels_imagebutton',
170 '#image' => $module_path .
'/images/' .
$image,
172 '#default_value' => $name,
177 * Set a button to a blank image -- used for placeholders when buttons are
178 * not relevant but just removing it would be visually unappealing.
180 function panels_set_blank(&$form) {
181 $form['#type'] = 'markup';
182 $form['#value'] = theme('image', drupal_get_path('module', 'panels') .
'/images/blank.gif');
185 // ---------------------------------------------------------------------------
186 // panels administrative pages
189 * Provide a list of panels, with links to edit or delete them.
191 function panels_list_page() {
192 $result = db_query("SELECT * FROM {panels_info} ORDER BY title");
193 while ($panels = db_fetch_object($result)) {
195 $item[] = check_plain($panels->title
);
196 $item[] = l($panels->path
, $panels->path
);
197 $item[] = implode(' | ', array(
198 l('edit', "admin/panels/edit/$panels->did"),
199 l('delete', "admin/panels/delete/$panels->did"),
208 $output = theme('table', $header, $items);
213 * Provide a form to confirm deletion of a panel page.
215 function panels_delete_page($did = '') {
216 $panels = panels_load_panels($did);
219 return 'admin/panels';
222 $form['did'] = array('#type' => 'value', '#value' => $panels->did
);
223 return confirm_form('panels_delete_confirm', $form,
224 t('Are you sure you want to delete %title?', array('%title' => $panels->title
)),
225 $_GET['destination'] ?
$_GET['destination'] : 'admin/panels',
226 t('This action cannot be undone.'),
227 t('Delete'), t('Cancel')
232 * Handle the submit button to delete a panel page.
234 function panels_delete_confirm_submit($formid, $form) {
235 if ($form['confirm']) {
236 panels_delete_panels((object) $form);
237 return 'admin/panels';
242 * Handle the add panels page
244 function panels_add_page($layout = NULL
) {
245 $layouts = panels_get_layouts();
246 theme_add_style(drupal_get_path('module', 'panels') .
'/panels_admin.css');
248 foreach ($layouts as
$id => $layout) {
250 // grab the first one for our default.
253 $file = panels_get_file_path($layout['module'], $layout['icon'], false
);
254 $output .
= theme('panels_add_image', $layout[title
], $id, l(theme('image', $file), $_GET['q'] .
'/' .
$id, NULL
, NULL
, NULL
, NULL
, TRUE
));
259 if (!$layouts[$layout]) {
260 return drupal_not_found();
263 $panels->layout
= $layout;
264 return panels_edit_form($panels);
267 function theme_panels_add_image($title, $id, $image) {
268 $output .
= '<div class="layout-link">';
270 $output .
= '<div>' .
l($title, $_GET['q'] .
'/' .
$id) .
'</div>';
274 // ---------------------------------------------------------------------------
275 // panels administrative pages
277 function panels_edit_page($did = NULL
) {
278 if (!$did || !($panels = panels_load_panels($did))) {
279 return drupal_not_found();
281 return panels_edit_form($panels);
285 * shortcut to ease the syntax of the various form builder tricks we use.
287 function panels_form_builder(&$form, $form_id = 'panels_edit_form') {
288 $form = form_builder($form_id, $form);
292 * Edit an already loaded panels.
294 function panels_edit_form($panels) {
295 theme_add_style(drupal_get_path('module', 'panels') .
'/panels_admin.css');
296 $layouts = panels_get_layouts();
297 $layout = $layouts[$panels->layout
];
299 $content_types = panels_get_content_types();
301 // Process all our add button stuff first so we can add stuff to the
302 // form semi dynamically.
304 $form['add'] = array(
305 '#type' => 'fieldset',
306 '#title' => t('Add content'),
307 '#collapsible' => false
,
308 '#description' => t('Select an area to add content to, then select a type of content and click the appropriate button. The content will be added.'),
311 // Drop the array keys into a temporary in order to protect references.
312 $temp = array_keys($layout['content areas']);
313 $default_radio = array_shift($temp);
315 $form['add']['area'] = array(
317 '#title' => t('Area'),
318 '#options' => $layout['content areas'],
319 '#prefix' => '<div class="container-inline">',
320 '#suffix' => '</div>',
321 '#default_value' => $default_radio,
323 foreach ($content_types as
$id => $type) {
324 $function = $type['admin'];
325 if (function_exists($function)) {
327 $form['add'][$id] = $function('add button', $dummy);
329 // $dummy needed for cause you can't have default args on a reference.
330 $form['add'][$id]['#parents'] = array('add', $id);
331 $form['add'][$id]['#tree'] = true
;
332 panels_form_builder($form['add'][$id]);
334 if ($conf = $function('add', $form_values['add'][$id])) {
335 $add->configuration
= $conf;
337 $form['add']['area']['#parents'] = array('area');
338 panels_form_builder($form['add']['area']);
339 $add->area
= $form_values['area'];
344 $form['layout'] = array(
346 '#value' => $panels->layout
349 $form['did'] = array(
351 '#value' => $panels->did
,
354 $form['info'] = array(
355 '#type' => 'fieldset',
356 '#title' => t('General information'),
357 '#collapsible' => false
,
360 $file = panels_get_file_path($layout['module'], $layout['icon'], false
);
361 $icon .
= theme('image', $file);
362 $form['info']['layout-icon'] = array(
363 '#value' => '<div class="layout-icon">' .
$icon .
'</div>',
366 $form['info']['layout-display'] = array(
367 '#value' => '<strong>Layout</strong>: ' .
$layout['title'],
370 $form['info']['title'] = array(
371 '#type' => 'textfield',
372 '#default_value' => $panels->title
,
373 '#title' => t('Page title'),
374 '#description' => t('The page title for this panels layout'),
377 $form['info']['css_id'] = array(
378 '#type' => 'textfield',
379 '#default_value' => $panels->css_id
,
380 '#title' => t('CSS ID'),
381 '#description' => t('The CSS ID to apply to this page'),
385 $result = db_query("SELECT r.rid, r.name FROM {role} r ORDER BY r.name");
386 while ($obj = db_fetch_object($result)) {
387 $rids[$obj->rid
] = $obj->name
;
390 $form['info']['access'] = array(
391 '#type' => 'checkboxes',
392 '#title' => t('Access'),
393 '#default_value' => $panels->access
,
395 '#description' => t('Only the checked roles will be able to see this panel in any form; if no roles are checked, access will not be restricted.'),
398 $form['info']['path'] = array(
399 '#type' => 'textfield',
400 '#default_value' => $panels->path
,
401 '#title' => t('Path'),
402 '#description' => t('The URL path to give this page, i.e, path/to/page'),
406 $form['content'] = array(
410 // Go through our content areas and display what we have.
411 foreach ($layout['content areas'] as
$area => $title) {
413 $form['content'][$area] = array(
417 // Construct the order, feeding it the default order for what
418 // we know about. When we pull it back out, it may well be
419 // different due to past submits.
421 if (is_array($panels->content
[$area])) {
422 $order = array_keys($panels->content
[$area]);
424 $form['content'][$area]['order'] = array(
426 '#default_value' => serialize($order),
427 '#parents' => array('content', $area, 'order'),
430 // If an add button has added an item to the area, put it in and update
432 panels_form_builder($form['content'][$area]['order']);
433 $order = unserialize($form['content'][$area]['order']['#value']);
434 if ($add->area
== $area) {
435 // say THIS 5 times real fast
436 if ($panels->content
[$area] && $order) {
437 $position = max(max(array_keys($order)), max(array_keys($panels->content
[$area]))) + 1;
440 $position = max(array_keys($order)) + 1;
446 $panels->content
[$area][$position] = $add;
447 $order[] = $position;
448 $form['content'][$area]['order']['#value'] = serialize($order);
451 // Go through each item in the area and render it.
452 $count = count($order);
453 foreach ($order as
$position => $id) {
454 // place buttons to re-order content.
455 $form['content'][$area][$id]['buttons'] = array(
456 '#parents' => array('content', $area, $id, 'buttons'),
459 panels_add_buttons($form['content'][$area][$id]['buttons'], $count, $position);
461 // figure out if one of those buttons was pressed
462 panels_form_builder($form['content'][$area][$id]['buttons']);
464 foreach ($GLOBALS['form_values']['content'][$area][$id]['buttons'] as
$button => $value) {
466 $function = 'panels_move_' .
$button;
467 $function($order, $position);
468 $form['content'][$area]['order']['#value'] = serialize($order);
469 if ($button == 'delete')
473 // If a content item was deleted, it still has buttons. Get rid of them.
474 // It had buttons because we needed to give it buttons to see if its
475 // buttons were clicked.
477 unset($form['content'][$area][$id]['buttons']);
480 // we finally get to add the conent item's content to the form.
481 $area_record = $panels->content
[$area][$id];
482 $form['content'][$area][$id]['type'] = array(
484 '#default_value' => $area_record->type
,
485 '#parents' => array('content', $area, $id, 'type'),
487 // retrieve what was already there -- so we can retain edits and
488 // content items that were added.
489 panels_form_builder($form['content'][$area][$id]['type']);
490 $type = $form['content'][$area][$id]['type']['#value'];
491 $function = $content_types[$type]['admin'];
492 if (function_exists($function)) {
495 '#parents' => array('content', $area, $id, 'configuration'),
497 $form['content'][$area][$id]['configuration'] = array_merge($array, $function('edit', $area_record->configuration
, array('content', $area, $id, 'configuration')));
498 panels_form_builder($form['content'][$area][$id]['configuration']);
504 $form['submit'] = array(
506 '#value' => t('Save'),
509 return drupal_get_form('panels_edit_form', $form);
513 * Display the form to edit a panels.
515 function theme_panels_edit_form($form) {
516 $layouts = panels_get_layouts();
517 $layout = $layouts[$form['layout']['#value']];
519 $content_types = panels_get_content_types();
521 $output .
= form_render($form['info']);
522 foreach ($layout['content areas'] as
$area => $title) {
523 $order = unserialize($form['content'][$area]['order']['#value']);
525 $area_content = t('This area has no content.');
529 $count = count($order);
530 foreach ($order as
$position => $id) {
532 if ($position == 0 ) {
533 panels_set_blank($form['content'][$area][$id]['buttons']['up']);
535 else if ($position == ($count - 1)) {
536 panels_set_blank($form['content'][$area][$id]['buttons']['down']);
539 $type = $form['content'][$area][$id]['type']['#value'];
540 $function = $content_types[$type]['admin'];
541 if (function_exists($function)) {
542 // figure out the actual values; using the global because we need it
543 // to be in the same format it'll be in 'submit'.
545 $conf_form = $form_values['content'][$area][$id]['configuration'];
546 $conf = $function('save', $conf_form);
548 '#title' => t('Configure'),
549 '#children' => form_render($form['content'][$area][$id]['configuration']),
550 '#collapsible' => true
,
553 $buttons = form_render($form['content'][$area][$id]['buttons']);
554 $area_content .
= $buttons .
' ' .
$function('list', $conf) .
555 theme('fieldset', $fieldset) /* . '<br />' */;
559 $content[$area] = theme('fieldset', array('#title' => check_plain($title), '#value' => $area_content));
562 $output .
= panels_get_layout($layout, $content);
564 $output .
= form_render($form);
568 function panels_edit_form_validate($form_id, $form_values, $form) {
569 $content_types = panels_get_content_types();
570 foreach ($form_values['content'] as
$area => $content) {
571 foreach ($content as
$id => $item) {
572 if (is_numeric($id)) {
573 $function = $content_types[$item['type']]['admin'];
574 if (function_exists($function)) {
575 $function('validate', $item['configuration'], $form['content'][$area][$id]['configuration']);
582 function panels_edit_form_submit($form_id, $form_values) {
583 $panels = (object) $form_values;
584 // be sure we get the order right.
585 foreach ($form_values['content'] as
$area => $content) {
587 $order = unserialize($content['order']);
588 if (is_array($order)) {
589 foreach($order as
$id) {
590 $array[] = $content[$id];
593 $panels->content
[$area] = $array;
595 $panels->access
= array_keys(array_filter($panels->access
));
596 panels_save_panels($panels);
597 drupal_set_message(t('The panels has been saved.'));
598 return 'admin/panels';
602 * add the buttons to a content item
604 function panels_add_buttons(&$form, $count, $position) {
605 $form['delete'] = panels_add_button('user-trash.png', 'delete', t('Delete this item'));
606 // Leaving these in but commented out as I'm not convinced we don't want them.
608 // $form['top'] = panels_add_button('go-top.png', 'top', t('Move item to top'));
611 $form['up'] = panels_add_button('go-up.png', 'up', t('Move item up'));
612 $form['down'] = panels_add_button('go-down.png', 'down', t('Move item down'));
615 // $form['bottom'] = panels_add_button('go-bottom.png', 'bottom', t('Move item to bottom'));
618 // $form['spacer'] = panels_add_blank();
624 * move an item in an array to the top
626 function panels_move_top(&$array, &$position) {
627 $value = $array[$position];
628 unset($array[$position]);
629 array_unshift($array, $value);
630 // reindex the array now
631 $array = array_values($array);
635 * move an item in an array to the bottom
637 function panels_move_bottom(&$array, &$position) {
638 $value = $array[$position];
639 unset($array[$position]);
641 // reindex the array now
642 $array = array_values($array);
646 * move an item in an array up one position
648 function panels_move_up(&$array, &$position) {
649 $value = $array[$position];
650 $array[$position] = $array[$position - 1];
651 $array[$position - 1] = $value;
655 * move an item in an array up one position
657 function panels_move_down(&$array, &$position) {
658 $value = $array[$position];
659 $array[$position] = $array[$position + 1];
660 $array[$position + 1] = $value;
664 * Remove an item from an array
666 function panels_move_delete(&$array, &$position) {
667 unset($array[$position]);
668 // reindex the array now
669 $array = array_values($array);
672 // ---------------------------------------------------------------------------
673 // panels database functions
675 function panels_load_panels($did) {
676 $panels = db_fetch_object(db_query("SELECT * FROM {panels_info} WHERE did = %d", $did));
680 $panels->content
= array();
681 $panels->access
= ($panels->access ?
explode(', ', $panels->access
) : array());
683 $result = db_query("SELECT * FROM {panels_area} WHERE did = %d ORDER BY area, position", $did);
684 while ($area = db_fetch_object($result)) {
685 $area->configuration
= unserialize($area->configuration
);
686 $panels->content
[$area->area
][] = $area;
691 function panels_save_panels($panels) {
692 $panels->access
= implode(', ', $panels->access
);
694 db_query("UPDATE {panels_info} SET title = '%s', access = '%s', path = '%s', css_id = '%s', layout = '%s' WHERE did = %d", $panels->title
, $panels->access
, $panels->path
, $panels->css_id
, $panels->layout
, $panels->did
);
695 db_query("DELETE FROM {panels_area} WHERE did = %d", $panels->did
);
698 $panels->did
= db_next_id("{panels_info_id}");
699 // Put this in the form so modules can utilize the did of a new panel.
700 $GLOBALS['form_values']['did'] = $panels->did
;
701 db_query("INSERT INTO {panels_info} (did, title, access, path, css_id, layout) VALUES (%d, '%s', '%s', '%s', '%s', '%s')", $panels->did
, $panels->title
, $panels->access
, $panels->path
, $panels->css_id
, $panels->layout
);
703 foreach ($panels->content as
$area => $info) {
704 foreach ($info as
$position => $block) {
705 if (is_numeric($position)) { // don't save some random form stuff that may've been here.
706 $block = (object) $block;
707 db_query("INSERT INTO {panels_area} (did, area, type, configuration, position) VALUES(%d, '%s', '%s', '%s', %d)", $panels->did
, $area, $block->type
, serialize($block->configuration
), $position);
711 cache_clear_all('menu:', TRUE
);
714 function panels_delete_panels($panels) {
715 db_query("DELETE FROM {panels_info} WHERE did = %d", $panels->did
);
716 db_query("DELETE FROM {panels_area} WHERE did = %d", $panels->did
);
717 cache_clear_all('menu:', TRUE
);
719 // ---------------------------------------------------------------------------
723 * Returns TRUE if the current page contains a panels layout.
724 * This can be checked in a theme to hide existing sidebars on panel pages, for example.
726 * @param $set (optional) used internally to set the page status
728 function panels_is_panels_page($set_panels = NULL
) {
730 if ($set_panels == TRUE
) {
731 $is_panels = $set_panels;
736 function panels_panels_page($did) {
737 $panels = panels_load_panels($did);
739 return drupal_not_found();
742 $layouts = panels_get_layouts();
743 $layout = $layouts[$panels->layout
];
744 $layout['css_id'] = $panels->css_id
;
747 watchdog('panels', t('Unable to find requested layout %s', array('%s' => check_plain($panels->layout
))));
748 return drupal_not_found();
751 panels_is_panels_page(TRUE
);
752 $content_types = panels_get_content_types();
754 foreach ($panels->content as
$location => $list) {
755 foreach ($list as
$area) {
756 $function = $content_types[$area->type
]['callback'];
757 if (function_exists($function)) {
758 $content[$area->area
] .
= $function($area->configuration
);
762 $output = panels_get_layout($layout, $content);
763 drupal_set_title(filter_xss_admin($panels->title
));
767 function panels_get_layout($layout, $content) {
768 $output = theme($layout['theme'], check_plain($layout['css_id']), $content);
771 if (file_exists(path_to_theme() .
'/' .
$layout['css'])) {
772 theme_add_style(path_to_theme() .
'/' .
$layout['css']);
775 theme_add_style(drupal_get_path('module', $layout['module']) .
'/' .
$layout['css']);
782 * For external use: Given a layout ID and a $content array, return the
785 function panels_print_layout($id, $content) {
786 $layouts = panels_get_layouts();
787 $layout = $layouts[$id];
792 return panels_get_layout($layout, $content);
795 // ---------------------------------------------------------------------------
796 // panels data loading
798 function panels_load_includes($directory, $callback) {
799 // Load all our module 'on behalfs'.
800 $path = drupal_get_path('module', 'panels') .
'/' .
$directory;
801 $files = system_listing('.inc$', $path, 'name', 0);
803 foreach($files as
$file) {
804 require_once('./' .
$file->filename
);
806 $output = module_invoke_all($callback);
807 foreach ($files as
$file) {
808 $function = 'panels_' .
$file->name .
'_' .
$callback;
809 if (function_exists($function)) {
810 $result = $function();
811 if (isset($result) && is_array($result)) {
812 $output = array_merge($output, $result);
819 function panels_get_layouts() {
820 static
$layout = NULL
;
822 $layouts = panels_load_includes('layouts', 'panels_layouts');
827 function panels_get_content_types() {
828 static
$layout = NULL
;
830 $layouts = panels_load_includes('content_types', 'panels_content_types');
836 * Helper function for autocompletion of node titles.
837 * This is mostly stolen from clipper.
839 function panels_node_autocomplete($string) {
840 if ($string != '') { // if there are node_types passed, we'll use those in a MySQL IN query.
841 $result = db_query_range(db_rewrite_sql('SELECT n.title, u.name FROM {node} n INNER JOIN {users} u ON u.uid = n.uid WHERE LOWER(title) LIKE LOWER("%%%s%%")'), $string, 0, 10);
842 $prefix = count($array) ?
implode(', ', $array) .
', ' : '';
845 while ($node = db_fetch_object($result)) {
847 // Commas and quotes in terms are special cases, so encode 'em.
848 if (preg_match('/,/', $node->title
) || preg_match('/"/', $node->title
)) {
849 $n = '"'.
preg_replace('/"/', '""', $node->title
) .
'"';
851 $matches[$prefix .
$n] = '<span class="autocomplete_title">'.
check_plain($node->title
) .
'</span> <span class="autocomplete_user">('.
t('by %user', array('%user' => check_plain($node->name
))) .
')</span>';
853 print drupal_to_js($matches);