Projet

Général

Profil

Paste
Télécharger (62,1 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / panels / plugins / layouts / flexible / flexible.inc @ 64156087

1
<?php
2

    
3
/**
4
 * @file
5
 * Flexible layout plugin.
6
 */
7

    
8
/**
9
 * Implementation of hook_panels_layouts().
10
 */
11
// Plugin definition.
12
$plugin = array(
13
  'title' => t('Flexible'),
14
  'category' => t('Builders'),
15
  'icon' => 'flexible.png',
16
  'theme' => 'panels_flexible',
17
  'admin theme' => 'panels_flexible_admin',
18
  'css' => 'flexible.css',
19
  'admin css' => 'flexible-admin.css',
20
  'settings form' => 'panels_flexible_settings_form',
21
  'settings submit' => 'panels_flexible_settings_submit',
22
  'settings validate' => 'panels_flexible_settings_validate',
23
  'regions function' => 'panels_flexible_panels',
24
  'hook menu' => 'panels_flexible_menu',
25

    
26
  // Reusable layout Builder specific directives.
27
  'builder' => TRUE,
28
  'builder tab title' => 'Add flexible layout',
29
  'get child' => 'panels_flexible_get_sublayout',
30
  'get children' => 'panels_flexible_get_sublayouts',
31

    
32
  // Define ajax callbacks.
33
  'ajax' => array(
34
    'settings' => 'panels_ajax_flexible_edit_settings',
35
    'add' => 'panels_ajax_flexible_edit_add',
36
    'remove' => 'panels_ajax_flexible_edit_remove',
37
    'resize' => 'panels_ajax_flexible_edit_resize',
38
    'reuse' => 'panels_ajax_flexible_edit_reuse',
39
  ),
40
);
41

    
42
/**
43
 * Merge the main flexible plugin with a layout to create a sub plugin.
44
 *
45
 * This is used for both panels_flexible_get_sublayout and
46
 * panels_flexible_get_sublayouts.
47
 */
48
function panels_flexible_merge_plugin($plugin, $layout) {
49
  $plugin['name'] = 'flexible:' . $layout->name;
50
  $plugin['category'] = !empty($layout->category) ? check_plain($layout->category) : t('Miscellaneous');
51
  $plugin['title'] = check_plain($layout->admin_title);
52
  $plugin['description'] = check_plain($layout->admin_description);
53
  $plugin['layout'] = $layout;
54
  $plugin['builder'] = FALSE;
55
  $plugin['builder tab title'] = NULL;
56
  return $plugin;
57
}
58

    
59
/**
60
 * Callback to provide a single stored flexible layout.
61
 */
62
function panels_flexible_get_sublayout($plugin, $layout_name, $sublayout_name) {
63
  // Do not worry about caching; Panels is handling that for us.
64
  ctools_include('export');
65
  $item = ctools_export_crud_load('panels_layout', $sublayout_name);
66
  if ($item) {
67
    return panels_flexible_merge_plugin($plugin, $item);
68
  }
69
}
70

    
71
/**
72
 * Callback to provide all stored flexible layouts.
73
 */
74
function panels_flexible_get_sublayouts($plugin, $layout_name) {
75
  $layouts[$layout_name] = $plugin;
76
  ctools_include('export');
77
  $items = ctools_export_load_object('panels_layout', 'conditions', array('plugin' => 'flexible'));
78
  foreach ($items as $name => $item) {
79
    $layouts['flexible:' . $name] = panels_flexible_merge_plugin($plugin, $item);
80
  }
81

    
82
  return $layouts;
83
}
84

    
85
/**
86
 * Flexible panel settings converter.
87
 *
88
 * Convert settings from old style to new, or provide defaults for
89
 * empty settings.
90
 *
91
 * @param array $settings
92
 *   Drupal settings for the layout.
93
 *
94
 * @return null
95
 *   Nothing to return.
96
 */
97
function panels_flexible_convert_settings(&$settings, &$layout) {
98
  // This indicates that this is a layout that they used the checkbox
99
  // on. The layout is still 'flexible' but it's actually pointing
100
  // to another stored one and we have to load it.
101
  if (!empty($settings['layout'])) {
102
    $layout = panels_get_layout('flexible:' . $settings['layout']);
103
  }
104

    
105
  if (!empty($layout['layout'])) {
106
    $settings = $layout['layout']->settings;
107
    if ($settings) {
108
      return $settings;
109
    }
110
  }
111

    
112
  if (empty($settings)) {
113
    // Set up a default.
114
    $settings = array(
115
      'items' => array(
116
        // The 'canvas' is a special row that does not get rendered
117
        // normally, but is used to contain the columns.
118
        'canvas' => array(
119
          'type' => 'row',
120
          'contains' => 'column',
121
          'children' => array('main'),
122
          'parent' => NULL,
123
        ),
124
        'main' => array(
125
          'type' => 'column',
126
          'width' => 100,
127
          'width_type' => '%',
128
          'children' => array('main-row'),
129
          'parent' => 'canvas',
130
        ),
131
        'main-row' => array(
132
          'type' => 'row',
133
          'contains' => 'region',
134
          'children' => array('center'),
135
          'parent' => 'main',
136
        ),
137
        'center' => array(
138
          'type' => 'region',
139
          'title' => t('Center'),
140
          'width' => 100,
141
          'width_type' => '%',
142
          'parent' => 'main-row',
143
        ),
144
      ),
145
    );
146
  }
147
  elseif (!isset($settings['items'])) {
148
    // Convert an old style flexible to a new style flexible.
149
    $old = $settings;
150
    $settings = array();
151
    $settings['items']['canvas'] = array(
152
      'type' => 'row',
153
      'contains' => 'column',
154
      'children' => array(),
155
      'parent' => NULL,
156
    );
157
    // Add the left sidebar column, row and region if it exists.
158
    if (!empty($old['sidebars']['left'])) {
159
      $settings['items']['canvas']['children'][] = 'sidebar-left';
160
      $settings['items']['sidebar-left'] = array(
161
        'type' => 'column',
162
        'width' => $old['sidebars']['left_width'],
163
        'width_type' => $old['sidebars']['width_type'],
164
        'children' => array('sidebar-left-row'),
165
        'parent' => 'canvas',
166
      );
167
      $settings['items']['sidebar-left-row'] = array(
168
        'type' => 'row',
169
        'contains' => 'region',
170
        'children' => array('sidebar_left'),
171
        'parent' => 'sidebar-left',
172
      );
173
      $settings['items']['sidebar_left'] = array(
174
        'type' => 'region',
175
        'title' => t('Left sidebar'),
176
        'width' => 100,
177
        'width_type' => '%',
178
        'parent' => 'sidebar-left-row',
179
      );
180
    }
181

    
182
    $settings['items']['canvas']['children'][] = 'main';
183

    
184
    if (!empty($old['sidebars']['right'])) {
185
      $settings['items']['canvas']['children'][] = 'sidebar-right';
186
      $settings['items']['sidebar-right'] = array(
187
        'type' => 'column',
188
        'width' => $old['sidebars']['right_width'],
189
        'width_type' => $old['sidebars']['width_type'],
190
        'children' => array('sidebar-right-row'),
191
        'parent' => 'canvas',
192
      );
193
      $settings['items']['sidebar-right-row'] = array(
194
        'type' => 'row',
195
        'contains' => 'region',
196
        'children' => array('sidebar_right'),
197
        'parent' => 'sidebar-right',
198
      );
199
      $settings['items']['sidebar_right'] = array(
200
        'type' => 'region',
201
        'title' => t('Right sidebar'),
202
        'width' => 100,
203
        'width_type' => '%',
204
        'parent' => 'sidebar-right-row',
205
      );
206
    }
207

    
208
    // Add the main column.
209
    $settings['items']['main'] = array(
210
      'type' => 'column',
211
      'width' => 100,
212
      'width_type' => '%',
213
      'children' => array(),
214
      'parent' => 'canvas',
215
    );
216

    
217
    // Add rows and regions.
218
    for ($row = 1; $row <= intval($old['rows']); $row++) {
219
      // Create entry for the row:
220
      $settings['items']["row_$row"] = array(
221
        'type' => 'row',
222
        'contains' => 'region',
223
        'children' => array(),
224
        'parent' => 'main',
225
      );
226
      // Add the row to the parent's children:
227
      $settings['items']['main']['children'][] = "row_$row";
228

    
229
      for ($col = 1; $col <= intval($old["row_$row"]['columns']); $col++) {
230
        // Create entry for the region:
231
        $settings['items']["row_${row}_$col"] = array(
232
          'type' => 'region',
233
          'width' => $old["row_$row"]["width_$col"],
234
          'width_type' => '%',
235
          'parent' => "row_$row",
236
        );
237
        // Add entry for the region to the row's children:
238
        $settings['items']["row_$row"]['children'][] = "row_${row}_$col";
239

    
240
        // Apply the proper title to the region:
241
        if (!empty($old["row_$row"]['names'][$col - 1])) {
242
          $settings['items']["row_${row}_$col"]['title'] = $old["row_$row"]['names'][$col - 1];
243
        }
244
        else {
245
          $settings['items']["row_${row}_$col"]['title'] = t("Row @row, Column @col", array('@row' => $row, '@col' => $col));
246
        }
247
      }
248
    }
249
  }
250
  elseif (isset($settings['canvas'])) {
251
    // Convert the old 'canvas' to the new canvas row.
252
    $settings['items']['canvas'] = array(
253
      'type' => 'row',
254
      'contains' => 'column',
255
      'children' => $settings['canvas'],
256
      'parent' => NULL,
257
    );
258
    unset($settings['canvas']);
259
  }
260
}
261

    
262
/**
263
 * Define the actual list of columns and rows for this flexible panel.
264
 */
265
function panels_flexible_panels($display, $settings, $layout) {
266
  $items = array();
267
  panels_flexible_convert_settings($settings, $layout);
268
  foreach ($settings['items'] as $id => $item) {
269
    // Remove garbage values.
270
    if (!isset($item['type'])) {
271
      unset($items[$id]);
272
    }
273
    elseif ($item['type'] == 'region') {
274
      $items[$id] = $item['title'];
275
    }
276
  }
277

    
278
  return $items;
279
}
280

    
281
/**
282
 * Create a renderer object.
283
 *
284
 * The renderer object contains data that is passed around from function
285
 * to function allowing us to render our CSS and HTML easily.
286
 *
287
 * @todo Convert the functions to methods and make this properly OO.
288
 */
289
function panels_flexible_create_renderer($admin, $css_id, $content, $settings, &$display, $layout, $handler) {
290
  $renderer = new stdClass();
291
  $renderer->settings = $settings;
292
  $renderer->content = $content;
293
  $renderer->css_id = $css_id;
294
  $renderer->did = &$display->did;
295
  if ($admin) {
296
    // Always scale in admin mode.
297
    $renderer->scale_base = 99.0;
298
  }
299
  else {
300
    $renderer->scale_base = !empty($settings['items']['canvas']['no_scale']) ? 100.0 : 99.0;
301
  }
302
  $renderer->id_str = $css_id ? 'id="' . $css_id . '"' : '';
303
  $renderer->admin = $admin;
304
  $renderer->handler = $handler;
305

    
306
  // Set up basic classes for all of our components.
307
  $renderer->name                 = !empty($layout['layout']) ? $layout['layout']->name : $display->did;
308
  $renderer->base_class           = $renderer->name;
309
  $renderer->item_class['column'] = 'panels-flexible-column';
310
  $renderer->item_class['row']    = 'panels-flexible-row';
311
  $renderer->item_class['region'] = 'panels-flexible-region';
312
  $renderer->base['canvas']       = 'panels-flexible-' . $renderer->base_class;
313

    
314
  // Override these if selected from the UI and not in admin mode.
315
  if (!$admin) {
316
    if (!empty($settings['items']['canvas']['class'])) {
317
      $renderer->base_class = $settings['items']['canvas']['class'];
318
      $renderer->base['canvas'] = $renderer->base_class;
319
    }
320
    if (!empty($settings['items']['canvas']['column_class'])) {
321
      $renderer->item_class['column'] = $settings['items']['canvas']['column_class'];
322
    }
323
    if (!empty($settings['items']['canvas']['row_class'])) {
324
      $renderer->item_class['row'] = $settings['items']['canvas']['row_class'];
325
    }
326
    if (!empty($settings['items']['canvas']['region_class'])) {
327
      $renderer->item_class['region'] = $settings['items']['canvas']['region_class'];
328
    }
329
  }
330

    
331
  // Get the separation values out of the canvas settings.
332
  $renderer->column_separation = !empty($settings['items']['canvas']['column_separation']) ? $settings['items']['canvas']['column_separation'] : '0.5em';
333

    
334
  $renderer->region_separation = !empty($settings['items']['canvas']['region_separation']) ? $settings['items']['canvas']['region_separation'] : '0.5em';
335

    
336
  $renderer->row_separation = !empty($settings['items']['canvas']['row_separation']) ? $settings['items']['canvas']['row_separation'] : '0.5em';
337

    
338
  // Make some appended classes so it's easier to reference them.
339
  $renderer->base['column'] = $renderer->item_class['column'] . '-' . $renderer->base_class;
340
  $renderer->base['row']    = $renderer->item_class['row'] . '-' . $renderer->base_class;
341
  $renderer->base['region'] = $renderer->item_class['region'] . '-' . $renderer->base_class;
342

    
343
  if ($renderer->name != 'new') {
344
    // Use v2 to guarantee all CSS gets regenerated to account for changes in
345
    // how some divs will be rendered.
346
    $renderer->css_cache_name = 'flexiblev2:' . $renderer->name;
347
    if ($admin) {
348
      ctools_include('css');
349
      ctools_css_clear($renderer->css_cache_name);
350
    }
351
  }
352
  return $renderer;
353
}
354

    
355
/**
356
 * Draw the flexible layout.
357
 */
358
function theme_panels_flexible($vars) {
359
  $css_id = $vars['css_id'];
360
  $content = $vars['content'];
361
  $settings = $vars['settings'];
362
  $display = $vars['display'];
363
  $layout = $vars['layout'];
364
  $handler = $vars['renderer'];
365

    
366
  panels_flexible_convert_settings($settings, $layout);
367

    
368
  $renderer = panels_flexible_create_renderer(FALSE, $css_id, $content, $settings, $display, $layout, $handler);
369

    
370
  // CSS must be generated because it reports back left/middle/right
371
  // positions.
372
  $css = panels_flexible_render_css($renderer);
373

    
374
  if (!empty($renderer->css_cache_name) && empty($display->editing_layout)) {
375
    ctools_include('css');
376
    // Generate an id based upon rows + columns:
377
    $filename = ctools_css_retrieve($renderer->css_cache_name);
378
    if (!$filename) {
379
      $filename = ctools_css_store($renderer->css_cache_name, $css, FALSE);
380
    }
381

    
382
    // Give the CSS to the renderer to put where it wants.
383
    if ($handler) {
384
      $handler->add_css($filename, 'module', 'all', FALSE);
385
    }
386
    else {
387
      drupal_add_css($filename);
388
    }
389
  }
390
  else {
391
    // If the id is 'new' we can't reliably cache the CSS in the filesystem
392
    // because the display does not truly exist, so we'll stick it in the
393
    // head tag. We also do this if we've been told we're in the layout
394
    // editor so that it always gets fresh CSS.
395
    drupal_add_css($css, array('type' => 'inline', 'preprocess' => FALSE));
396
  }
397

    
398
  // Also store the CSS on the display in case the live preview or something
399
  // needs it.
400
  $display->add_css = $css;
401

    
402
  $output = "<div class=\"panel-flexible " . $renderer->base['canvas'] . " clearfix\" $renderer->id_str>\n";
403
  $output .= "<div class=\"panel-flexible-inside " . $renderer->base['canvas'] . "-inside\">\n";
404

    
405
  $output .= panels_flexible_render_items($renderer, $settings['items']['canvas']['children'], $renderer->base['canvas']);
406

    
407
  // Wrap the whole thing up nice and snug.
408
  $output .= "</div>\n</div>\n";
409

    
410
  return $output;
411
}
412

    
413
/**
414
 * Draw the flexible layout.
415
 */
416
function theme_panels_flexible_admin($vars) {
417
  $css_id = $vars['css_id'];
418
  $content = $vars['content'];
419
  $settings = $vars['settings'];
420
  $display = $vars['display'];
421
  $layout = $vars['layout'];
422
  $handler = $vars['renderer'];
423

    
424
  // We never draw stored flexible layouts in admin mode; they must be edited
425
  // from the stored layout UI at that point.
426
  if (!empty($layout['layout'])) {
427
    return theme_panels_flexible(array('css_id' => $css_id, 'content' => $content, 'settings' => $settings, 'display' => $display, 'layout' => $layout, 'renderer' => $handler));
428
  }
429

    
430
  panels_flexible_convert_settings($settings, $layout);
431
  $renderer = panels_flexible_create_renderer(TRUE, $css_id, $content, $settings, $display, $layout, $handler);
432

    
433
  $css = panels_flexible_render_css($renderer);
434

    
435
  // For the administrative view, add CSS directly to head.
436
  drupal_add_css($css, array('type' => 'inline', 'preprocess' => FALSE));
437

    
438
  if (empty($display->editing_layout)) {
439
    $output = '<input type="submit" id="panels-flexible-toggle-layout" class="form-submit" value ="' .
440
      t('Show layout designer') . '">';
441
    if (user_access('administer panels layouts')) {
442
      $output .= '<input type="hidden" class="panels-flexible-reuse-layout-url" value="' . url($handler->get_url('layout', 'reuse'), array('absolute' => TRUE)) . '">';
443
      $output .= '<input type="submit" id="panels-flexible-reuse-layout" class="form-submit ctools-use-modal" value ="' .
444
        t('Reuse layout') . '">';
445
    }
446
    $output .= "<div class=\"panel-flexible " . $renderer->base['canvas'] . " clearfix panel-flexible-admin panel-flexible-no-edit-layout\" $renderer->id_str>\n";
447
  }
448
  else {
449
    $output = "<div class=\"panel-flexible " . $renderer->base['canvas'] . " clearfix panel-flexible-admin panel-flexible-edit-layout\" $renderer->id_str>\n";
450
  }
451
  $output .= "<div class=\"panel-flexible-inside " . $renderer->base['canvas'] . "-inside \">\n";
452

    
453
  $content = panels_flexible_render_items($renderer, $settings['items']['canvas']['children'], $renderer->base['row'] . '-canvas');
454
  $output .= panels_flexible_render_item($renderer, $settings['items']['canvas'], $content, 'canvas', 0, 0, TRUE);
455

    
456
  // Wrap the whole thing up nice and snug.
457
  $output .= "</div>\n</div>\n";
458

    
459
  drupal_add_js($layout['path'] . '/flexible-admin.js');
460
  drupal_add_js(array('flexible' => array('resize' => url($handler->get_url('layout', 'resize'), array('absolute' => TRUE)))), 'setting');
461
  return $output;
462
}
463

    
464
/**
465
 * Render a piece of a flexible layout.
466
 */
467
function panels_flexible_render_items($renderer, $list, $owner_id) {
468
  $output = '';
469
  $groups = array('left' => '', 'middle' => '', 'right' => '');
470
  $max = count($list) - 1;
471
  $prev = NULL;
472

    
473
  foreach ($list as $position => $id) {
474
    $item = $renderer->settings['items'][$id];
475
    $location = isset($renderer->positions[$id]) ? $renderer->positions[$id] : 'middle';
476

    
477
    if ($renderer->admin && $item['type'] != 'row' && $prev) {
478
      $groups[$location] .= panels_flexible_render_splitter($renderer, $prev, $id);
479
    }
480

    
481
    switch ($item['type']) {
482
      case 'column':
483
        $content = panels_flexible_render_items($renderer, $item['children'], $renderer->base['column'] . '-' . $id);
484
        if (empty($renderer->settings['items'][$id]['hide_empty']) || trim($content)) {
485
          $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max);
486
        }
487
        break;
488

    
489
      case 'row':
490
        $content = panels_flexible_render_items($renderer, $item['children'], $renderer->base['row'] . '-' . $id);
491
        if (empty($renderer->settings['items'][$id]['hide_empty']) || trim($content)) {
492
          $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max, TRUE);
493
        }
494
        break;
495

    
496
      case 'region':
497
        if (empty($renderer->settings['items'][$id]['hide_empty'])) {
498
          $content = isset($renderer->content[$id]) ? $renderer->content[$id] : "&nbsp;";
499
        }
500
        else {
501
          $content = isset($renderer->content[$id]) ? trim($renderer->content[$id]) : "";
502
        }
503
        if (empty($renderer->settings['items'][$id]['hide_empty']) || $content) {
504
          $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max);
505
        }
506
        break;
507
    }
