D6 FAPI compliance updates
[project/panels.git] / includes / common.inc
CommitLineData
db205271
EM
1<?php
2// $Id$
3
4
5/**
6 * @file
7 * Functions used by more than one panels client module.
8 */
9
10/**
11 * Class definition for the allowed layouts governing structure.
12 *
13 * @ingroup mainapi
14 *
15 * This class is designed to handle panels allowed layouts data from start to finish, and sees
16 * action at two times:\n
17 * - When a client module wants to generate a form allowing an admin to create or edit a set
18 * of allowed layouts. In this case, either a new panels_allowed_layouts object is created
19 * or one is retrieved from storage and panels_allowed_layouts::set_allowed() is called to
20 * generate the allowed layouts form. \n
21 * - When a client module is calling panels_edit_layout(), a saved instantiation of this object
22 * can be called up and passed in to the fourth parameter, and only the allowed layouts saved
23 * in that object will be displayed on the form. \n
24 * Because the panels API does not impose a data structure on the allowed_layouts data, client
25 * modules can create as many of these objects as they want, and organize them around any concept:
26 * node types, date published, author roles...anything.
27 *
28 * To call the settings form, instantiate this class - or, if your client module's needs are
29 * heavy-duty, extend this class and instantiate your subclass - assign values to any relevant
30 * desired members, and call panels_allowed_layouts::set_allowed(). See the documentation on
31 * that method for a sample implementation.
32 *
33 * Note that when unserializing saved tokens of this class, you must
34 * run panels_load_include('common') before unserializing in order to ensure
35 * that the object is properly loaded.
36 *
37 * Client modules extending this class should implement a save() method and use it for
38 * their custom data storage routine. You'll need to rewrite other class methods if
39 * you choose to go another route.
40 *
41 * @see panels_edit_layout()
42 * @see _panels_edit_layout()
43 *
44 */
45class panels_allowed_layouts {
46
47 /**
48 * Specifies whether newly-added layouts (as in, new .inc files) should be automatically
49 * allowed (TRUE) or disallowed (FALSE) for $this. Defaults to TRUE, which is more
50 * permissive but less of an administrative hassle if/when you add new layouts. Note
51 * that this parameter will be derived from $allowed_layouts if a value is passed in.
52 */
53 var $allow_new = TRUE;
54
55 /**
56 * Optional member. If provided, the Panels API will generate a drupal variable using
57 * variable_set($module_name . 'allowed_layouts', serialize($this)), thereby handling the
58 * storage of this object entirely within the Panels API. This object will be
59 * called and rebuilt by panels_edit_layout() if the same $module_name string is passed in
60 * for the $allowed_types parameter. \n
61 * This is primarily intended for convenience - client modules doing heavy-duty implementations
62 * of the Panels API will probably want to create their own storage method.
63 * @see panels_edit_layout()
64 */
65 var $module_name = NULL;
66
67 /**
68 * An associative array of all available layouts, keyed by layout name (as defined
69 * in the corresponding layout plugin definition), with value = 1 if the layout is
70 * allowed, and value = 0 if the layout is not allowed.
71 * Calling array_filter(panels_allowed_layouts::$allowed_layout_settings) will return an associative array
72 * containing only the allowed layouts, and wrapping that in array_keys() will
73 * return an indexed version of that array.
74 */
75 var $allowed_layout_settings = array();
76
77 /**
78 * Hack-imitation of D6's $form_state. Used by the panels_common_set_allowed_types()
79 * form to indicate whether the returned value is in its 'render', 'failed-validate',
80 * or 'submit' stage.
81 */
82 var $form_state;
83
84 /**
85 * Constructor function; loads the $allowed_layout_settings array with initial values according
86 * to $start_allowed
87 *
88 * @param bool $start_allowed
89 * $start_allowed determines whether all available layouts will be marked
90 * as allowed or not allowed on the initial call to panels_allowed_layouts::set_allowed()
91 *
92 */
93 function panels_allowed_layouts($start_allowed = TRUE) {
94 // TODO would be nice if there was a way to just fetch the names easily
95 foreach ($this->list_layouts() as $layout_name) {
96 $this->allowed_layout_settings[$layout_name] = $start_allowed ? 1 : 0;
97 }
98 }
99
100 /**
101 * Manage panels_common_set_allowed_layouts(), the FAPI code for selecting allowed layouts.
102 *
103 * MAKE SURE to set panels_allowed_layouts::allow_new before calling this method. If you want the panels API
104 * to handle saving these allowed layout settings, panels_allowed_layouts::module_name must also be set.
105 *
106 * Below is a sample implementation; refer to the rest of the class documentation to understand all the
107 * specific pieces. Values that are intended to be replaced are wrapped with <>.
108 *
109 * \n @code
110 * function docdemo_allowed_layouts() {
111 * panels_load_include('common');
112 * if (!is_a($allowed_layouts = unserialize(variable_get('panels_common_allowed_layouts', serialize(''))), 'panels_allowed_layouts')) {
113 * $allowed_layouts = new panels_allowed_layouts();
114 * $allowed_layouts->allow_new = TRUE;
115 * $allowed_layouts->module_name = '<client_module_name>';
116 * }
117 * $result = $allowed_layouts->set_allowed('<Desired client module form title>');
118 * if (in_array($allowed_layouts->form_state, array('failed-validate', 'render'))) {
119 * return $result;
120 * }
121 * elseif ($allowed_layouts->form_state == 'submit') {
122 * drupal_goto('</path/to/desired/redirect>');
123 * }
124 * }
125 * @endcode \n
126 *
127 * If $allowed_layouts->form_state == 'failed-validate' || 'render', then you'll need to return
128 * $result as it contains the structured form HTML generated by drupal_render_form() and is ready
129 * to be passed through index.php's call to theme('page', ...).
130 *
131 * However, if $allowed_layouts->form_state == 'submit', then the form has been submitted and we should
132 * react. It's really up to your client module how you handle the rest; panels_allowed_layouts::save() (or
133 * panels_allowed_layouts::api_save(), if that's the route you're going) will have already been called,
134 * so if those methods handle your save routine, then all there is left to do is handle redirects, if you
135 * want. The current implementation of the allowed layouts form currently never redirects, so it's up to
136 * you to control where the user ends up next.
137 *
138 * @param string $title
139 * Used to set the title of the allowed layouts form. If no value is given, defaults to
140 * 'Panels: Allowed Layouts'.
141 *
142 * @return mixed $result
143 * - On the first passthrough when the form is being rendered, $result is the form's structured
144 * HTML, ready to be pushed to the screen with a call to theme('page', ...).
145 * - A successful second passthrough indicates a successful submit, and
146 * $result === panels_allowed_layouts::allowed_layout_settings. Returning it is simply for convenience.
147 */
148 function set_allowed($title = 'Panels: Allowed Layouts') {
149 $this->sync_with_available();
150 $form_id = 'panels_common_set_allowed_layouts';
4f318c06 151 // TODO switch to drupal_build_form(); need to pass by ref
db205271
EM
152 $form = drupal_retrieve_form($form_id, $this, $title);
153
154 if ($result = drupal_process_form($form_id, $form)) {
155 // successful submit
156 $this->form_state = 'submit';
157 return $result;
158 }
159 $this->form_state = isset($_POST['op']) ? 'failed-validate' : 'render';
160 $result = drupal_render_form($form_id, $form);
161 return $result;
162 }
163
164 /**
165 * Checks for newly-added layouts and deleted layouts. If any are found, updates panels_allowed_layouts::allowed_layout_settings;
166 * new additions are made according to panels_allowed_layouts::allow_new, while deletions are unset().
167 *
168 * Note that any changes made by this function are not saved in any permanent location.
169 */
170 function sync_with_available() {
171 $layouts = $this->list_layouts();
172 foreach (array_diff($layouts, array_keys($this->allowed_layout_settings)) as $new_layout) {
173 $this->allowed_layout_settings[$new_layout] = $this->allow_new ? 1 : 0;
174 }
175 foreach (array_diff(array_keys($this->allowed_layout_settings), $layouts) as $deleted_layout) {
176 unset($this->allowed_layout_settings[$deleted_layout]);
177 }
178 }
179
180 /**
181 * Use panels_allowed_layouts::module_name to generate a variable for variable_set(), in which
182 * a serialized version of $this will be stored.
183 *
184 * Does nothing if panels_allowed_layouts::module_name is not set.
185 *
186 * IMPORTANT NOTE: if you use variable_get() in a custom client module save() method, you MUST
187 * wrap $this in serialize(), then unserialize() what you get from variable_get(). Failure to
188 * do so will result in an incomplete object. The following code will work:
189 * @code
190 * $allowed_layouts = unserialize(variable_get('your_variable_name', serialize(''));
191 * @endcode
192 *
193 * If you don't serialize the second parameter of variable_get() and the variable name you provide
194 * can't be found, an E_STRICT warning will be generated for trying to unserialize an entity
195 * that has not been serialized.
196 *
197 */
198 function api_save() {
199 if (!is_null($this->module_name)) {
200 variable_set($this->module_name . "_allowed_layouts", serialize($this));
201 }
202 }
203
204 /**
205 * Snag a list of the current layouts for internal use.
206 *
207 * Data is not saved in a class member in order to ensure that it's
208 * fresh.
209 *
210 * @return array $layouts
211 * An indexed array of the system names for all currently available layouts.
212 */
213 function list_layouts() {
214 static $layouts = array();
215 if (empty($layouts)) {
216 panels_load_include('plugins');
217 $layouts = array_keys(panels_get_layouts());
218 }
219 return $layouts;
220 }
221}
222
223/**
224 * A common settings page for Panels modules, because this code is relevant to
225 * any modules that don't already have special requirements.
226 */
941ed792 227function panels_common_settings(&$form_state, $module_name = 'panels_common') {
db205271
EM
228 panels_load_include('plugins');
229 $content_types = panels_get_content_types();
230 $default_types = variable_get($module_name . '_default', NULL);
231 if (!isset($default_types)) {
232 $default_types = array('block' => TRUE, 'views' => TRUE, 'other' => TRUE);
233 $skip = TRUE;
234 }
235
236 foreach ($content_types as $id => $info) {
237 if (empty($info['single'])) {
238 $default_options[$id] = t('New @s', array('@s' => $info['title']));
239 }
240 }
241
242 $default_options['other'] = t('New content of other types');
243 $form['panels_common_default'] = array(
244 '#type' => 'checkboxes',
245 '#title' => t('New content behavior'),
246 '#description' => t('Select the default behavior of new content added to the system. If checked, new content will automatically be immediately available to be added to Panels pages. If not checked, new content will not be available until specifically allowed here.'),
247 '#options' => $default_options,
248 '#default_value' => array_keys(array_filter($default_types)),
249 );
250
251 if ($skip) {
252 $form['markup'] = array('#value' => t('<p>Click Submit to be presented with a complete list of available content types set to the defaults you selected.</p>'));
253 $form['skip'] = array('#type' => 'value', '#value' => TRUE);
254 }
255 else {
256 // Rebuild the entire list, setting appropriately from defaults. Give
257 // each type its own checkboxes set unless it's 'single' in which
258 // case it can go into our fake other set.
259 $available_content_types = panels_get_all_content_types();
260 $allowed_content_types = variable_get($module_name . '_allowed_types', array());
261
262 foreach ($available_content_types as $id => $types) {
263 foreach ($types as $type => $info) {
264 $key = $id . '-' . $type;
265 $checkboxes = empty($content_types[$id]['single']) ? $id : 'other';
266 $options[$checkboxes][$key] = $info['title'];
267 if (!isset($allowed_content_types[$key])) {
268 $allowed[$checkboxes][$key] = isset($default_types[$id]) ? $default_types[$id] : $default_types['other'];
269 }
270 else {
271 $allowed[$checkboxes][$key] = $allowed_content_types[$key];
272 }
273 }
274 }
275
941ed792
EM
276 $form['content_types'] = array(
277 '#tree' => TRUE,
278 '#prefix' => '<div class="clear-block">',
279 '#suffix' => '</div>',
280 );
db205271
EM
281 // cheat a bit
282 $content_types['other'] = array('title' => t('Other'), 'weight' => 10);
283 foreach ($content_types as $id => $info) {
284 if (isset($allowed[$id])) {
285 $form['content_types'][$id] = array(
286 '#prefix' => '<div class="panels-page-type-container">',
287 '#suffix' => '</div>',
288 '#type' => 'checkboxes',
289 '#title' => t('Allowed @s content', array('@s' => $info['title'])),
290 '#options' => $options[$id],
291 '#default_value' => array_keys(array_filter($allowed[$id])),
292 );
293 }
294 }
295 }
296
297 $form['module_name'] = array(
298 '#type' => 'value',
299 '#value' => $module_name,
300 );
301
302 $form['submit'] = array(
303 '#type' => 'submit',
4f318c06 304 '#value' => t('Save'),
db205271
EM
305 );
306
307 drupal_add_css(panels_get_path('css/panels_page.css'));
308 return $form;
309}
310
311/**
312 * Submit hook for panels_common_settings
313 */
941ed792
EM
314function panels_common_settings_submit($form, &$form_state) {
315 $module_name = $form_state['values']['module_name'];
316 variable_set($module_name . '_default', $form_state['values']['panels_common_default']);
317 if (!$form_state['values']['skip']) {
db205271 318 // merge the broken apart array neatly back together
941ed792 319 variable_set($module_name . '_allowed_types', call_user_func_array('array_merge', $form_state['values']['content_types']));
db205271
EM
320 }
321 drupal_set_message(t('Your changes have been saved.'));
322}
323
324/**
325 * Based upon the settings, get the allowed types for this node.
326 */
327function panels_common_get_allowed_types($module, $contexts = array(), $has_content = FALSE, $default_defaults = array(), $default_allowed_types = array()) {
328
329 // Get a list of all types that are available
330
331 $default_types = variable_get($module . '_defaults', $default_defaults);
332 $allowed_types = variable_get($module . '_allowed_types', $default_allowed_types);
333
334 // By default, if they haven't gone and done the initial setup here,
335 // let all 'other' types (which will be all types) be available.
336 if (!isset($default_types['other'])) {
337 $default_types['other'] = TRUE;
338 }
339
340 panels_load_include('plugins');
341 $content_types = panels_get_available_content_types($contexts, $has_content, $allowed_types, $default_types);
342
343 return $content_types;
344}
345
346/**
347 * The FAPI code for generating an 'allowed layouts' selection form.
348 *
349 * NOTE: Because the Panels API does not guarantee a particular method of storing the data on allowed layouts,
350 * it is not_possible for the Panels API to implement any checks that determine whether reductions in
351 * the set of allowed layouts conflict with pre-existing layout selections. $displays in that category
352 * will continue to function with their current layout as normal until the user/owner/admin attempts
353 * to change layouts on that display, at which point they will have to select from the new set of
354 * allowed layouts. If this is not the desired behavior for your client module, it's up to you to
355 * write a validation routine that determines what should be done with conflicting layouts.
356 *
357 * Remember that changing layouts where panes have already been created can result in data loss;
358 * consult panels_change_layout() to see how the Panels API handles that process. Running
359 * drupal_execute('panels_change_layout', ...) is one possible starting point.
360 *
361 * @ingroup forms
362 *
363 * @param array $allowed_layouts
364 * The set of allowed layouts that should be used as the default values
365 * for this form. If none is provided, then by default no layouts will be restricted.
366 * @param string $title
367 * The title that will be used for the form. Defaults to 'Panels: Allowed Layouts' if
368 * no value was provided in panels_allowed_layouts::set_allowed.
369 */
370// TODO need to add something that handles $finish & $destination-type stuff.
4f318c06 371function panels_common_set_allowed_layouts(&$form_state, $allowed_layouts, $title) {
db205271
EM
372 $layouts = panels_get_layouts();
373 foreach ($layouts as $id => $layout) {
374 $options[$id] = panels_print_layout_icon($id, $layout, check_plain($layout['title']));
375 }
376
377 drupal_set_title($title);
378
4f318c06
SB
379 $form_state['allowed_layouts'] = &$allowed_layouts;
380
db205271
EM
381 $form['variables'] = array('#type' => 'value', '#value' => array($allowed_layouts));
382
383 drupal_add_js(panels_get_path('js/layout.js'));
384 $form['layouts'] = array(
385 '#type' => 'checkboxes',
386 '#title' => t('Select allowed layouts'),
387 '#options' => $options,
388 '#description' => t('Check the boxes for all layouts you want to allow users choose from when picking a layout. You must allow at least one layout.'),
389 '#default_value' => array_keys(array_filter($allowed_layouts->allowed_layout_settings)),
390 );
391
392 $form['clearer'] = array(
4f318c06 393 // TODO: Fix this to use clear-block instead
db205271
EM
394 '#value' => '<div style="clear: both;"></div>',
395 );
396 $form['#redirect'] = FALSE;
397 $form['submit'] = array(
398 '#type' => 'submit',
399 '#value' => t('Save'),
400 );
401
402 $form['#token'] = FALSE;
403 return $form;
404}
405
4f318c06
SB
406function panels_common_set_allowed_layouts_validate($form, &$form_state) {
407 $selected = array_filter($form_state['values']['layouts']);
db205271
EM
408 if (empty($selected)) {
409 form_set_error('layouts', 'You must choose at least one layout to allow.');
410 }
411}
412
4f318c06
SB
413function panels_common_set_allowed_layouts_submit($form, &$form_status) {
414 foreach ($form_state['values']['layouts'] as $layout => $setting) {
415 $form_state['allowed_layouts']->allowed_layout_settings[$layout] = $setting === 0 ? 0 : 1;
db205271 416 }
4f318c06
SB
417 method_exists($form_state['allowed_layouts'], 'save') ? $form_state['allowed_layouts']->save() : $form_state['allowed_layouts']->api_save();
418 return $form_state['allowed_layouts']->allowed_layout_settings;
db205271
EM
419}
420
421/**
422 * The layout information fieldset displayed at admin/edit/panel-%implementation%/add/%layout%.
423 */
424function panels_common_get_layout_information($panel_implementation, $contexts = array()) {
425 $form = array();
426 panels_load_include('plugins');
427 $layout = panels_get_layout($panel_implementation->display->layout);
428
429 $form = array(
430 '#type' => 'fieldset',
431 '#title' => t('Layout'),
432 );
433
434 $form['layout-icon'] = array(
435 '#value' => panels_print_layout_icon($panel_implementation->display->layout, $layout),
436 );
437
438 $form['layout-display'] = array(
439 '#value' => check_plain($layout['title']),
440 );
441 $content = '<dl class="content-list">';
442
443 foreach (panels_get_panels($layout, $panel_implementation->display) as $panel_id => $title) {
444 $content .= "<dt>$title</dt><dd>";
445 if ($panel_implementation->display->panels[$panel_id]) {
446 $content .= '<ol>';
447 foreach ($panel_implementation->display->panels[$panel_id] as $pid) {
448 $content .= '<li>'. panels_get_pane_title($panel_implementation->display->content[$pid], $contexts) .'</li>';
449 }
450 $content .= '</ol>';
451 }
452 else {
453 $content .= t('Empty');
454 }
455 $content .= '</dd>';
456 }
457 $content .= '</dl>';
458
459 $form['layout-content'] = array(
460 '#value' => $content,
461 );
462
463 return $form;
464}
465
db205271
EM
466/**
467 * Create a visible list of content in a display.
468 * Note that the contexts must be pre-loaded.
469 */
470function theme_panels_common_content_list($display) {
471 $layout = panels_get_layout($display->layout);
472 $content = '<dl class="content-list">';
473 foreach (panels_get_panels($layout, $display) as $panel_id => $title) {
474 $content .= "<dt>$title</dt><dd>";
0b7505e9 475 if (!empty($display->panels[$panel_id])) {
db205271
EM
476 $content .= '<ol>';
477 foreach ($display->panels[$panel_id] as $pid) {
478 $content .= '<li>' . panels_get_pane_title($display->content[$pid], $display->context) . '</li>';
479 }
480 $content .= '</ol>';
481 }
482 else {
483 $content .= t('Empty');
484 }
485 $content .= '</dd>';
486 }
487 $content .= '</dl>';
488 return $content;
489}
490
491/**
492 * Create a visible list of all the contexts available on an object.
493 * Assumes arguments, relationships and context objects.
494 *
495 * Contexts must be preloaded.
496 */
497function theme_panels_common_context_list($object) {
498 $titles = array();
499 $output = '';
500 $count = 1;
501 // First, make a list of arguments. Arguments are pretty simple.
502 if (!empty($object->arguments)) {
503 foreach ($object->arguments as $argument) {
504 $output .= '<tr>';
505 $output .= '<td><em>' . t('Argument @count', array('@count' => $count)) . '</em></td>';
506 $output .= '<td>' . check_plain($argument['identifier']) . '</td>';
507 $output .= '</tr>';
508 $titles[panels_argument_context_id($argument)] = $argument['identifier'];
509 $count++;
510 }
511 }
512 $count = 1;
513 // Then, make a nice list of contexts.
514 if (!empty($object->contexts)) {
515 foreach ($object->contexts as $context) {
516 $output .= '<tr>';
517 $output .= '<td><em>' . t('Context @count', array('@count' => $count)) . '</em></td>';
518 $output .= '<td>' . check_plain($context['identifier']) . '</td>';
519 $output .= '</tr>';
520 $titles[panels_context_context_id($context)] = $context['identifier'];
521 $count++;
522 }
523 }
524 // And relationships
525 if (!empty($object->relationships)) {
526 foreach ($object->relationships as $relationship) {
527 $output .= '<tr>';
528 $output .= '<td><em>' . t('From @title', array('@title' => $titles[$relationship['context']])) . '</em></td>';
529 $output .= '<td>' . check_plain($relationship['identifier']) . '</td>';
530 $output .= '</tr>';
531 $titles[panels_relationship_context_id($relationship)] = $relationship['identifier'];
532 $count++;
533 }
534 }
535 if ($output) {
536 return "<table><tbody>$output</tbody></table>\n";
537 }
538}
539