/[drupal]/contributions/modules/composite/composite.module
ViewVC logotype

Contents of /contributions/modules/composite/composite.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (show annotations) (download) (as text)
Tue Oct 14 01:03:56 2008 UTC (13 months, 1 week ago) by bengtan
Branch: MAIN
CVS Tags: DRUPAL-6--1-0-BETA1, HEAD
Branch point for: DRUPAL-6--1
File MIME type: text/x-php
Initial import.
1 <?php
2
3 // $Id$
4 // Modeline for drupal
5 // vim: set expandtab tabstop=2 shiftwidth=2 autoindent smartindent filetype=php:
6
7 /*
8 * Copyright 2008 ThinkLeft (thinkleft.com.au)
9 * Copyright 2008 ProsePoint (www.prosepoint.org)
10 */
11
12 /**
13 * Denotes that a reference is not enabled in any zone and should not
14 * be shown.
15 */
16 define('COMPOSITE_ZONE_NONE', -1);
17
18 function _composite_compare($a = 'reset-zones', $b) {
19 static $zones;
20
21 if (is_string($a)) {
22 // $a is a command
23 if ($a == 'reset-zones') {
24 // Store zones information from $b
25 $zones = array_flip(array_keys($b));
26 }
27 // If $a is a string, return regardless.
28 return;
29 }
30
31 // Sort by zone (in the order defined by the layout).
32 if ((!empty($a['zone']) && !empty($b['zone'])) && ($place = ($zones[$a['zone']] - $zones[$b['zone']]))) {
33 return $place;
34 }
35 // Sort by weight.
36 $weight = $a['weight'] - $b['weight'];
37 if ($weight) {
38 return $weight;
39 }
40 // Sort by title.
41 return strcmp($a['info'], $b['info']);
42 }
43
44 // Helper function: Filter out properties, fill in 'info' field
45 function _composite_references_preprocess($original, $zones) {
46 // Build a list of references, also generating labels in the process
47 $references = array();
48 foreach (element_children($original) as $id) {
49 $reference = $original[$id];
50 composite_invoke_referenceapi($reference, 'info');
51 $references[$id] = $reference;
52 }
53
54 return $references;
55 }
56
57 /***********************************************************
58 * CONFIGURATION FUNCTIONS *
59 ************************************************************/
60
61 /**
62 * Helper function to see if a node is a composite node
63 */
64 function composite_enabled($node) {
65 return variable_get('composite_enabled_' . $node->type, FALSE);
66 }
67
68 function composite_get_types($type = '') {
69 static $types = null;
70
71 if (empty($types)) {
72 $types = array();
73 foreach (module_implements('composite_types') as $module) {
74 $data = module_invoke($module, 'composite_types');
75
76 foreach ($data as $k => $unused) {
77 // Fill in 'module' if not specified
78 if (!$data[$k]['module']) {
79 $data[$k]['module'] = $module;
80 }
81
82 // Prefix 'file' with module dir
83 if ($data[$k]['file']) {
84 $data[$k]['file'] = drupal_get_path('module', $module) .'/'. $data[$k]['file'];
85 }
86 }
87 $types = array_merge_recursive($types, $data);
88 }
89
90 foreach (module_implements('composite_types_alter') as $module) {
91 $function = $module . '_composite_types_alter';
92 $function($types);
93 }
94 }
95
96 if ($type == '')
97 return $types;
98 else if (array_key_exists($type, $types))
99 return $types[$type];
100 }
101
102 // Includes the 'file' parameter of a reference type, if defined.
103 function composite_include_file($type) {
104 if ($type['file'] && is_file($type['file'])) {
105 include_once $type['file'];
106 }
107 }
108
109 function composite_invoke_referenceapi(&$reference, $op, $a3 = NULL, $a4 = NULL) {
110 $type_def = composite_get_types($reference['type']);
111 if ($type_def) {
112 composite_include_file($type_def);
113 $function = $type_def['module'] .'_composite_'. $type_def['type'] .'_api';
114 if (function_exists($function)) {
115 return $function($reference, $op, $a3, $a4);
116 }
117 }
118 }
119
120 function composite_get_layouts($type = '') {
121 static $layouts = array();
122 static $layouts_select = array();
123
124 if (!$layouts) {
125 // Would like to make layouts pluggable, but doesn't quite work
126 // because of inability to specify template files from other modules.
127 $layouts = module_invoke_all('composite_layouts');
128
129 // Do some defaults and path processing
130 // Construct a select list for convenience
131 foreach ($layouts as $key => $v) {
132 $key_dashed = strtr($key, '_', '-');
133 if (!$layouts[$key]['template']) {
134 $layouts[$key]['template'] = 'composite-layout-' . $key_dashed;
135 }
136 if (!$layouts[$key]['path']) {
137 $layouts[$key]['path'] = drupal_get_path('module', 'composite') . '/theme';
138 }
139
140 $css = $layouts[$key]['css'] ? $layouts[$key]['css'] : 'composite-layout-' . $key_dashed . '.css';
141 $css = $layouts[$key]['path'] . '/' . $css;
142 if (is_file($css)) {
143 $layouts[$key]['css'] = $css;
144 }
145
146 $icon = $layouts[$key]['icon'] ? $layouts[$key]['icon'] : 'composite-layout-' . $key_dashed . '.icon.png';
147 $icon = $layouts[$key]['path'] . '/' . $icon;
148 if (is_file($icon)) {
149 $layouts[$key]['icon'] = $icon;
150 }
151
152 $layouts_select[$key] = $v['name'];
153 }
154 }
155
156 if ($type == 'select') {
157 return $layouts_select;
158 }
159 else {
160 return $layouts;
161 }
162 }
163
164 function composite_get_layout($layout = '', $type = '') {
165 $layouts = composite_get_layouts($type);
166 return $layouts[$layout];
167 }
168
169 /***********************************************************
170 * MENU *
171 ************************************************************/
172
173 /**
174 * Implementation of hook_menu().
175 */
176 function composite_menu() {
177 $items = array();
178
179 // Generate local task for each reference type that requests it
180 $types = composite_get_types();
181 foreach ($types as $type) {
182 if ($type['local task']) {
183 $items['node/%node/composite_' . $type['type']] = array(
184 'title' => $type['label']['plural'],
185 'page callback' => 'composite_general_select_page',
186 'page arguments' => array($type['type'], 1),
187 'access callback' => 'composite_access',
188 'access arguments' => array($type['type'], 'update', 1),
189 'type' => MENU_LOCAL_TASK,
190 'weight' => 3,
191 'file' => 'composite.pages.inc',
192 );
193 }
194 }
195 $items['node/%node/composite_zones'] = array(
196 'title' => t('Zones'),
197 'page callback' => 'composite_zones_page',
198 'page arguments' => array(1),
199 'access callback' => 'composite_access',
200 'access arguments' => array('zones', 'update', 1),
201 'type' => MENU_LOCAL_TASK,
202 'weight' => 3,
203 'file' => 'composite.pages.inc',
204 );
205
206 return $items;
207 }
208
209 /**
210 * Access function used to turn tabs off, or defer to node_access
211 */
212 function composite_access($tab, $op, $node) {
213 $access = FALSE;
214
215 if (composite_enabled($node) && $node->composite_layout) {
216 $access_function = 'node_access';
217 // Change access function if this tab was generated for a reference type
218 // and the optional task access function is defined
219 $type = composite_get_types($tab);
220 if ($type && $type['task access']) {
221 composite_include_file($type);
222 $function = $type['task access'];
223 if (function_exists($function)) {
224 $access_function = $function;
225 }
226 }
227 $access = $access_function($op, $node);
228 }
229 return $access;
230 }
231
232 /***********************************************************
233 * NODE MANIPULATIONS .ie load/save/view etc. *
234 ************************************************************/
235
236 /**
237 * Implementation of hook_form_alter().
238 */
239 function composite_form_alter(&$form, $form_state, $form_id) {
240 if ($form['#id'] == 'node-form' && composite_enabled($form['#node'])) {
241 // .../node/xx/edit or .../node/add/xx page
242 $node = $form['#node'];
243 $form['composite_settings'] = array(
244 '#type' => 'fieldset',
245 '#title' => t('Composite layout'),
246 '#description' => t('Select the desired composite layout of this node.'),
247 '#collapsible' => TRUE,
248 '#collapsed' => !$node->composite_layout,
249 '#weight' => 10,
250 );
251
252 $form['composite_settings']['composite_references'] = array(
253 '#type' => 'value',
254 '#value' => $node->composite_references,
255 );
256 $form['composite_settings']['composite_layout'] = array(
257 '#title' => t('Layout'),
258 '#type' => 'composite_layout_radios',
259 '#options' => array('--' => t('No composite layout')) + composite_get_layouts('select'),
260 '#default_value' => $node->composite_layout ? $node->composite_layout : '--',
261 );
262 $form['composite_settings']['composite_content_reference'] = array(
263 '#type' => 'checkbox',
264 '#title' => t('Node content should be included as an item available for composite layout.'),
265 '#description' => t('This has no effect if no composite layout is selected.'),
266 '#default_value' => isset($node->composite_references['content']['type']) && ($node->composite_references['content']['type'] == 'content'),
267 );
268 }
269 if ($form_id == 'node_type_form' && isset($form['#node_type'])) {
270 // .../admin/content/node-type/xx page
271 $node_type = $form['#node_type']->type;
272 $composite_enabled = variable_get('composite_enabled_' . $node_type, FALSE);
273
274 // The code below borrows heavily from nodereference_field_settings()
275 $form['composite_extra_config'] = array(
276 '#type' => 'fieldset',
277 '#title' => t('Composite node'),
278 '#collapsible' => TRUE,
279 '#collapsed' => !$composite_enabled,
280 );
281 $form['composite_extra_config']['composite_enabled'] = array(
282 '#title' => t('Make this type a composite node type'),
283 '#type' => 'checkbox',
284 '#default_value' => $composite_enabled,
285 );
286
287 $form['composite_extra_config']['composite_referenceable_types'] = array(
288 '#title' => t('Content types that can be referenced'),
289 '#type' => 'checkboxes',
290 '#multiple' => TRUE,
291 '#options' => node_get_types('names'),
292 '#default_value' => variable_get('composite_referenceable_types_' . $node_type, array()),
293 );
294
295 if (module_exists('views')) {
296 $views = array('--' => '--');
297 $all_views = views_get_all_views();
298 foreach ($all_views as $view) {
299 // Only 'node' views that have fields will work for our purpose.
300 if ($view->base_table == 'node' && !empty($view->display['default']->display_options['fields'])) {
301 if ($view->type == 'Default') {
302 $views[t('Default Views')][$view->name] = $view->name;
303 }
304 else {
305 $views[t('Existing Views')][$view->name] = $view->name;
306 }
307 }
308 }
309
310 if (count($views) > 1) {
311 $form['composite_extra_config']['advanced'] = array(
312 '#type' => 'fieldset',
313 '#title' => t('Advanced - Nodes that can be referenced (View)'),
314 '#collapsible' => TRUE,
315 '#collapsed' => variable_get('composite_advanced_view_' . $node_type, '--') == '--',
316 );
317 $form['composite_extra_config']['advanced']['composite_advanced_view'] = array(
318 '#type' => 'select',
319 '#title' => t('View used to select the nodes'),
320 '#options' => $views,
321 '#default_value' => variable_get('composite_advanced_view_' . $node_type, '--'),
322 '#description' => t('Choose the "Views module" view that selects the nodes that can be referenced.<br />Note:<ul><li>Only views that have fields will work for this purpose.</li><li>This will discard the "Content types" settings above. Use the view\'s "filters" section instead.</li><li>Use the view\'s "fields" section to display additional informations about candidate nodes on node creation/edition form.</li><li>Use the view\'s "sort criteria" section to determine the order in which candidate nodes will be displayed.</li></ul>'),
323 );
324 $form['composite_extra_config']['advanced']['composite_advanced_view_args'] = array(
325 '#type' => 'textfield',
326 '#title' => t('View arguments'),
327 '#default_value' => variable_get('composite_advanced_view_args_' . $node_type, ''),
328 '#required' => FALSE,
329 '#description' => t('Provide a comma separated list of arguments to pass to the view.'),
330 );
331 }
332 }
333 }
334 }
335
336 /**
337 * Implementation of hook_nodeapi().
338 */
339 function composite_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
340 if (!composite_enabled($node))
341 return;
342
343 switch ($op) {
344 case 'delete':
345 // Notice that we're matching all revisions, by using the node's nid.
346 db_query("DELETE FROM {node_composite} WHERE nid = %d", $node->nid);
347 db_query("DELETE FROM {node_composite_references} WHERE nid = %d", $node->nid);
348 break;
349
350 case 'delete revision':
351 composite_delete_revision($node);
352 break;
353
354 case 'insert':
355 case 'update':
356 composite_update($node);
357 break;
358
359 case 'load':
360 return composite_load($node);
361 break;
362
363 case 'view':
364 // Only do something in full view, and if there is a layout defined.
365 if (!$a3 /* !$teaser */ && $node->composite_layout) {
366 $node->composite_content = TRUE;
367 }
368 break;
369
370 case 'alter':
371 if (isset($node->composite_content)) {
372 // Have to do our 'view' stuff in the alter hook because we have to wait
373 // for other modules to do their processing first.
374 $node->composite_content = composite_view($node, composite_get_layout($node->composite_layout), $node->composite_references);
375 foreach ($node->composite_content as $zone => $zone_item) {
376 $node->composite_content[$zone] = drupal_render($zone_item);
377 }
378 $node->body = theme('composite_content', composite_get_layout($node->composite_layout), $node->composite_content);
379 }
380 break;
381 }
382 }
383
384 function composite_delete_revision($node) {
385 db_query("DELETE FROM {node_composite} WHERE nid = %d AND vid = %d", $node->nid, $node->vid);
386 db_query("DELETE FROM {node_composite_references} WHERE nid = %d AND vid = %d", $node->nid, $node->vid);
387 }
388
389 /**
390 * Implementation of hook_update().
391 *
392 * Not really. It just happens to have a compatible function signature with hook_update
393 *
394 */
395 function composite_update($node) {
396 // Just delete everything, and then add back in later
397 composite_delete_revision($node);
398
399 if ($node->composite_layout && $node->composite_layout != '--') {
400 // Treat the content_reference specially if we came in from an edit form.
401 if (isset($node->composite_content_reference)) {
402 if ($node->composite_content_reference) {
403 // Add content_reference, but only if it doesn't already exist
404 if (!$node->composite_references['content']) {
405 $node->composite_references['content'] = array(
406 'type' => 'content',
407 'weight' => 0,
408 'id' => 'content',
409 'data' => '',
410 'zone' => COMPOSITE_ZONE_NONE,
411 );
412 }
413 }
414 else {
415 // Unset content_reference
416 unset($node->composite_references['content']);
417 }
418 }
419
420 // Update node_composite
421 db_query("INSERT INTO {node_composite} (nid, vid, layout) VALUES (%d, %d, '%s') ", $node->nid, $node->vid, $node->composite_layout);
422
423 // Update node_composite_references
424 $args = array();
425 $query_parts = array();
426 foreach (element_children($node->composite_references) as $id) {
427 $reference = $node->composite_references[$id];
428 if (is_array($reference) && $reference['id']) {
429 $query_parts[] = " (%d, %d, '%s', %d, '%s', '%s', '%s')";
430 $args[] = $node->nid;
431 $args[] = $node->vid;
432 $args[] = $reference['type'];
433 $args[] = $reference['weight'];
434 $args[] = $id;
435 $args[] = $reference['data'] ? serialize($reference['data']) : '';
436 $args[] = $reference['zone'];
437 }
438 }
439
440 if (count($query_parts)) {
441 $query = "INSERT INTO {node_composite_references} (nid, vid, type, weight, id, data, zone) VALUES" . implode(', ', $query_parts);
442 db_query($query, $args);
443 }
444 }
445 }
446
447 /**
448 * Implementation of hook_load().
449 *
450 * Not really. It just happens to have a compatible function signature with hook_load
451 */
452 function composite_load($node) {
453 $types = composite_get_types();
454 $additions = array();
455
456 $result = db_fetch_object(db_query('SELECT layout FROM {node_composite} WHERE vid = %d', $node->vid));
457 $additions['composite_layout'] = $result->layout;
458
459 // Seed reference sublists - they must be arrays or empty arrays
460 $sublists = array();
461 foreach ($types as $type) {
462 $sublists[$type['type']] = array();
463 }
464
465 $result = db_query("SELECT type, weight, id, data, zone FROM {node_composite_references} WHERE vid = %d", $node->vid);
466 while ($object = db_fetch_object($result)) {
467 // Some common manipulations
468 $object = (array) $object;
469 $object['data'] = unserialize($object['data']);
470
471 // Some type specific manipulations
472 composite_invoke_referenceapi($object, 'load');
473
474 $additions['composite_references'][$object['id']] = $object;
475 $sublists[$object['type']][$object['id']] = $object;
476 }
477
478 // Add reference sublists to the load
479 foreach ($types as $type) {
480 $additions['composite_references']['#'. $type['type'] .'_references'] = $sublists[$type['type']];
481 }
482
483 return $additions;
484 }
485
486 /**
487 * Nodeapi view helper
488 * Assemble the composite content into an array in a similar fashion to $node->content
489 *
490 * - $content: Original node content
491 * - $layout: Layout definition
492 * - $references: List of items for composite content
493 */
494 function composite_view($node, $layout = array(), $references = array()) {
495 $composite_content = array();
496 $references = _composite_references_preprocess($references, $layout['zones'], FALSE);
497
498 foreach (element_children($references) as $id) {
499 $reference = $references[$id];
500 // Filter out items not in a displayable zone
501 if (array_key_exists($reference['zone'], $layout['zones'])) {
502 $output = composite_invoke_referenceapi($reference, 'view', $node);
503 if ($output) {
504 $composite_content[$reference['zone']][$reference['id']] = array(
505 '#value' => $output,
506 '#weight' => $reference['weight'],
507 );
508 }
509 }
510 }
511 return $composite_content;
512 }
513
514 /***********************************************************
515 * CUSTOM LAYOUT SELECT ELEMENT *
516 ************************************************************/
517
518 function composite_elements() {
519 $type['composite_layout_radios'] = array('#input' => TRUE, '#process' => array('composite_layout_radios_process'));
520 return $type;
521 }
522
523 function composite_layout_radios_process($element) {
524 if (count($element['#options']) > 0) {
525 // Do the normal radios thing
526 $element = expand_radios($element);
527
528 // And then add in our icons
529 $layouts = composite_get_layouts();
530 drupal_add_css(drupal_get_path('module', 'composite') . '/composite.css', 'module', 'all', FALSE);
531
532 foreach (element_children($element) as $key) {
533 if (array_key_exists($key, $layouts)) {
534 $element[$key]['#prefix'] = '<div class="composite-layout-option">';
535 $element[$key]['#suffix'] = '</div>';
536
537 if ($layouts[$key]['icon']) {
538 $element[$key]['#prefix'] .= theme('composite_layout_icon', $layouts[$key]['icon']);
539 }
540 }
541 }
542
543 $element['#prefix'] = '<div class="clear-block">';
544 $element['#suffix'] = '</div>';
545 return $element;
546 }
547 }
548
549 /***********************************************************
550 * THEME-ING *
551 ************************************************************/
552
553 /**
554 * Implementation of hook_theme()
555 */
556 function composite_theme() {
557 return array(
558 'composite_layout_radios' => array(
559 'arguments' => array('element' => NULL),
560 ),
561 'composite_layout_icon' => array(
562 'arguments' => array('file' => NULL),
563 ),
564 'composite_zones_form' => array(
565 'template' => 'composite-zones-form',
566 'file' => 'composite.pages.inc',
567 'arguments' => array('form' => NULL),
568 ),
569 'composite_zones_preview' => array(
570 'arguments' => array('node' => NULL),
571 ),
572 'composite_content' => array(
573 'template' => 'composite-content',
574 'path' => drupal_get_path('module', 'composite') . '/theme',
575 'arguments' => array('layout' => array(), 'composite_content' => array()),
576 ),
577 'composite_node_title' => array(
578 'arguments' => array('title' => ''),
579 ),
580 );
581 }
582
583 function theme_composite_layout_radios($element) {
584 return theme('radios', $element);
585 }
586
587 function theme_composite_layout_icon($file) {
588 return '<img src="' . url($file) .'" class="composite-layout-icon" />';
589 }
590
591 function theme_composite_zones_preview($node) {
592 if (isset($node->nid)) {
593 $output = '<div class="preview">' . node_view($node, FALSE, FALSE) .'</div>';
594 return $output;
595 }
596 }
597
598 function template_preprocess_composite_content(&$variables) {
599 $layout = $variables['layout'];
600 $composite_content = $variables['composite_content'];
601 $key = strtr($layout['key'], '_', '-');
602
603 // Always include the default template
604 $variables['template_files'][] = 'composite-content';
605 $variables['template_files'][] = $layout['template'];
606
607 if ($layout['css']) {
608 drupal_add_css($layout['css']);
609 }
610
611 // Extract out composite_content into separate variables
612 $variables['content'] = $composite_content['content'];
613 foreach ($layout['zones'] as $zone => $unused) {
614 $variables[$zone] = $composite_content[$zone];
615 }
616 }
617
618 function theme_composite_node_title($title) {
619 return '<div>' . $title . '</div>';
620 }
621
622 /***********************************************************
623 * REFERENCE TYPE DEFINITIONS *
624 ************************************************************/
625
626 function composite_composite_types() {
627 $types = array(
628 // 'content' is treated specially in some places.
629 'content' => array(
630 'type' => 'content',
631 ),
632 'block' => array(
633 'type' => 'block',
634 // 'module' => 'composite', // optional value
635 'file' => 'composite.block.inc', // optional, path relative from directory of 'module'
636 'label' => array('singular' => t('Block'), 'plural' => t('Blocks')),
637
638 // 'local task' - whether composite.module should generate a local task tab for this type
639 'local task' => TRUE,
640 // 'task access' - optional menu access function for the local task tab
641 'task access' => '',
642 // 'potentials callback' - callback to retrieve list of potential composite references
643 'potentials callback' => 'composite_composite_block_potentials',
644 ),
645 'node' => array(
646 'type' => 'node',
647 'file' => 'composite.node.inc',
648 'label' => array('singular' => t('Node'), 'plural' => t('Nodes')),
649 'local task' => TRUE,
650 'task access' => 'composite_composite_node_access',
651 'potentials callback' => 'composite_composite_node_potentials',
652 ),
653 );
654 return $types;
655 }
656
657 function composite_composite_content_api(&$reference, $op, $node = NULL, $a4 = NULL) {
658 switch ($op) {
659 case 'info':
660 $reference['info'] = t('Node: Original content');
661 break;
662
663 // Return a rendering of the reference item
664 case 'view':
665 // Be careful of security here. The correct field (and whether to escape
666 // malicious text) depends on who calls composite_view() which calls this,
667 return $node->body;
668 break;
669 }
670 }
671
672 /***********************************************************
673 * LAYOUT DEFINITIONS *
674 ************************************************************/
675
676 function composite_composite_layouts() {
677 // Try to keep zone names common across layouts (but obviously within
678 // sensible limits), so there is minimal disruption if the layout changes.
679 $layouts['onecol'] = array(
680 'key' => 'onecol',
681 'name' => t('Single column'),
682 'zones' => array(
683 'left' => t('Left column'),
684 ),
685 // The following three vars are optional. If not specified, they
686 // will be derived from key, in an equivalent manner to what you see below.
687 'path' => drupal_get_path('module', 'composite') . '/theme',
688 'css' => 'composite-layout-onecol.css',
689 'template' => 'composite-layout-onecol',
690 'icon' => 'composite-layout-onecol.icon.png'
691 );
692 $layouts['twocol'] = array(
693 'key' => 'twocol',
694 'name' => t('Two columns'),
695 'zones' => array(
696 'top' => t('Top'),
697 'left' => t('Left column'),
698 'right' => t('Right column'),
699 'bottom' => t('Bottom'),
700 ),
701 );
702 $layouts['twocol_bricks'] = array(
703 'key' => 'twocol_bricks',
704 'name' => t('Two columns bricks'),
705 'zones' => array(
706 'top' => t('Top'),
707 'left' => t('Left column (upper)'),
708 'right' => t('Right column (upper)'),
709 'middle_row' => t('Middle row'),
710 'left2' => t('Left column (lower)'),
711 'right2' => t('Right column (lower)'),
712 'bottom' => t('Bottom'),
713 ),
714 );
715 $layouts['threecol_33_33_33'] = array(
716 'key' => 'threecol_33_33_33',
717 'name' => t('Three columns 33/33/33'),
718 'zones' => array(
719 'top' => t('Top'),
720 'left' => t('Left column'),
721 'middle' => t('Middle column'),
722 'right' => t('Right column'),
723 'bottom' => t('Bottom'),
724 ),
725 );
726 $layouts['threecol_25_50_25'] = array(
727 'key' => 'threecol_25_50_25',
728 'name' => t('Three columns 25/50/25'),
729 'zones' => array(
730 'top' => t('Top'),
731 'left' => t('Left column'),
732 'middle' => t('Middle column'),
733 'right' => t('Right column'),
734 'bottom' => t('Bottom'),
735 ),
736 );
737 return $layouts;
738 }
739

  ViewVC Help
Powered by ViewVC 1.1.2