508

    
509
    // If all items are fixed then we have a special splitter on the right to
510
    // control the overall width.
511
    if (!empty($renderer->admin) && $max == $position && $location == 'left') {
512
      $groups[$location] .= panels_flexible_render_splitter($renderer, $id, NULL);
513
    }
514
    $prev = $id;
515
  }
516

    
517
  $group_count = count(array_filter($groups));
518

    
519
  // Render each group. We only render the group div if we're in admin mode
520
  // or if there are multiple groups.
521
  foreach ($groups as $position => $content) {
522
    if (!empty($content) || $renderer->admin) {
523
      if ($group_count > 1 || $renderer->admin) {
524
        $output .= '<div class="' . $owner_id . '-' . $position . '">' . $content . '</div>';
525
      }
526
      else {
527
        $output .= $content;
528
      }
529
    }
530
  }
531

    
532
  return $output;
533
}
534

    
535
/**
536
 * Render a column in the flexible layout.
537
 */
538
function panels_flexible_render_item($renderer, $item, $content, $id, $position, $max, $clear = FALSE) {
539

    
540
  // If we are rendering a row and there is just one row, we don't need to
541
  // render the row unless there is fixed_width content inside it.
542
  if (empty($renderer->admin) && $item['type'] == 'row' && $max == 0) {
543
    $fixed = FALSE;
544
    foreach ($item['children'] as $id) {
545
      if ($renderer->settings['items'][$id]['width_type'] != '%') {
546
        $fixed = TRUE;
547
        break;
548
      }
549
    }
550

    
551
    if (!$fixed) {
552
      return $content;
553
    }
554
  }
555

    
556
  // If we are rendering a column and there is just one column, we don't
557
  // need to render the column unless it has a fixed_width.
558
  if (empty($renderer->admin) && $item['type'] == 'column' && $max == 0 && $item['width_type'] == '%') {
559
    return $content;
560
  }
561

    
562
  $base = $renderer->item_class[$item['type']];
563
  $output = '<div class="' . $base . ' ' . $renderer->base[$item['type']] . '-' . $id;
564
  if ($position == 0) {
565
    $output .= ' ' . $base . '-first';
566
  }
567
  if ($position == $max) {
568
    $output .= ' ' . $base . '-last';
569
  }
570
  if ($clear) {
571
    $output .= ' clearfix';
572
  }
573

    
574
  if (isset($item['class'])) {
575
    $output .= ' ' . check_plain($item['class']);
576
  }
577

    
578
  $output .= '">' . "\n";
579

    
580
  if (!empty($renderer->admin)) {
581
    $output .= panels_flexible_render_item_links($renderer, $id, $item);
582
  }
583

    
584
  $output .= '  <div class="inside ' . $base . '-inside ' . $base . '-' . $renderer->base_class . '-' . $id . '-inside';
585
  if ($position == 0) {
586
    $output .= ' ' . $base . '-inside-first';
587
  }
588
  if ($position == $max) {
589
    $output .= ' ' . $base . '-inside-last';
590
  }
591
  if ($clear) {
592
    $output .= ' clearfix';
593
  }
594

    
595
  $output .= "\">\n";
596
  $output .= $content;
597
  $output .= '  </div>' . "\n";
598
  $output .= '</div>' . "\n";
599

    
600
  return $output;
601
}
602
/**
603
 * Render a splitter div to place between the $left and $right items.
604
 *
605
 * If the right ID is NULL that means there isn't actually a box to the
606
 * right, but we need a splitter anyway. We'll mostly use info about the
607
 * left, but pretend it's 'fluid' so that the javascript won't actually
608
 * modify the right item.
609
 */
610
function panels_flexible_render_splitter($renderer, $left_id, $right_id) {
611
  $left = $renderer->settings['items'][$left_id];
612

    
613
  $left_class = $renderer->base[$left['type']] . '-' . $left_id;
614
  if ($right_id) {
615
    $right = $renderer->settings['items'][$right_id];
616
    $right_class = $renderer->base[$left['type']] . '-' . $right_id;
617
  }
618
  else {
619
    $right = $left;
620
    $right_class = $left_class;
621
  }
622

    
623
  $output = '<div tabindex="0"
624
    class="panels-flexible-splitter flexible-splitter-for-' . $left_class . '">';
625

    
626
  // Name the left object:
627
  $output .= '<span class="panels-flexible-splitter-left">';
628
  $output .= '.' . $left_class;
629
  $output .= '</span>';
630

    
631
  $output .= '<span class="panels-flexible-splitter-left-id">';
632
  $output .= $left_id;
633
  $output .= '</span>';
634

    
635
  $output .= '<span class="panels-flexible-splitter-left-width ' . $left_class . '-width">';
636
  $output .= $left['width'];
637
  $output .= '</span>';
638

    
639
  $output .= '<span class="panels-flexible-splitter-left-scale">';
640
  $output .= isset($renderer->scale[$left_id]) ? $renderer->scale[$left_id] : 1;
641
  $output .= '</span>';
642

    
643
  $output .= '<span class="panels-flexible-splitter-left-width-type">';
644
  $output .= $left['width_type'];
645
  $output .= '</span>';
646

    
647
  // Name the right object:
648
  $output .= '<span class="panels-flexible-splitter-right">';
649
  $output .= '.' . $right_class;
650
  $output .= '</span>';
651

    
652
  $output .= '<span class="panels-flexible-splitter-right-id">';
653
  $output .= $right_id;
654
  $output .= '</span>';
655

    
656
  $output .= '<span class="panels-flexible-splitter-right-width ' . $right_class . '-width">';
657
  $output .= $right['width'];
658
  $output .= '</span>';
659

    
660
  $output .= '<span class="panels-flexible-splitter-right-scale">';
661
  $output .= isset($renderer->scale[$right_id]) ? $renderer->scale[$right_id] : 1;
662
  $output .= '</span>';
663

    
664
  $output .= '<span class="panels-flexible-splitter-right-width-type">';
665
  // If there is no right, make it fluid.
666
  $output .= $right_id ? $right['width_type'] : '%';
667
  $output .= '</span>';
668

    
669
  $output .= '</div>';
670
  return $output;
671
}
672

    
673
/**
674
 * Render the dropdown links for an item.
675
 */
676
function panels_flexible_render_item_links($renderer, $id, $item) {
677
  $links = array();
678
  $remove = '';
679
  $add = '';
680
  if ($item['type'] == 'column') {
681
    $title = t('Column');
682
    $settings = t('Column settings');
683
    if (empty($item['children'])) {
684
      $remove = t('Remove column');
685
      $add = t('Add row');
686
    }
687
    else {
688
      $add = t('Add row to top');
689
      $add2 = t('Add row to bottom');
690
    }
691
  }
692
  elseif ($item['type'] == 'row') {
693
    if ($id == 'canvas') {
694
      $title = t('Canvas');
695
      $settings = t('Canvas settings');
696
    }
697
    else {
698
      $title = t('Row');
699
      $settings = t('Row settings');
700
    }
701
    if (empty($item['children'])) {
702
      if ($id != 'canvas') {
703
        $remove = t('Remove row');
704
      }
705
      $add = $item['contains'] == 'region' ? t('Add region') : t('Add column');
706
    }
707
    else {
708
      $add = $item['contains'] == 'region' ? t('Add region to left') : t('Add column to left');
709
      $add2 = $item['contains'] == 'region' ? t('Add region to right') : t('Add column to right');
710
    }
711
  }
712
  elseif ($item['type'] == 'region') {
713
    $title = t('Region');
714
    $settings = t('Region settings');
715
    $remove = t('Remove region');
716
  }
717

    
718
  if (!empty($settings)) {
719
    $links[] = array(
720
      'title' => $settings,
721
      'href' => $renderer->handler->get_url('layout', 'settings', $id),
722
      'attributes' => array('class' => array('ctools-use-modal')),
723
    );
724
  }
725
  if ($add) {
726
    $links[] = array(
727
      'title' => $add,
728
      'href' => $renderer->handler->get_url('layout', 'add', $id),
729
      'attributes' => array('class' => array('ctools-use-modal')),
730
    );
731
  }
732
  if (isset($add2)) {
733
    $links[] = array(
734
      'title' => $add2,
735
      'href' => $renderer->handler->get_url('layout', 'add', $id, 'right'),
736
      'attributes' => array('class' => array('ctools-use-modal')),
737
    );
738
  }
739
  if ($remove) {
740
    $links[] = array(
741
      'title' => $remove,
742
      'href' => $renderer->handler->get_url('layout', 'remove', $id),
743
      'attributes' => array('class' => array('use-ajax')),
744
    );
745
  }
746

    
747
  return theme('ctools_dropdown', array('title' => $title, 'links' => $links, 'class' => 'flexible-layout-only flexible-links flexible-title flexible-links-' . $id));
748
}
749
/**
750
 * Provide CSS for a flexible layout.
751
 */
752
function panels_flexible_render_css($renderer) {
753
  if ($renderer->admin) {
754
    $parent_class = '.' . $renderer->base['row'] . '-canvas';
755
  }
756
  else {
757
    $parent_class = '.' . $renderer->base['canvas'];
758
  }
759
  return panels_flexible_render_css_group($renderer, $renderer->settings['items']['canvas']['children'], $parent_class, 'column', 'canvas');
760
}
761

    
762
/**
763
 * Render the CSS for a group of items to be displayed together.
764
 *
765
 * Columns and regions, when displayed as a group, need to cooperate in
766
 * order to share margins and make sure that percent widths add up
767
 * to the right total.
768
 */
769
function panels_flexible_render_css_group($renderer, $list, $owner_id, $type, $id) {
770
  $css = array();
771

    
772
  // Start off with some generic CSS to properly pad regions.
773
  $css[$owner_id . ' .' . $renderer->item_class['region']] = array(
774
    'padding' => '0',
775
  );
776

    
777
  $css[$owner_id . ' .' . $renderer->item_class['region'] . '-inside'] = array(
778
    'padding-right' => $renderer->region_separation,
779
    'padding-left' => $renderer->region_separation,
780
  );
781

    
782
  $css[$owner_id . ' .' . $renderer->item_class['region'] . '-inside-first'] = array(
783
    'padding-left' => '0',
784
  );
785

    
786
  $css[$owner_id . ' .' . $renderer->item_class['region'] . '-inside-last'] = array(
787
    'padding-right' => '0',
788
  );
789

    
790
  $css[$owner_id . ' .' . $renderer->item_class['column']] = array(
791
    'padding' => '0',
792
  );
793

    
794
  $css[$owner_id . ' .' . $renderer->item_class['column'] . '-inside'] = array(
795
    'padding-right' => $renderer->column_separation,
796
    'padding-left' => $renderer->column_separation,
797
  );
798

    
799
  $css[$owner_id . ' .' . $renderer->item_class['column'] . '-inside-first'] = array(
800
    'padding-left' => '0',
801
  );
802

    
803
  $css[$owner_id . ' .' . $renderer->item_class['column'] . '-inside-last'] = array(
804
    'padding-right' => '0',
805
  );
806

    
807
  // And properly pad rows too:
808
  $css[$owner_id . ' .' . $renderer->item_class['row']] = array(
809
    'padding' => '0 0 ' . $renderer->row_separation . ' 0',
810
    'margin' => '0',
811
  );
812

    
813
  $css[$owner_id . ' .' . $renderer->item_class['row'] . '-last'] = array(
814
    'padding-bottom' => '0',
815
  );
816

    
817
  panels_flexible_get_css_group($css, $renderer, $list, $owner_id, $type, $id);
818

    
819
  ctools_include('css');
820
  return ctools_css_assemble($css);
821
}
822

    
823
/**
824
 * Construct an array with all of the CSS properties for a group.
825
 *
826
 * This will parse down into children and produce all of the CSS needed if you
827
 * start from the top.
828
 */
829
function panels_flexible_get_css_group(&$css, $renderer, $list, $owner_id, $type, $item_id) {
830
  if ($type != 'row') {
831
    // Go through our items and break up into right/center/right groups so we
832
    // can figure out our offsets.
833
    // right == any items on the right that are 'fixed'.
834
    // middle == all fluid items.
835
    // right == any items on the right that are 'fixed'.
836
    $left = $middle = $right = array();
837
    $left_total = $right_total = $middle_total = 0;
838
    $current = 'left';
839
    foreach ($list as $id) {
840
      if ($renderer->settings['items'][$id]['width_type'] == 'px') {
841
        // Fixed:
842
        if ($current == 'left') {
843
          $left[] = $id;
844
          $renderer->positions[$id] = 'left';
845
          $left_total += $renderer->settings['items'][$id]['width'];
846
        }
847
        else {
848
          $current = 'right';
849
          $right[] = $id;
850
          $renderer->positions[$id] = 'right';
851
          $right_total += $renderer->settings['items'][$id]['width'];
852
        }
853
      }
854
      else {
855
        // Fluid:
856
        if ($current != 'right') {
857
          $current = 'middle';
858
          $middle[] = $id;
859
          $renderer->positions[$id] = 'middle';
860
          $middle_total += $renderer->settings['items'][$id]['width'];
861
        }
862
        // Fall through: if current is 'right' and we ran into a 'fluid' then
863
        // it gets *dropped* because that is invalid.
864
      }
865
    }
866

    
867
    // Go through our right sides and create CSS.
868
    foreach ($left as $id) {
869
      $class = "." . $renderer->base[$type] . "-$id";
870
      $css[$class] = array(
871
        'position' => 'relative',
872
        'float' => 'left',
873
        'background-color' => 'transparent',
874
        'width' => $renderer->settings['items'][$id]['width'] . "px",
875
      );
876
    }
877

    
878
    // Do the same for right.
879
    $right_pixels = 0;
880

    
881
    foreach ($right as $id) {
882
      $class = "." . $renderer->base[$type] . "-$id";
883
      $css[$class] = array(
884
        'float' => 'left',
885
        'width' => $renderer->settings['items'][$id]['width'] . "px",
886
      );
887
    }
888

    
889
    $max = count($middle) - 1;
890

    
891
    if ($middle_total) {
892
      // Because we love IE so much, auto scale everything to 99%. This
893
      // means adding up the actual widths and then providing a multiplier
894
      // to each so that the total is 99%.
895
      $scale = $renderer->scale_base / $middle_total;
896
      foreach ($middle as $position => $id) {
897
        $class = "." . $renderer->base[$type] . "-$id";
898
        $css[$class] = array(
899
          'float' => 'left',
900
          'width' => number_format($renderer->settings['items'][$id]['width'] * $scale, 4, '.', '') . "%",
901
        );
902

    
903
        // Store this so we can use it later.
904
        // @todo: Store the scale, not the new width, so .js can adjust
905
        // bi-directionally.
906
        $renderer->scale[$id] = $scale;
907
      }
908
    }
909

    
910
    // If there is any total remaining, we need to offset the splitter
911
    // by this much too.
912
    if ($left_total) {
913
      // Add this even if it's 0 so we can handle removals.
914
      $css["$owner_id-inside"]['padding-left'] = '0px';
915
      if ($renderer->admin || count($middle)) {
916
        $css["$owner_id-middle"]['margin-left'] = $left_total . 'px';
917
        // IE hack!
918
        $css["* html $owner_id-left"]['left'] = $left_total . "px";
919
        // Make this one very specific to the admin CSS so that preview
920
        // does not stomp it.
921
        $css[".panel-flexible-admin $owner_id-inside"]['padding-left'] = '0px';
922
      }
923
      else {
924
        $css["$owner_id-inside"]['margin-left'] = '-' . $left_total . 'px';
925
        $css["$owner_id-inside"]['padding-left'] = $left_total . 'px';
926
        // IE hack!
927
        $css["* html $owner_id-inside"]['left'] = $left_total . "px";
928
      }
929
    }
930
    if ($right_total) {
931
      $css["$owner_id-middle"]['margin-right'] = $right_total . 'px';
932
    }
933
    $css["$owner_id-inside"]['padding-right'] = '0px';
934
  }
935

    
936
  // If the canvas has a fixed width set, and this is the canvas, fix the
937
  // width.
938
  if ($item_id == 'canvas') {
939
    $item = $renderer->settings['items'][$item_id];
940

    
941
    if (!empty($item['fixed_width']) && intval($item['fixed_width'])) {
942
      $css['.' . $renderer->base['canvas']]['width'] = intval($item['fixed_width']) . 'px';
943
    }
944
    else {
945
      $css['.' . $renderer->base['canvas']]['width'] = 'auto';
946
    }
947
  }
948

    
949
  // Go through each item and process children.
950
  foreach ($list as $id) {
951
    $item = $renderer->settings['items'][$id];
952
    if (empty($item['children'])) {
953
      continue;
954
    }
955

    
956
    if ($type == 'column') {
957
      // Columns can only contain rows.
958
      $child_type = 'row';
959
    }
960
    else {
961
      $child_type = isset($item['contains']) ? $item['contains'] : 'region';
962
    }
963

    
964
    $class = "." . $renderer->base[$type] . "-$id";
965
    panels_flexible_get_css_group($css, $renderer, $item['children'], $class, $child_type, $id);
966
  }
967
}
968

    
969
/**
970
 * AJAX responder to edit flexible settings for an item.
971
 *
972
 * @param object $handler
973
 *   The display renderer handler object.
974
 * @param mixed $id
975
 *   Id for the panel.
976
 */
977
function panels_ajax_flexible_edit_settings($handler, $id) {
978
  $settings = &$handler->display->layout_settings;
979
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
980

    
981
  if (empty($settings['items'][$id])) {
982
    ctools_modal_render(t('Error'), t('Invalid item id.'));
983
  }
984

    
985
  $item = &$settings['items'][$id];
986
  $siblings = array();
987

    
988
  if ($id != 'canvas') {
989
    $siblings = $settings['items'][$item['parent']]['children'];
990
  }
991

    
992
  switch ($item['type']) {
993
    case 'column':
994
      $title = t('Configure column');
995
      break;
996

    
997
    case 'row':
998
      if ($id == 'canvas') {
999
        $title = t('Configure canvas');
1000
      }
1001
      else {
1002
        $title = t('Configure row');
1003
      }
1004
      break;
1005

    
1006
    case 'region':
1007
      $title = t('Configure region');
1008
      break;
1009
  }
1010

    
1011
  $form_state = array(
1012
    'display' => &$handler->display,
1013
    'item' => &$item,
1014
    'id' => $id,
1015
    'siblings' => $siblings,
1016
    'settings' => &$settings,
1017
    'ajax' => TRUE,
1018
    'title' => $title,
1019
    'op' => 'edit',
1020
  );
1021

    
1022
  $output = ctools_modal_form_wrapper('panels_flexible_config_item_form', $form_state);
1023
  if (!empty($form_state['executed'])) {
1024
    // If the width type changed then other nearby items will have
1025
    // to have their widths adjusted.
1026
    panels_edit_cache_set($handler->cache);
1027

    
1028
    $css_id = isset($handler->display->css_id) ? $handler->display->css_id : '';
1029
    $renderer = panels_flexible_create_renderer(TRUE, $css_id, array(), $settings, $handler->display, $handler->plugins['layout'], $handler);
1030

    
1031
    $output = array();
1032
    // If the item is a region, replace the title.
1033
    $class = $renderer->base[$item['type']] . '-' . $id;
1034
    if ($item['type'] == 'region') {
1035
      $output[] = ajax_command_replace(".$class h2.label",
1036
        '<h2 class="label">' . check_plain($item['title']) . '</h2>');
1037
    }
1038

    
1039
    // Rerender our links in case something changed.
1040
    $output[] = ajax_command_replace('.flexible-links-' . $id,
1041
      panels_flexible_render_item_links($renderer, $id, $item));
1042

    
1043
    // If editing the canvas, reset the CSS width.
1044
    if ($id == 'canvas') {
1045
      // Update canvas CSS.
1046
      $css = array(
1047
        '.' . $renderer->item_class['column'] . '-inside' => array(
1048
          'padding-left' => $renderer->column_separation,
1049
          'padding-right' => $renderer->column_separation,
1050
        ),
1051
        '.' . $renderer->item_class['region'] . '-inside' => array(
1052
          'padding-left' => $renderer->region_separation,
1053
          'padding-right' => $renderer->region_separation,
1054
        ),
1055
        '.' . $renderer->item_class['row'] => array(
1056
          'padding-bottom' => $renderer->row_separation,
1057
        ),
1058
      );
1059
      if (!empty($item['fixed_width']) && intval($item['fixed_width'])) {
1060
        $css['.' . $renderer->base['canvas']] = array('width' => intval($item['fixed_width']) . 'px');
1061
      }
1062
      else {
1063
        $css['.' . $renderer->base['canvas']] = array('width' => 'auto');
1064
      }
1065
      foreach ($css as $selector => $data) {
1066
        $output[] = ajax_command_css($selector, $data);
1067
      }
1068
    }
1069

    
1070
    $output[] = ctools_modal_command_dismiss();
1071
  }
1072

    
1073
  $handler->commands = $output;
1074
}
1075

    
1076
/**
1077
 * Configure a row, column or region on the flexible page.
1078
 */
1079
function panels_flexible_config_item_form($form, &$form_state) {
1080
  $display = &$form_state['display'];
1081
  $item = &$form_state['item'];
1082
  $siblings = &$form_state['siblings'];
1083
  $settings = &$form_state['settings'];
1084
  $id = &$form_state['id'];
1085

    
1086
  if ($item['type'] == 'region') {
1087
    $form['title'] = array(
1088
      '#title' => t('Region title'),
1089
      '#type' => 'textfield',
1090
      '#default_value' => $item['title'],
1091
      '#required' => TRUE,
1092
    );
1093
  }
1094

    
1095
  if ($id == 'canvas') {
1096
    $form['class'] = array(
1097
      '#title' => t('Canvas class'),
1098
      '#type' => 'textfield',
1099
      '#default_value' => isset($item['class']) ? $item['class'] : '',
1100
      '#description' => t('This class will the primary class for this layout. It will also be appended to all column, row and region_classes to ensure that layouts within layouts will not inherit CSS from each other. If left blank, the name of the layout or ID of the display will be used.'),
1101
    );
1102

    
1103
    $form['column_class'] = array(
1104
      '#title' => t('Column class'),
1105
      '#type' => 'textfield',
1106
      '#default_value' => isset($item['column_class']) ? $item['column_class'] : '',
1107
      '#description' => t('This class will be applied to all columns of the layout. If left blank this will be panels-flexible-column.'),
1108
    );
1109

    
1110
    $form['row_class'] = array(
1111
      '#title' => t('Row class'),
1112
      '#type' => 'textfield',
1113
      '#default_value' => isset($item['row_class']) ? $item['row_class'] : '',
1114
      '#description' => t('This class will be applied to all rows of the layout. If left blank this will be panels-flexible-row.'),
1115
    );
1116

    
1117
    $form['region_class'] = array(
1118
      '#title' => t('Region class'),
1119
      '#type' => 'textfield',
1120
      '#default_value' => isset($item['region_class']) ? $item['region_class'] : '',
1121
      '#description' => t('This class will be applied to all regions of the layout. If left blank this will be panels-flexible-region.'),
1122
    );
1123

    
1124
    $form['no_scale'] = array(
1125
      '#type' => 'checkbox',
1126
      '#title' => t('Scale fluid widths for IE6'),
1127
      '#description' => t('IE6 does not do well with 100% widths. If checked, width will be scaled to 99% to compensate.'),
1128
      '#default_value' => empty($item['no_scale']),
1129
    );
1130

    
1131
    $form['fixed_width'] = array(
1132
      '#type' => 'textfield',
1133
      '#title' => t('Fixed width'),
1134
      '#description' => t('If a value is entered, the layout canvas will be fixed to the given pixel width.'),
1135
      '#default_value' => isset($item['fixed_width']) ? $item['fixed_width'] : '',
1136
    );
1137

    
1138
    $form['column_separation'] = array(
1139
      '#type' => 'textfield',
1140
      '#title' => t('Column separation'),
1141
      '#description' => t('How much padding to put on columns that are that are next to other columns. Note that this is put on both columns so the real amount is doubled.'),
1142
      '#default_value' => isset($item['column_separation']) ? $item['column_separation'] : '0.5em',
1143
    );
1144

    
1145
    $form['region_separation'] = array(
1146
      '#type' => 'textfield',
1147
      '#title' => t('Region separation'),
1148
      '#description' => t('How much padding to put on regions that are that are next to other regions. Note that this is put on both regions so the real amount is doubled.'),
1149
      '#default_value' => isset($item['region_separation']) ? $item['region_separation'] : '0.5em',
1150
    );
1151

    
1152
    $form['row_separation'] = array(
1153
      '#type' => 'textfield',
1154
      '#title' => t('Row separation'),
1155
      '#description' => t('How much padding to put on beneath rows to separate them from each other. Because this is placed only on the bottom, not hte top, this is NOT doubled like column/region separation.'),
1156
      '#default_value' => isset($item['row_separation']) ? $item['row_separation'] : '0.5em',
1157
    );
1158
  }
1159
  else {
1160
    $form['class'] = array(
1161
      '#title' => t('CSS class'),
1162
      '#type' => 'textfield',
1163
      '#default_value' => isset($item['class']) ? $item['class'] : '',
1164
      '#description' => t('Enter a CSS class that will be used. This can be used to apply automatic styling from your theme, for example.'),
1165
    );
1166

    
1167
    if ($item['type'] != 'row') {
1168
      // Test to see if there are fluid items to the left or the right. If there
1169
      // are fluid items on both sides, this item cannot be set to fixed.
1170
      $left = $right = FALSE;
1171
      $current = 'left';
1172
      foreach ($siblings as $sibling) {
1173
        if ($sibling == $id) {
1174
          $current = 'right';
1175
        }
1176
        elseif ($settings['items'][$sibling]['width_type'] == '%') {
1177
          // Indirection.
1178
          $$current = TRUE;
1179
        }
1180
      }
1181

    
1182
      $form['width_type'] = array(
1183
        '#type' => 'select',
1184
        '#title' => t('Width'),
1185
        '#default_value' => $item['width_type'],
1186
        '#options' => array(
1187
          '%' => t('Fluid'),
1188
          'px' => t('Fixed'),
1189
        ),
1190
        '#disabled' => TRUE,
1191
      );
1192
    }
1193
    else {
1194
      $form['contains'] = array(
1195
        '#type' => 'select',
1196
        '#title' => t('Contains'),
1197
        '#default_value' => $item['contains'],
1198
        '#options' => array(
1199
          'region' => t('Regions'),
1200
          'column' => t('Columns'),
1201
        ),
1202
      );
1203

    
1204
      if (!empty($item['children'])) {
1205
        $form['contains']['#disabled'] = TRUE;
1206
        $form['contains']['#value'] = $item['contains'];
1207
        $form['contains']['#description'] = t('You must remove contained items to change the row container type.');
1208
      }
1209
    }
1210
  }
1211

    
1212
  $form['hide_empty'] = array(
1213
    '#title' => t('Hide element if empty'),
1214
    '#type' => 'checkbox',
1215
    '#default_value' => !empty($item['hide_empty']) ? 1 : 0,
1216
  );
1217

    
1218
  $form['save'] = array(
1219
    '#type' => 'submit',
1220
    '#value' => t('Save'),
1221
  );
1222

    
1223
  return $form;
1224
}
1225

    
1226
/**
1227
 * Submit handler for editing a flexible item.
1228
 */
1229
function panels_flexible_config_item_form_submit(&$form, &$form_state) {
1230
  $item = &$form_state['item'];
1231
  if ($item['type'] == 'region') {
1232
    $item['title'] = $form_state['values']['title'];
1233
  }
1234

    
1235
  $item['class'] = $form_state['values']['class'];
1236

    
1237
  if ($form_state['id'] == 'canvas') {
1238
    $item['column_class'] = $form_state['values']['column_class'];
1239
    $item['row_class'] = $form_state['values']['row_class'];
1240
    $item['region_class'] = $form_state['values']['region_class'];
1241
    // Reverse this as the checkbox is backward from how we actually store
1242
    // it to make it simpler to default to scaling.
1243
    $item['no_scale'] = !$form_state['values']['no_scale'];
1244
    $item['fixed_width'] = $form_state['values']['fixed_width'];
1245
    $item['column_separation'] = $form_state['values']['column_separation'];
1246
    $item['region_separation'] = $form_state['values']['region_separation'];
1247
    $item['row_separation'] = $form_state['values']['row_separation'];
1248
  }
1249
  elseif ($item['type'] != 'row') {
1250
    $item['width_type'] = $form_state['values']['width_type'];
1251
  }
1252
  else {
1253
    $item['contains'] = $form_state['values']['contains'];
1254
  }
1255
  $item['hide_empty'] = $form_state['values']['hide_empty'];
1256

    
1257
}
1258

    
1259
/**
1260
 * AJAX responder to add a new row, column or region to a flexible layout.
1261
 */
1262
function panels_ajax_flexible_edit_add($handler, $id, $location = 'left') {
1263
  ctools_include('modal');
1264
  ctools_include('ajax');
1265
  $settings = &$handler->display->layout_settings;
1266
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
1267

    
1268
  if (empty($settings['items'][$id])) {
1269
    ctools_modal_render(t('Error'), t('Invalid item id.'));
1270
  }
1271

    
1272
  $parent = &$settings['items'][$id];
1273

    
1274
  switch ($parent['type']) {
1275
    case 'column':
1276
      $title = t('Add row');
1277
      // Create the new item with defaults.
1278
      $item = array(
1279
        'type' => 'row',
1280
        'contains' => 'region',
1281
        'children' => array(),
1282
        'parent' => $id,
1283
      );
1284
      break;
1285

    
1286
    case 'row':
1287
      switch ($parent['contains']) {
1288
        case 'region':
1289
          $title = $location == 'left' ? t('Add region to left') : t('Add region to right');
1290
          $item = array(
1291
            'type' => 'region',
1292
            'title' => '',
1293
            'width' => 100,
1294
            'width_type' => '%',
1295
            'parent' => $id,
1296
          );
1297
          break;
1298

    
1299
        case 'column':
1300
          $title = $location == 'left' ? t('Add column to left') : t('Add column to right');
1301
          $item = array(
1302
            'type' => 'column',
1303
            'width' => 100,
1304
            'width_type' => '%',
1305
            'parent' => $id,
1306
            'children' => array(),
1307
          );
1308
          break;
1309
      }
1310
      // Create the new item with defaults.
1311
      break;
1312

    
1313
    case 'region':
1314
      // Cannot add items to regions.
1315
      break;
1316
  }
1317

    
1318
  $form_state = array(
1319
    'display' => &$handler->display,
1320
    'parent' => &$parent,
1321
    'item' => &$item,
1322
    'id' => $id,
1323
    'settings' => &$settings,
1324
    'ajax' => TRUE,
1325
    'title' => $title,
1326
    'location' => $location,
1327
  );
1328

    
1329
  $output = ctools_modal_form_wrapper('panels_flexible_add_item_form', $form_state);
1330
  if (!empty($form_state['executed'])) {
1331
    // If the width type changed then other nearby items will have
1332
    // to have their widths adjusted.
1333
    panels_edit_cache_set($handler->cache);
1334
    $output = array();
1335

    
1336
    $css_id = isset($handler->display->css_id) ? $handler->display->css_id : '';
1337
    // Create a renderer object so we can render our new stuff.
1338
    $renderer = panels_flexible_create_renderer(TRUE, $css_id, array(), $settings, $handler->display, $handler->plugins['layout'], $handler);
1339

    
1340
    $content = '';
1341
    if ($item['type'] == 'region') {
1342
      $handler->plugins['layout']['regions'][$form_state['key']] = $item['title'];
1343

    
1344
      $content = $handler->render_region($form_state['key'], array());
1345

    
1346
      // Manually add the hidden field that our region uses to store pane info.
1347
      $content .= '<input type="hidden" name="panel[pane][' .
1348
        $form_state['key'] . ']" value="" />';
1349

    
1350
    }
1351
    else {
1352
      // We need to make sure the left/middle/right divs exist inside this
1353
      // so that more stuff can be added inside it as needed.
1354
      foreach (array('left', 'middle', 'right') as $position) {
1355
        if (!empty($content) || $renderer->admin) {
1356
          $content .= '<div class="' . $renderer->base[$item['type']] . '-' . $form_state['key'] . '-' . $position . '"></div>';
1357
        }
1358
      }
1359

    
1360
    }
1361

    
1362
    // Render the item.
1363
    $parent_class = $renderer->base[$parent['type']] . '-' . $id;
1364
    $item_output = panels_flexible_render_item($renderer, $item, $content, $form_state['key'], 0, 0, $item['type'] == 'row');
1365

    
1366
    // Get all the CSS necessary for the entire row (as width adjustments may
1367
    // have cascaded).
1368
    $css = array();
1369
    panels_flexible_get_css_group($css, $renderer, $parent['children'], '.' . $parent_class, $item['type'], $id);
1370

    
1371
    $position = isset($renderer->positions[$form_state['key']]) ? $renderer->positions[$form_state['key']] : 'middle';
1372
    // If there's a nearby item, add the splitter and rewrite the width
1373
    // of the nearby item as it probably got adjusted.
1374
    // The blocks of code in this else look very similar but are not actually
1375
    // duplicated because the order changes based on left or right.
1376
    switch ($position) {
1377
      case 'left':
1378
        if ($location == 'left') {
1379
          $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], $form_state['sibling']);
1380
          $output[] = ajax_command_prepend('#panels-dnd-main .' . $parent_class . '-left', $item_output);
1381
        }
1382
        elseif ($location == 'right') {
1383
          // If we are adding to the right side of the left box, there is
1384
          // a splitter that we have to remove; then we add our box normally,
1385
          // and then add a new splitter for just our guy.
1386
          $output[] = ajax_command_remove('panels-flexible-splitter-for-' . $renderer->base[$item['type']] . '-' . $form_state['key']);
1387
          $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) . $item_output;
1388
          $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], NULL);
1389
          $output[] = ajax_command_append('#panels-dnd-main .' . $parent_class . '-left', $item_output);
1390
        }
1391
        break;
1392

    
1393
      case 'right':
1394
        if (!empty($form_state['sibling'])) {
1395
          $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) . $item_output;
1396
        }
1397
        $output[] = ajax_command_append('#panels-dnd-main .' . $parent_class . '-right', $item_output);
1398
        break;
1399

    
1400
      case 'middle':
1401
        if ($location == 'left') {
1402
          if (!empty($form_state['sibling'])) {
1403
            $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], $form_state['sibling']);
1404
          }
1405
          $output[] = ajax_command_prepend('#panels-dnd-main .' . $parent_class . '-middle', $item_output);
1406
        }
1407
        else {
1408
          if (!empty($form_state['sibling'])) {
1409
            $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) . $item_output;
1410
          }
1411
          $output[] = ajax_command_append('#panels-dnd-main .' . $parent_class . '-middle', $item_output);
1412
        }
1413
        break;
1414

    
1415
    }
1416

    
1417
    // Send our fix height command.
1418
    $output[] = array('command' => 'flexible_fix_height');
1419

    
1420
    if (!empty($form_state['sibling'])) {
1421
      $sibling_width = '#panels-dnd-main .' . $renderer->base[$item['type']] . '-' . $form_state['sibling'] . '-width';
1422
      $output[] = array(
1423
        'command' => 'flexible_set_width',
1424
        'selector' => $sibling_width,
1425
        'width' => $settings['items'][$form_state['sibling']]['width'],
1426
      );
1427
    }
1428
    foreach ($css as $selector => $data) {
1429
      $output[] = ajax_command_css($selector, $data);
1430
    }
1431

    
1432
    // Rerender our parent item links:
1433
    $output[] = ajax_command_replace('.flexible-links-' . $id,
1434
      panels_flexible_render_item_links($renderer, $id, $parent));
1435

    
1436
    $output[] = array(
1437
      'command' => 'flexible_fix_firstlast',
1438
      'selector' => '.' . $parent_class . '-inside',
1439
      'base' => 'panels-flexible-' . $item['type'],
1440
    );
1441

    
1442
    $output[] = ctools_modal_command_dismiss();
1443
  }
1444

    
1445
  $handler->commands = $output;
1446
}
1447
/**
1448
 * Form to add a row, column or region to a flexible layout.
1449
 */
1450
function panels_flexible_add_item_form($form, &$form_state) {
1451
  $display = &$form_state['display'];
1452
  $item = &$form_state['item'];
1453
  $parent = &$form_state['parent'];
1454
  $settings = &$form_state['settings'];
1455
  $location = &$form_state['location'];
1456
  $id = &$form_state['id'];
1457

    
1458
  if ($item['type'] == 'region') {
1459
    $form['title'] = array(
1460
      '#title' => t('Region title'),
1461
      '#type' => 'textfield',
1462
      '#default_value' => $item['title'],
1463
      '#required' => TRUE,
1464
    );
1465
  }
1466

    
1467
  $form['class'] = array(
1468
    '#title' => t('CSS Class'),
1469
    '#type' => 'textfield',
1470
    '#default_value' => isset($item['class']) ? $item['class'] : '',
1471
    '#description' => t('Enter a CSS class that will be used. This can be used to apply automatic styling from your theme, for example.'),
1472
  );
1473

    
1474
  if ($item['type'] != 'row') {
1475
    // If there is a 'fixed' type on the side we're adding to, then this
1476
    // must also be fixed. Otherwise it can be either and should default to
1477
    // fluid.
1478
    $restrict = FALSE;
1479

    
1480
    if (!empty($parent['children'])) {
1481
      if ($location == 'left') {
1482
        $sibling = reset($parent['children']);
1483
      }
1484
      else {
1485
        $sibling = end($parent['children']);
1486
      }
1487
      if ($settings['items'][$sibling]['width_type'] == 'px') {
1488
        $restrict = TRUE;
1489
        $item['width_type'] = 'px';
1490
      }
1491
    }
1492

    
1493
    $form['width_type'] = array(
1494
      '#type' => 'select',
1495
      '#title' => t('Width'),
1496
      '#default_value' => $item['width_type'],
1497
      '#options' => array(
1498
        '%' => t('Fluid'),
1499
        'px' => t('Fixed'),
1500
      ),
1501
      '#disabled' => $restrict,
1502
    );
1503
    if ($restrict) {
1504
      // This forces the value because disabled items don't always send
1505
      // their data back.
1506
      $form['width_type']['#value'] = $item['width_type'];
1507
      $form['width_type']['#description'] = t('Items cannot be set to fluid if there are fixed items already on that side.');
1508
    }
1509
  }
1510
  else {
1511
    $form['contains'] = array(
1512
      '#type' => 'select',
1513
      '#title' => t('Contains'),
1514
      '#default_value' => $item['contains'],
1515
      '#options' => array(
1516
        'region' => t('Regions'),
1517
        'column' => t('Columns'),
1518
      ),
1519
    );
1520
  }
1521

    
1522
  $form['hide_empty'] = array(
1523
    '#title' => t('Hide element if empty'),
1524
    '#type' => 'checkbox',
1525
    '#default_value' => 0,
1526
  );
1527

    
1528
  $form['save'] = array(
1529
    '#type' => 'submit',
1530
    '#value' => t('Save'),
1531
  );
1532

    
1533
  return $form;
1534
}
1535

    
1536
/**
1537
 * Submit handler for editing a flexible item.
1538
 */
1539
function panels_flexible_add_item_form_submit(&$form, &$form_state) {
1540
  $item = &$form_state['item'];
1541
  $parent = &$form_state['parent'];
1542
  $location = &$form_state['location'];
1543
  $settings = &$form_state['settings'];
1544

    
1545
  $item['class'] = $form_state['values']['class'];
1546

    
1547
  if ($item['type'] == 'region') {
1548
    $item['title'] = $form_state['values']['title'];
1549
  }
1550

    
1551
  if ($item['type'] != 'row') {
1552
    $item['width_type'] = $form_state['values']['width_type'];
1553
  }
1554
  else {
1555
    $item['contains'] = $form_state['values']['contains'];
1556
  }
1557

    
1558
  $item['hide_empty'] = $form_state['values']['hide_empty'];
1559

    
1560
  if ($item['type'] == 'region') {
1561
    // Derive the region key from the title.
1562
    $key = preg_replace("/[^a-z0-9]/", '_', drupal_strtolower($item['title']));
1563
    while (isset($settings['items'][$key])) {
1564
      $key .= '_';
1565
    }
1566
    $form_state['key'] = $key;
1567
  }
1568
  else {
1569
    $form_state['key'] = $key = max(array_keys($settings['items'])) + 1;
1570
  }
1571

    
1572
  $form_state['sibling'] = NULL;
1573
  if ($item['type'] != 'row' && !empty($parent['children'])) {
1574
    // Figure out what the width should be and adjust our sibling if
1575
    // necessary.
1576
    if ($location == 'left') {
1577
      $form_state['sibling'] = reset($parent['children']);
1578
    }
1579
    else {
1580
      $form_state['sibling'] = end($parent['children']);
1581

    
1582
    }
1583

    
1584
    // If there is no sibling, or the sibling is of a different type,
1585
    // the default 100 will work for either fixed or fluid.
1586
    if ($form_state['sibling'] && $settings['items'][$form_state['sibling']]['width_type'] == $item['width_type']) {
1587
      // Steal half of the sibling's space.
1588
      $width = $settings['items'][$form_state['sibling']]['width'] / 2;
1589
      $settings['items'][$form_state['sibling']]['width'] = $width;
1590
      $item['width'] = $width;
1591
    }
1592
  }
1593

    
1594
  // Place the item.
1595
  $settings['items'][$key] = $item;
1596
  if ($location == 'left') {
1597
    array_unshift($parent['children'], $key);
1598
  }
1599
  else {
1600
    $parent['children'][] = $key;
1601
  }
1602
}
1603

    
1604
/**
1605
 * Panels remove AJAX responder.
1606
 *
1607
 * Removes an existing row, column or region from a flexible layout.
1608
 */
1609
function panels_ajax_flexible_edit_remove($handler, $id) {
1610
  $settings = &$handler->display->layout_settings;
1611
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
1612

    
1613
  if (empty($settings['items'][$id])) {
1614
    ajax_render_error(t('Invalid item id.'));
1615
  }
1616

    
1617
  $item = &$settings['items'][$id];
1618
  $css_id = isset($handler->display->css_id) ? $handler->display->css_id : '';
1619
  // Create a renderer object so we can render our new stuff.
1620
  $renderer = panels_flexible_create_renderer(TRUE, $css_id, array(), $settings, $handler->display, $handler->plugins['layout'], $handler);
1621
  $siblings = &$settings['items'][$item['parent']]['children'];
1622
  $parent_class = '.' . $renderer->base[$settings['items'][$item['parent']]['type']] . '-' . $item['parent'];
1623

    
1624
  // Find the offset of our array. This will also be the key because
1625
  // this is a simple array.
1626
  $offset = array_search($id, $siblings);
1627

    
1628
  // Only bother with this stuff if our item is fluid, since fixed is
1629
  // as fixed does.
1630
  if ($item['type'] != 'row') {
1631
    if (isset($siblings[$offset + 1])) {
1632
      $next = $siblings[$offset + 1];
1633
    }
1634
    if (isset($siblings[$offset - 1])) {
1635
      $prev = $siblings[$offset - 1];
1636
    }
1637

    
1638
    if ($item['width_type'] == '%') {
1639
      // First, try next.
1640
      if (isset($next) && $settings['items'][$next]['width_type'] == '%') {
1641
        $settings['items'][$next]['width'] += $item['width'];
1642
      }
1643
      // If that failed, try the previous one.
1644
      elseif (isset($prev) && $settings['items'][$prev]['width_type'] == '%') {
1645
        $settings['items'][$prev]['width'] += $item['width'];
1646
      }
1647
    }
1648
    // Not sure what happens if they both failed. Maybe nothing.
1649
  }
1650

    
1651
  // Remove the item.
1652
  array_splice($siblings, $offset, 1);
1653

    
1654
  unset($settings['items'][$id]);
1655

    
1656
  // Save our new state.
1657
  panels_edit_cache_set($handler->cache);
1658
  $class = $renderer->base[$item['type']] . '-' . $id;
1659
  $output = array();
1660

    
1661
  $output[] = ajax_command_remove('#panels-dnd-main .' . $class);
1662

    
1663
  // Regenerate the CSS for siblings.
1664
  if (!empty($siblings)) {
1665
    // Get all the CSS necessary for the entire row (as width adjustments may
1666
    // have cascaded).
1667
    $css = array();
1668
    panels_flexible_get_css_group($css, $renderer, $siblings, $parent_class, $item['type'], $item['parent']);
1669
    foreach ($css as $selector => $data) {
1670
      $output[] = ajax_command_css($selector, $data);
1671
    }
1672
  }
1673

    
1674
  // There are potentially two splitters linked to this item to be removed.
1675
  if (!empty($prev)) {
1676
    $output[] = ajax_command_remove('.flexible-splitter-for-' . $renderer->base[$item['type']] . '-' . $prev);
1677
  }
1678

    
1679
  // Try to remove the 'next' one even if there isn't a $next.
1680
  $output[] = ajax_command_remove('.flexible-splitter-for-' . $renderer->base[$item['type']] . '-' . $id);
1681

    
1682
  if (!empty($prev) && !empty($next)) {
1683
    // Add a new splitter that links $prev and $next:
1684
    $splitter = panels_flexible_render_splitter($renderer, $prev, $next);
1685
    $prev_class = '#panels-dnd-main .' . $renderer->base[$item['type']] . '-' . $prev;
1686
    $output[] = ajax_command_after($prev_class, $splitter);
1687
    // Send our fix height command.
1688
    $output[] = array('command' => 'flexible_fix_height');
1689
  }
1690
  // Rerender our parent item links:
1691
  $output[] = ajax_command_replace('.flexible-links-' . $item['parent'],
1692
    panels_flexible_render_item_links($renderer, $item['parent'], $settings['items'][$item['parent']]));
1693

    
1694
  $output[] = array(
1695
    'command' => 'flexible_fix_firstlast',
1696
    'selector' => $parent_class . '-inside',
1697
    'base' => 'panels-flexible-' . $item['type'],
1698
  );
1699

    
1700
  $handler->commands = $output;
1701
}
1702

    
1703
/**
1704
 * AJAX responder to store resize information when the user adjusts splitter.
1705
 */
1706
function panels_ajax_flexible_edit_resize($handler) {
1707
  ctools_include('ajax');
1708
  $settings = &$handler->display->layout_settings;
1709
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
1710

    
1711
  $settings['items'][$_POST['left']]['width'] = $_POST['left_width'];
1712
  if (!empty($_POST['right']) && $_POST['right'] != $_POST['left']) {
1713
    $settings['items'][$_POST['right']]['width'] = $_POST['right_width'];
1714
  }
1715

    
1716
  // Save our new state.
1717
  panels_edit_cache_set($handler->cache);
1718

    
1719
  $handler->commands = array('ok');
1720
}
1721

    
1722
/**
1723
 * AJAX form to bring up the "reuse" modal.
1724
 */
1725
function panels_ajax_flexible_edit_reuse($handler) {
1726
  $settings = &$handler->display->layout_settings;
1727
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
1728

    
1729
  $form_state = array(
1730
    'display' => &$handler->display,
1731
    'settings' => &$settings,
1732
    'ajax' => TRUE,
1733
    'title' => t('Save this layout for reuse'),
1734
  );
1735

    
1736
  $output = ctools_modal_form_wrapper('panels_flexible_reuse_form', $form_state);
1737
  if (!empty($form_state['executed'])) {
1738
    // Create the new layout.
1739
    ctools_include('export');
1740
    $layout = ctools_export_crud_new('panels_layout');
1741
    $layout->plugin = 'flexible';
1742
    $layout->name = $form_state['values']['name'];
1743
    $layout->admin_title = $form_state['values']['admin_title'];
1744
    $layout->admin_description = $form_state['values']['admin_description'];
1745
    $layout->category = $form_state['values']['category'];
1746
    $layout->settings = $handler->display->layout_settings;
1747

    
1748
    // Save it.
1749
    ctools_export_crud_save('panels_layout', $layout);
1750

    
1751
    if (empty($form_state['values']['keep'])) {
1752
      // Set the actual layout_settings to now use the newly minted layout:
1753
      $handler->display->layout = 'flexible:' . $layout->name;
1754
      $handler->display->layout_settings = array();
1755

    
1756
      // Save our new state.
1757
      panels_edit_cache_set($handler->cache);
1758
    }
1759

    
1760
    // Dismiss the modal.
1761
    $output[] = ctools_modal_command_dismiss();
1762
  }
1763

    
1764
  $handler->commands = $output;
1765
}
1766

    
1767
function panels_flexible_reuse_form($form, &$form_state) {
1768
  $form['markup'] = array(
1769
    '#prefix' => '<div class="description">',
1770
    '#suffix' => '</div>',
1771
    '#value' => t('If you save this layout for reuse it will appear in the list of reusable layouts at admin/structure/panels/layouts, and you will need to go there to edit it. This layout will then become an option for all future panels you make.'),
1772
  );
1773

    
1774
  $form['admin_title'] = array(
1775
    '#type' => 'textfield',
1776
    '#title' => t('Administrative title'),
1777
    '#description' => t('This will appear in the administrative interface to easily identify it.'),
1778
  );
1779

    
1780
  $form['name'] = array(
1781
    '#type' => 'machine_name',
1782
    '#title' => t('Machine name'),
1783
    '#description' => t('The machine readable name of this layout. It must be unique, and it must contain only alphanumeric characters and underscores. Once created, you will not be able to change this value!'),
1784
    '#machine_name' => array(
1785
      'exists' => 'panels_flexible_edit_name_exists',
1786
      'source' => array('admin_title'),
1787
    ),
1788
  );
1789

    
1790
  $form['category'] = array(
1791
    '#type' => 'textfield',
1792
    '#title' => t('Category'),
1793
    '#description' => t('What category this layout should appear in. If left blank the category will be "Miscellaneous".'),
1794
  );
1795

    
1796
  $form['admin_description'] = array(
1797
    '#type' => 'textarea',
1798
    '#title' => t('Administrative description'),
1799
    '#description' => t('A description of what this layout is, does or is for, for administrative use.'),
1800
  );
1801

    
1802
  $form['keep'] = array(
1803
    '#type' => 'checkbox',
1804
    '#title' => t('Keep current panel layout flexible'),
1805
    '#description' => t('If checked, this panel will continue to use a generic flexible layout and will not use the saved layout. Use this option if you wish to clone this layout.'),
1806
  );
1807

    
1808
  $form['submit'] = array(
1809
    '#type' => 'submit',
1810
    '#value' => t('Save'),
1811
  );
1812

    
1813
  return $form;
1814
}
1815

    
1816
function panels_flexible_reuse_form_validate(&$form, &$form_state) {
1817
  if (empty($form_state['values']['name'])) {
1818
    form_error($form['name'], t('You must choose a machine name.'));
1819
  }
1820

    
1821
  ctools_include('export');
1822
  $test = ctools_export_crud_load('panels_layout', $form_state['values']['name']);
1823
  if ($test) {
1824
    form_error($form['name'], t('That name is used by another layout: @layout', array('@layout' => $test->admin_title)));
1825
  }
1826

    
1827
  // Ensure name fits the rules:
1828
  if (preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['name'])) {
1829
    form_error($form['name'], t('Name must be alphanumeric or underscores only.'));
1830
  }
1831
}
1832

    
1833
/**
1834
 * Test for #machine_name type to see if an export exists.
1835
 */
1836
function panels_flexible_edit_name_exists($name, $element, &$form_state) {
1837
  ctools_include('export');
1838
  $plugin = $form_state['plugin'];
1839

    
1840
  return (empty($form_state['item']->export_ui_allow_overwrite) && ctools_export_crud_load($plugin['schema'], $name));
1841
}