Projet

Général

Profil

Paste
Télécharger (61 ko) Statistiques
| Branche: | Révision:

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

1
<?php
2

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

    
21
  // Reuisable layout Builder specific directives
22
  'builder' => TRUE,
23
  'builder tab title' => 'Add flexible layout', // menu so translated elsewhere
24

    
25
  'get child' => 'panels_flexible_get_sublayout',
26
  'get children' => 'panels_flexible_get_sublayouts',
27

    
28
  // Define ajax callbacks
29
  'ajax' => array(
30
    'settings' => 'panels_ajax_flexible_edit_settings',
31
    'add' => 'panels_ajax_flexible_edit_add',
32
    'remove' => 'panels_ajax_flexible_edit_remove',
33
    'resize' => 'panels_ajax_flexible_edit_resize',
34
    'reuse' => 'panels_ajax_flexible_edit_reuse',
35
  ),
36
);
37

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

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

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

    
78
  return $layouts;
79
}
80

    
81
/**
82
 * Convert settings from old style to new, or provide defaults for
83
 * empty settings.
84
 * @param <type> $settings
85
 */
86
function panels_flexible_convert_settings(&$settings, &$layout) {
87
  // This indicates that this is a layout that they used the checkbox
88
  // on. The layout is still 'flexible' but it's actually pointing
89
  // to another stored one and we have to load it.
90
  if (!empty($settings['layout'])) {
91
    $layout = panels_get_layout('flexible:' . $settings['layout']);
92
  }
93

    
94
  if (!empty($layout['layout'])) {
95
    $settings = $layout['layout']->settings;
96
    if ($settings) {
97
      return $settings;
98
    }
99
  }
100

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

    
171
    $settings['items']['canvas']['children'][] = 'main';
172

    
173
    if (!empty($old['sidebars']['right'])) {
174
      $settings['items']['canvas']['children'][] = 'sidebar-right';
175
      $settings['items']['sidebar-right'] = array(
176
        'type' => 'column',
177
        'width' => $old['sidebars']['right_width'],
178
        'width_type' => $old['sidebars']['width_type'],
179
        'children' => array('sidebar-right-row'),
180
        'parent' => 'canvas',
181
      );
182
      $settings['items']['sidebar-right-row'] = array(
183
        'type' => 'row',
184
        'contains' => 'region',
185
        'children' => array('sidebar_right'),
186
        'parent' => 'sidebar-right',
187
      );
188
      $settings['items']['sidebar_right'] = array(
189
        'type' => 'region',
190
        'title' => t('Right sidebar'),
191
        'width' => 100,
192
        'width_type' => '%',
193
        'parent' => 'sidebar-right-row',
194
      );
195
    }
196

    
197
    // Add the main column.
198
    $settings['items']['main'] = array(
199
      'type' => 'column',
200
      'width' => 100,
201
      'width_type' => '%',
202
      'children' => array(),
203
      'parent' => 'canvas',
204
    );
205

    
206
    // Add rows and regions.
207
    for ($row = 1; $row <= intval($old['rows']); $row++) {
208
      // Create entry for the row
209
      $settings['items']["row_$row"] = array(
210
        'type' => 'row',
211
        'contains' => 'region',
212
        'children' => array(),
213
        'parent' => 'main',
214
      );
215
      // Add the row to the parent's children
216
      $settings['items']['main']['children'][] = "row_$row";
217

    
218
      for ($col = 1; $col <= intval($old["row_$row"]['columns']); $col++) {
219
        // Create entry for the region
220
        $settings['items']["row_${row}_$col"] = array(
221
          'type' => 'region',
222
          'width' => $old["row_$row"]["width_$col"],
223
          'width_type' => '%',
224
          'parent' => "row_$row",
225
        );
226
        // Add entry for the region to the row's children
227
        $settings['items']["row_$row"]['children'][] = "row_${row}_$col";
228

    
229
        // Apply the proper title to the region
230
        if (!empty($old["row_$row"]['names'][$col - 1])) {
231
          $settings['items']["row_${row}_$col"]['title'] = $old["row_$row"]['names'][$col - 1];
232
        }
233
        else {
234
          $settings['items']["row_${row}_$col"]['title'] = t("Row @row, Column @col", array('@row' => $row, '@col' => $col));
235
        }
236
      }
237
    }
238
  }
239
  else if (isset($settings['canvas'])) {
240
    // Convert the old 'canvas' to the new canvas row.
241
    $settings['items']['canvas'] = array(
242
      'type' => 'row',
243
      'contains' => 'column',
244
      'children' => $settings['canvas'],
245
      'parent' => NULL,
246
    );
247
    unset($settings['canvas']);
248
  }
249
}
250

    
251
/**
252
 * Define the actual list of columns and rows for this flexible panel.
253
 */
254
function panels_flexible_panels($display, $settings, $layout) {
255
  $items = array();
256
  panels_flexible_convert_settings($settings, $layout);
257
  foreach ($settings['items'] as $id => $item) {
258
    // Remove garbage values.
259
    if (!isset($item['type'])) {
260
      unset($items[$id]);
261
    }
262
    else if ($item['type'] == 'region') {
263
      $items[$id] = $item['title'];
264
    }
265
  }
266

    
267
  return $items;
268
}
269

    
270
/**
271
 * Create a renderer object.
272
 *
273
 * The renderer object contains data that is passed around from function
274
 * to function allowing us to render our CSS and HTML easily.
275
 *
276
 * @todo Convert the functions to methods and make this properly OO.
277
 */
278
function panels_flexible_create_renderer($admin, $css_id, $content, $settings, &$display, $layout, $handler) {
279
  $renderer = new stdClass;
280
  $renderer->settings = $settings;
281
  $renderer->content = $content;
282
  $renderer->css_id = $css_id;
283
  $renderer->did = &$display->did;
284
  if ($admin) {
285
    // always scale in admin mode.
286
    $renderer->scale_base = 99.0;
287
  }
288
  else {
289
    $renderer->scale_base = !empty($settings['items']['canvas']['no_scale']) ? 100.0 : 99.0;
290
  }
291
  $renderer->id_str = $css_id ? 'id="' . $css_id . '"' : '';
292
  $renderer->admin = $admin;
293
  $renderer->handler = $handler;
294

    
295
  // Set up basic classes for all of our components.
296
  $renderer->name                 = !empty($layout['layout']) ? $layout['layout']->name : $display->did;
297
  $renderer->base_class           = $renderer->name;
298
  $renderer->item_class['column'] = 'panels-flexible-column';
299
  $renderer->item_class['row']    = 'panels-flexible-row';
300
  $renderer->item_class['region'] = 'panels-flexible-region';
301
  $renderer->base['canvas']       = 'panels-flexible-' . $renderer->base_class;
302

    
303
  // Override these if selected from the UI and not in admin mode.
304
  if (!$admin) {
305
    if (!empty($settings['items']['canvas']['class'])) {
306
      $renderer->base_class = $settings['items']['canvas']['class'];
307
      $renderer->base['canvas'] = $renderer->base_class;
308
    }
309
    if (!empty($settings['items']['canvas']['column_class'])) {
310
      $renderer->item_class['column'] = $settings['items']['canvas']['column_class'];
311
    }
312
    if (!empty($settings['items']['canvas']['row_class'])) {
313
      $renderer->item_class['row'] = $settings['items']['canvas']['row_class'];
314
    }
315
    if (!empty($settings['items']['canvas']['region_class'])) {
316
      $renderer->item_class['region'] = $settings['items']['canvas']['region_class'];
317
    }
318
  }
319

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

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

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

    
327
  // Make some appended classes so it's easier to reference them.
328

    
329
  $renderer->base['column'] = $renderer->item_class['column'] . '-' . $renderer->base_class;
330
  $renderer->base['row']    = $renderer->item_class['row'] . '-' . $renderer->base_class;
331
  $renderer->base['region'] = $renderer->item_class['region'] . '-' . $renderer->base_class;
332

    
333
  if ($renderer->name != 'new') {
334
    // Use v2 to guarantee all CSS gets regenerated to account for changes in
335
    // how some divs will be rendered.
336
    $renderer->css_cache_name = 'flexiblev2:' . $renderer->name;
337
    if ($admin) {
338
      ctools_include('css');
339
      ctools_css_clear($renderer->css_cache_name);
340
    }
341
  }
342
  return $renderer;
343
}
344

    
345
/**
346
 * Draw the flexible layout.
347
 */
348
function theme_panels_flexible($vars) {
349
  $css_id = $vars['css_id'];
350
  $content = $vars['content'];
351
  $settings = $vars['settings'];
352
  $display = $vars['display'];
353
  $layout = $vars['layout'];
354
  $handler = $vars['renderer'];
355

    
356
  panels_flexible_convert_settings($settings, $layout);
357

    
358
  $renderer = panels_flexible_create_renderer(FALSE, $css_id, $content, $settings, $display, $layout, $handler);
359

    
360
  // CSS must be generated because it reports back left/middle/right
361
  // positions.
362
  $css = panels_flexible_render_css($renderer);
363

    
364
  if (!empty($renderer->css_cache_name) && empty($display->editing_layout)) {
365
    ctools_include('css');
366
    // Generate an id based upon rows + columns:
367
    $filename = ctools_css_retrieve($renderer->css_cache_name);
368
    if (!$filename) {
369
      $filename = ctools_css_store($renderer->css_cache_name, $css, FALSE);
370
    }
371

    
372
    // Give the CSS to the renderer to put where it wants.
373
    if ($handler) {
374
      $handler->add_css($filename, 'module', 'all', FALSE);
375
    }
376
    else {
377
      drupal_add_css($filename);
378
    }
379
  }
380
  else {
381
    // If the id is 'new' we can't reliably cache the CSS in the filesystem
382
    // because the display does not truly exist, so we'll stick it in the
383
    // head tag. We also do this if we've been told we're in the layout
384
    // editor so that it always gets fresh CSS.
385
    drupal_add_css($css, array('type' => 'inline', 'preprocess' => FALSE));
386
  }
387

    
388
  // Also store the CSS on the display in case the live preview or something
389
  // needs it
390
  $display->add_css = $css;
391

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

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

    
397
  // Wrap the whole thing up nice and snug
398
  $output .= "</div>\n</div>\n";
399

    
400
  return $output;
401
}
402

    
403
/**
404
 * Draw the flexible layout.
405
 */
406
function theme_panels_flexible_admin($vars) {
407
  $css_id = $vars['css_id'];
408
  $content = $vars['content'];
409
  $settings = $vars['settings'];
410
  $display = $vars['display'];
411
  $layout = $vars['layout'];
412
  $handler = $vars['renderer'];
413

    
414
  // We never draw stored flexible layouts in admin mode; they must be edited
415
  // from the stored layout UI at that point.
416
  if (!empty($layout['layout'])) {
417
    return theme_panels_flexible(array('css_id' => $css_id, 'content' => $content, 'settings' => $settings, 'display' => $display, 'layout' => $layout, 'renderer' => $handler));
418
  }
419

    
420
  panels_flexible_convert_settings($settings, $layout);
421
  $renderer = panels_flexible_create_renderer(TRUE, $css_id, $content, $settings, $display, $layout, $handler);
422

    
423
  $css = panels_flexible_render_css($renderer);
424

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

    
428
  if (empty($display->editing_layout)) {
429
    $output = '<input type="submit" id="panels-flexible-toggle-layout" class="form-submit" value ="' .
430
      t('Show layout designer') . '">';
431
    if (user_access('administer panels layouts')) {
432
      $output .= '<input type="hidden" class="panels-flexible-reuse-layout-url" value="' . url($handler->get_url('layout', 'reuse'), array('absolute' => TRUE)) . '">';
433
      $output .= '<input type="submit" id="panels-flexible-reuse-layout" class="form-submit ctools-use-modal" value ="' .
434
        t('Reuse layout') . '">';
435
    }
436
    $output .= "<div class=\"panel-flexible " . $renderer->base['canvas'] . " clearfix panel-flexible-admin panel-flexible-no-edit-layout\" $renderer->id_str>\n";
437
  }
438
  else {
439
    $output = "<div class=\"panel-flexible " . $renderer->base['canvas'] . " clearfix panel-flexible-admin panel-flexible-edit-layout\" $renderer->id_str>\n";
440
  }
441
  $output .= "<div class=\"panel-flexible-inside " . $renderer->base['canvas'] . "-inside \">\n";
442

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

    
446
  // Wrap the whole thing up nice and snug
447
  $output .= "</div>\n</div>\n";
448

    
449
  drupal_add_js($layout['path'] . '/flexible-admin.js');
450
  drupal_add_js(array('flexible' => array('resize' => url($handler->get_url('layout', 'resize'), array('absolute' => TRUE)))), 'setting');
451
  return $output;
452
}
453

    
454
/**
455
 * Render a piece of a flexible layout.
456
 */
457
function panels_flexible_render_items($renderer, $list, $owner_id) {
458
  $output = '';
459
  $groups = array('left' => '', 'middle' => '', 'right' => '');
460
  $max = count($list) - 1;
461
  $prev = NULL;
462

    
463
  foreach ($list as $position => $id) {
464
    $item = $renderer->settings['items'][$id];
465
    $location = isset($renderer->positions[$id]) ? $renderer->positions[$id] : 'middle';
466

    
467
    if ($renderer->admin && $item['type'] != 'row' && $prev ) {
468
      $groups[$location] .= panels_flexible_render_splitter($renderer, $prev, $id);
469
    }
470

    
471
    switch ($item['type']) {
472
      case 'column':
473
        $content = panels_flexible_render_items($renderer, $item['children'], $renderer->base['column'] . '-' . $id);
474
        $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max);
475
        break;
476
      case 'row':
477
        $content = panels_flexible_render_items($renderer, $item['children'], $renderer->base['row'] . '-' . $id);
478
        $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max, TRUE);
479
        break;
480
      case 'region':
481
        $content = isset($renderer->content[$id]) ? $renderer->content[$id] : "&nbsp;";
482
        $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max);
483
        break;
484
    }
485

    
486
    // If all items are fixed then we have a special splitter on the right to
487
    // control the overall width.
488
    if (!empty($renderer->admin) && $max == $position && $location == 'left') {
489
      $groups[$location] .= panels_flexible_render_splitter($renderer, $id, NULL);
490
    }
491
    $prev = $id;
492
  }
493

    
494
  $group_count = count(array_filter($groups));
495

    
496
  // Render each group. We only render the group div if we're in admin mode
497
  // or if there are multiple groups.
498
  foreach ($groups as $position => $content) {
499
    if (!empty($content) || $renderer->admin) {
500
      if ($group_count > 1 || $renderer->admin) {
501
        $output .= '<div class="' . $owner_id . '-' . $position . '">' . $content . '</div>';
502
      }
503
      else {
504
        $output .= $content;
505
      }
506
    }
507
  }
508

    
509
  return $output;
510
}
511

    
512
/**
513
 * Render a column in the flexible layout.
514
 */
515
function panels_flexible_render_item($renderer, $item, $content, $id, $position, $max, $clear = FALSE) {
516

    
517
  // If we are rendering a row and there is just one row, we don't need to
518
  // render the row unless there is fixed_width content inside it.
519
  if (empty($renderer->admin) && $item['type'] == 'row' && $max == 0) {
520
    $fixed = FALSE;
521
    foreach ($item['children'] as $id) {
522
      if ($renderer->settings['items'][$id]['width_type'] != '%') {
523
        $fixed = TRUE;
524
        break;
525
      }
526
    }
527

    
528
    if (!$fixed) {
529
      return $content;
530
    }
531
  }
532

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

    
539
  $base = $renderer->item_class[$item['type']];
540
  $output = '<div class="' . $base . ' ' . $renderer->base[$item['type']] . '-' . $id;
541
  if ($position == 0) {
542
    $output .= ' ' . $base . '-first';
543
  }
544
  if ($position == $max) {
545
    $output .= ' ' . $base . '-last';
546
  }
547
  if ($clear) {
548
    $output .= ' clearfix';
549
  }
550

    
551
  if (isset($item['class'])) {
552
    $output .= ' ' . check_plain($item['class']);
553
  }
554

    
555
  $output .= '">' . "\n";
556

    
557
  if (!empty($renderer->admin)) {
558
    $output .= panels_flexible_render_item_links($renderer, $id, $item);
559
  }
560

    
561
  $output .= '  <div class="inside ' . $base . '-inside ' . $base . '-' . $renderer->base_class . '-' . $id . '-inside';
562
  if ($position == 0) {
563
    $output .= ' ' . $base . '-inside-first';
564
  }
565
  if ($position == $max) {
566
    $output .= ' ' . $base . '-inside-last';
567
  }
568
  if ($clear) {
569
    $output .= ' clearfix';
570
  }
571

    
572
  $output .= "\">\n";
573
  $output .= $content;
574
  $output .= '  </div>' . "\n";
575
  $output .= '</div>' . "\n";
576

    
577
  return $output;
578
}
579
/**
580
 * Render a splitter div to place between the $left and $right items.
581
 *
582
 * If the right ID is NULL that means there isn't actually a box to the
583
 * right, but we need a splitter anyway. We'll mostly use info about the
584
 * left, but pretend it's 'fluid' so that the javascript won't actually
585
 * modify the right item.
586
 */
587
function panels_flexible_render_splitter($renderer, $left_id, $right_id) {
588
  $left = $renderer->settings['items'][$left_id];
589

    
590
  $left_class = $renderer->base[$left['type']] . '-' . $left_id;
591
  if ($right_id) {
592
    $right = $renderer->settings['items'][$right_id];
593
    $right_class = $renderer->base[$left['type']] . '-' . $right_id;
594
  }
595
  else {
596
    $right = $left;
597
    $right_class = $left_class;
598
  }
599

    
600
  $output = '<div tabindex="0"
601
    class="panels-flexible-splitter flexible-splitter-for-' . $left_class . '">';
602

    
603
  // Name the left object
604
  $output .= '<span class="panels-flexible-splitter-left">';
605
  $output .= '.' . $left_class;
606
  $output .= '</span>';
607

    
608
  $output .= '<span class="panels-flexible-splitter-left-id">';
609
  $output .= $left_id;
610
  $output .= '</span>';
611

    
612
  $output .= '<span class="panels-flexible-splitter-left-width ' . $left_class . '-width">';
613
  $output .= $left['width'];
614
  $output .= '</span>';
615

    
616
  $output .= '<span class="panels-flexible-splitter-left-scale">';
617
  $output .= isset($renderer->scale[$left_id]) ? $renderer->scale[$left_id] : 1;
618
  $output .= '</span>';
619

    
620
  $output .= '<span class="panels-flexible-splitter-left-width-type">';
621
  $output .= $left['width_type'];
622
  $output .= '</span>';
623

    
624
  // Name the right object
625
  $output .= '<span class="panels-flexible-splitter-right">';
626
  $output .= '.' . $right_class;
627
  $output .= '</span>';
628

    
629
  $output .= '<span class="panels-flexible-splitter-right-id">';
630
  $output .= $right_id;
631
  $output .= '</span>';
632

    
633
  $output .= '<span class="panels-flexible-splitter-right-width ' . $right_class . '-width">';
634
  $output .= $right['width'];
635
  $output .= '</span>';
636

    
637
  $output .= '<span class="panels-flexible-splitter-right-scale">';
638
  $output .= isset($renderer->scale[$right_id]) ? $renderer->scale[$right_id] : 1;
639
  $output .= '</span>';
640

    
641
  $output .= '<span class="panels-flexible-splitter-right-width-type">';
642
  // If there is no right, make it fluid.
643
  $output .= $right_id ? $right['width_type'] : '%';
644
  $output .= '</span>';
645

    
646
  $output .= '</div>';
647
  return $output;
648
}
649

    
650
/**
651
 * Render the dropdown links for an item.
652
 */
653
function panels_flexible_render_item_links($renderer, $id, $item) {
654
  $links = array();
655
  $remove = '';
656
  $add = '';
657
  if ($item['type'] == 'column') {
658
    $title = t('Column');
659
    $settings = t('Column settings');
660
    if (empty($item['children'])) {
661
      $remove = t('Remove column');
662
      $add = t('Add row');
663
    }
664
    else {
665
      $add = t('Add row to top');
666
      $add2 = t('Add row to bottom');
667
    }
668
  }
669
  else if ($item['type'] == 'row') {
670
    if ($id == 'canvas') {
671
      $title = t('Canvas');
672
      $settings = t('Canvas settings');
673
    }
674
    else {
675
      $title = t('Row');
676
      $settings = t('Row settings');
677
    }
678
    if (empty($item['children'])) {
679
      if ($id != 'canvas') {
680
        $remove = t('Remove row');
681
      }
682
      $add = $item['contains'] == 'region' ? t('Add region') : t('Add column');
683
    }
684
    else {
685
      $add = $item['contains'] == 'region' ? t('Add region to left') : t('Add column to left');
686
      $add2 = $item['contains'] == 'region' ? t('Add region to right') : t('Add column to right');
687
    }
688
  }
689
  else if ($item['type'] == 'region') {
690
    $title = t('Region');
691
    $settings = t('Region settings');
692
    $remove = t('Remove region');
693
  }
694

    
695
  if (!empty($settings)) {
696
    $links[] = array(
697
      'title' => $settings,
698
      'href' => $renderer->handler->get_url('layout', 'settings', $id),
699
      'attributes' => array('class' => array('ctools-use-modal')),
700
    );
701
  }
702
  if ($add) {
703
    $links[] = array(
704
      'title' => $add,
705
      'href' => $renderer->handler->get_url('layout', 'add', $id),
706
      'attributes' => array('class' => array('ctools-use-modal')),
707
    );
708
  }
709
  if (isset($add2)) {
710
    $links[] = array(
711
      'title' => $add2,
712
      'href' => $renderer->handler->get_url('layout', 'add', $id, 'right'),
713
      'attributes' => array('class' => array('ctools-use-modal')),
714
    );
715
  }
716
  if ($remove) {
717
    $links[] = array(
718
      'title' => $remove,
719
      'href' => $renderer->handler->get_url('layout', 'remove', $id),
720
      'attributes' => array('class' => array('use-ajax')),
721
    );
722
  }
723

    
724
  return theme('ctools_dropdown', array('title' => $title, 'links' => $links, 'class' => 'flexible-layout-only flexible-links flexible-title flexible-links-' . $id));
725
}
726
/**
727
 * Provide CSS for a flexible layout.
728
 */
729
function panels_flexible_render_css($renderer) {
730
  if ($renderer->admin) {
731
    $parent_class = '.' . $renderer->base['row'] . '-canvas';
732
  }
733
  else {
734
    $parent_class = '.' . $renderer->base['canvas'];
735
  }
736
  return panels_flexible_render_css_group($renderer, $renderer->settings['items']['canvas']['children'], $parent_class, 'column', 'canvas');
737
}
738

    
739
/**
740
 * Render the CSS for a group of items to be displayed together.
741
 *
742
 * Columns and regions, when displayed as a group, need to cooperate in
743
 * order to share margins and make sure that percent widths add up
744
 * to the right total.
745
 */
746
function panels_flexible_render_css_group($renderer, $list, $owner_id, $type, $id) {
747
  $css = array();
748

    
749
  // Start off with some generic CSS to properly pad regions
750
  $css[$owner_id . ' .' . $renderer->item_class['region']] = array(
751
    'padding' => '0',
752
  );
753

    
754
  $css[$owner_id . ' .' . $renderer->item_class['region'] . '-inside'] = array(
755
    'padding-right' => $renderer->region_separation,
756
    'padding-left' => $renderer->region_separation,
757
  );
758

    
759
  $css[$owner_id . ' .' . $renderer->item_class['region'] . '-inside-first'] = array(
760
    'padding-left' => '0',
761
  );
762

    
763
  $css[$owner_id . ' .' . $renderer->item_class['region'] . '-inside-last'] = array(
764
    'padding-right' => '0',
765
  );
766

    
767
  $css[$owner_id . ' .' . $renderer->item_class['column']] = array(
768
    'padding' => '0',
769
  );
770

    
771
  $css[$owner_id . ' .' . $renderer->item_class['column'] . '-inside'] = array(
772
    'padding-right' => $renderer->column_separation,
773
    'padding-left' => $renderer->column_separation,
774
  );
775

    
776
  $css[$owner_id . ' .' . $renderer->item_class['column'] . '-inside-first'] = array(
777
    'padding-left' => '0',
778
  );
779

    
780
  $css[$owner_id . ' .' . $renderer->item_class['column'] . '-inside-last'] = array(
781
    'padding-right' => '0',
782
  );
783

    
784
  // And properly pad rows too
785
  $css[$owner_id . ' .' . $renderer->item_class['row']] = array(
786
    'padding' => '0 0 ' . $renderer->row_separation . ' 0',
787
    'margin' => '0',
788
  );
789

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

    
794
  panels_flexible_get_css_group($css, $renderer, $list, $owner_id, $type, $id);
795

    
796
  ctools_include('css');
797
  return ctools_css_assemble($css);
798
}
799

    
800
/**
801
 * Construct an array with all of the CSS properties for a group.
802
 *
803
 * This will parse down into children and produce all of the CSS needed if you
804
 * start from the top.
805
 */
806
function panels_flexible_get_css_group(&$css, $renderer, $list, $owner_id, $type, $item_id) {
807
  if ($type != 'row') {
808
    // Go through our items and break up into right/center/right groups so we
809
    // can figure out our offsets.
810

    
811
    // right == any items on the right that are 'fixed'.
812
    // middle == all fluid items.
813
    // right == any items on the right that are 'fixed'.
814
    $left = $middle = $right = array();
815
    $left_total = $right_total = $middle_total = 0;
816
    $current = 'left';
817
    foreach ($list as $id) {
818
      if ($renderer->settings['items'][$id]['width_type'] == 'px') {
819
        // fixed
820
        if ($current == 'left') {
821
          $left[] = $id;
822
          $renderer->positions[$id] = 'left';
823
          $left_total += $renderer->settings['items'][$id]['width'];
824
        }
825
        else {
826
          $current = 'right';
827
          $right[] = $id;
828
          $renderer->positions[$id] = 'right';
829
          $right_total += $renderer->settings['items'][$id]['width'];
830
        }
831
      }
832
      else {
833
        // fluid
834
        if ($current != 'right') {
835
          $current = 'middle';
836
          $middle[] = $id;
837
          $renderer->positions[$id] = 'middle';
838
          $middle_total += $renderer->settings['items'][$id]['width'];
839
        }
840
        // fall through: if current is 'right' and we ran into a 'fluid' then
841
        // it gets *dropped* because that is invalid.
842
      }
843
    }
844

    
845
    // Go through our right sides and create CSS.
846
    foreach ($left as $id) {
847
      $class = "." . $renderer->base[$type] . "-$id";
848
      $css[$class] = array(
849
        'position' => 'relative',
850
        'float' => 'left',
851
        'background-color' => 'transparent',
852
        'width' => $renderer->settings['items'][$id]['width'] . "px",
853
      );
854
    }
855

    
856
    // Do the same for right.
857
    $right_pixels = 0;
858

    
859
    foreach ($right as $id) {
860
      $class = "." . $renderer->base[$type] . "-$id";
861
      $css[$class] = array(
862
        'float' => 'left',
863
        'width' => $renderer->settings['items'][$id]['width'] . "px",
864
      );
865
    }
866

    
867
    $max = count($middle) - 1;
868

    
869
    if ($middle_total) {
870
      // Because we love IE so much, auto scale everything to 99%. This
871
      // means adding up the actual widths and then providing a multiplier
872
      // to each so that the total is 99%.
873
      $scale = $renderer->scale_base / $middle_total;
874
      foreach ($middle as $position => $id) {
875
        $class = "." . $renderer->base[$type] . "-$id";
876
        $css[$class] = array(
877
          'float' => 'left',
878
          'width' => number_format($renderer->settings['items'][$id]['width'] * $scale, 4, '.', '') . "%",
879
        );
880

    
881
        // Store this so we can use it later.
882
        // @todo: Store the scale, not the new width, so .js can adjust
883
        // bi-directionally.
884
        $renderer->scale[$id] = $scale;
885
      }
886
    }
887

    
888
    // If there is any total remaining, we need to offset the splitter
889
    // by this much too.
890
    if ($left_total) {
891
      // Add this even if it's 0 so we can handle removals.
892
      $css["$owner_id-inside"]['padding-left'] = '0px';
893
      if ($renderer->admin || count($middle)) {
894
        $css["$owner_id-middle"]['margin-left'] = $left_total . 'px';
895
        // IE hack
896
        $css["* html $owner_id-left"]['left'] = $left_total . "px";
897
        // Make this one very specific to the admin CSS so that preview
898
        // does not stomp it.
899
        $css[".panel-flexible-admin $owner_id-inside"]['padding-left'] = '0px';
900
      }
901
      else {
902
        $css["$owner_id-inside"]['margin-left'] = '-' . $left_total . 'px';
903
        $css["$owner_id-inside"]['padding-left'] = $left_total . 'px';
904
        // IE hack
905
        $css["* html $owner_id-inside"]['left'] = $left_total . "px";
906
      }
907
    }
908
    if ($right_total) {
909
      $css["$owner_id-middle"]['margin-right'] = $right_total . 'px';
910
    }
911
    $css["$owner_id-inside"]['padding-right'] = '0px';
912
  }
913

    
914
  // If the canvas has a fixed width set, and this is the canvas, fix the
915
  // width.
916
  if ($item_id == 'canvas') {
917
    $item = $renderer->settings['items'][$item_id];
918

    
919
    if (!empty($item['fixed_width']) && intval($item['fixed_width'])) {
920
      $css['.' . $renderer->base['canvas']]['width'] = intval($item['fixed_width']) . 'px';
921
    }
922
    else {
923
      $css['.' . $renderer->base['canvas']]['width'] = 'auto';
924
    }
925
  }
926

    
927
  // Go through each item and process children.
928
  foreach ($list as $id) {
929
    $item = $renderer->settings['items'][$id];
930
    if (empty($item['children'])) {
931
      continue;
932
    }
933

    
934
    if ($type == 'column') {
935
      // Columns can only contain rows.
936
      $child_type = 'row';
937
    }
938
    else {
939
      $child_type = isset($item['contains']) ? $item['contains'] : 'region';
940
    }
941

    
942
    $class = "." . $renderer->base[$type] . "-$id";
943
    panels_flexible_get_css_group($css, $renderer, $item['children'], $class, $child_type, $id);
944
  }
945
}
946

    
947
/**
948
 * AJAX responder to edit flexible settings for an item.
949
 *
950
 * $handler object
951
 *   The display renderer handler object.
952
 */
953
function panels_ajax_flexible_edit_settings($handler, $id) {
954
  $settings = &$handler->display->layout_settings;
955
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
956

    
957
  if (empty($settings['items'][$id])) {
958
    ctools_modal_render(t('Error'), t('Invalid item id.'));
959
  }
960

    
961
  $item = &$settings['items'][$id];
962
  $siblings = array();
963

    
964
  if ($id != 'canvas') {
965
    $siblings = $settings['items'][$item['parent']]['children'];
966
  }
967

    
968

    
969
  switch ($item['type']) {
970
    case 'column':
971
      $title = t('Configure column');
972
      break;
973
    case 'row':
974
      if ($id == 'canvas') {
975
        $title = t('Configure canvas');
976
      }
977
      else {
978
        $title = t('Configure row');
979
      }
980
      break;
981
    case 'region':
982
      $title = t('Configure region');
983
      break;
984
  }
985

    
986
  $form_state = array(
987
    'display' => &$handler->display,
988
    'item' => &$item,
989
    'id' => $id,
990
    'siblings' => $siblings,
991
    'settings' => &$settings,
992
    'ajax' => TRUE,
993
    'title' => $title,
994
    'op' => 'edit',
995
  );
996

    
997
  $output = ctools_modal_form_wrapper('panels_flexible_config_item_form', $form_state);
998
  if (!empty($form_state['executed'])) {
999
    // If the width type changed then other nearby items will have
1000
    // to have their widths adjusted.
1001
    panels_edit_cache_set($handler->cache);
1002

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

    
1006
    $output = array();
1007
    // If the item is a region, replace the title.
1008
    $class = $renderer->base[$item['type']] . '-' . $id;
1009
    if ($item['type'] == 'region') {
1010
      $output[] = ajax_command_replace(".$class h2.label",
1011
        '<h2 class="label">' . check_plain($item['title']) . '</h2>');
1012
    }
1013

    
1014
    // Rerender our links in case something changed.
1015
    $output[] = ajax_command_replace('.flexible-links-' . $id,
1016
      panels_flexible_render_item_links($renderer, $id, $item));
1017

    
1018
    // If editing the canvas, reset the CSS width
1019
    if ($id == 'canvas') {
1020
      // update canvas CSS.
1021
      $css = array(
1022
        '.' . $renderer->item_class['column'] . '-inside' => array(
1023
          'padding-left' => $renderer->column_separation,
1024
          'padding-right' => $renderer->column_separation,
1025
        ),
1026
        '.' . $renderer->item_class['region'] . '-inside' => array(
1027
          'padding-left' => $renderer->region_separation,
1028
          'padding-right' => $renderer->region_separation,
1029
        ),
1030
        '.' . $renderer->item_class['row'] => array(
1031
          'padding-bottom' => $renderer->row_separation,
1032
        ),
1033
      );
1034
      if (!empty($item['fixed_width']) && intval($item['fixed_width'])) {
1035
        $css['.' . $renderer->base['canvas']] = array('width' => intval($item['fixed_width']) . 'px');
1036
      }
1037
      else {
1038
        $css['.' . $renderer->base['canvas']] = array('width' => 'auto');
1039
      }
1040
      foreach ($css as $selector => $data) {
1041
        $output[] = ajax_command_css($selector, $data);
1042
      }
1043
    }
1044

    
1045
    $output[] = ctools_modal_command_dismiss();
1046
  }
1047

    
1048
  $handler->commands = $output;
1049
}
1050

    
1051
/**
1052
 * Configure a row, column or region on the flexible page.
1053
 *
1054
 * @param <type> $form_state
1055
 * @return <type>
1056
 */
1057
function panels_flexible_config_item_form($form, &$form_state) {
1058
  $display = &$form_state['display'];
1059
  $item = &$form_state['item'];
1060
  $siblings = &$form_state['siblings'];
1061
  $settings = &$form_state['settings'];
1062
  $id = &$form_state['id'];
1063

    
1064
  if ($item['type'] == 'region') {
1065
    $form['title'] = array(
1066
      '#title' => t('Region title'),
1067
      '#type' => 'textfield',
1068
      '#default_value' => $item['title'],
1069
      '#required' => TRUE,
1070
    );
1071
  }
1072

    
1073
  if ($id == 'canvas') {
1074
    $form['class'] = array(
1075
      '#title' => t('Canvas class'),
1076
      '#type' => 'textfield',
1077
      '#default_value' => isset($item['class']) ? $item['class'] : '',
1078
      '#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.'),
1079
    );
1080

    
1081
    $form['column_class'] = array(
1082
      '#title' => t('Column class'),
1083
      '#type' => 'textfield',
1084
      '#default_value' => isset($item['column_class']) ? $item['column_class'] : '',
1085
      '#description' => t('This class will be applied to all columns of the layout. If left blank this will be panels-flexible-column.'),
1086
    );
1087

    
1088
    $form['row_class'] = array(
1089
      '#title' => t('Row class'),
1090
      '#type' => 'textfield',
1091
      '#default_value' => isset($item['row_class']) ? $item['row_class'] : '',
1092
      '#description' => t('This class will be applied to all rows of the layout. If left blank this will be panels-flexible-row.'),
1093
    );
1094

    
1095
    $form['region_class'] = array(
1096
      '#title' => t('Region class'),
1097
      '#type' => 'textfield',
1098
      '#default_value' => isset($item['region_class']) ? $item['region_class'] : '',
1099
      '#description' => t('This class will be applied to all regions of the layout. If left blank this will be panels-flexible-region.'),
1100
    );
1101

    
1102
    $form['no_scale'] = array(
1103
      '#type' => 'checkbox',
1104
      '#title' => t('Scale fluid widths for IE6'),
1105
      '#description' => t('IE6 does not do well with 100% widths. If checked, width will be scaled to 99% to compensate.'),
1106
      '#default_value' => empty($item['no_scale']),
1107
    );
1108

    
1109
    $form['fixed_width'] = array(
1110
      '#type' => 'textfield',
1111
      '#title' => t('Fixed width'),
1112
      '#description' => t('If a value is entered, the layout canvas will be fixed to the given pixel width.'),
1113
      '#default_value' => isset($item['fixed_width']) ? $item['fixed_width'] : '',
1114
    );
1115

    
1116
    $form['column_separation'] = array(
1117
      '#type' => 'textfield',
1118
      '#title' => t('Column separation'),
1119
      '#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.'),
1120
      '#default_value' => isset($item['column_separation']) ? $item['column_separation'] : '0.5em',
1121
    );
1122

    
1123
    $form['region_separation'] = array(
1124
      '#type' => 'textfield',
1125
      '#title' => t('Region separation'),
1126
      '#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.'),
1127
      '#default_value' => isset($item['region_separation']) ? $item['region_separation'] : '0.5em',
1128
    );
1129

    
1130
    $form['row_separation'] = array(
1131
      '#type' => 'textfield',
1132
      '#title' => t('Row separation'),
1133
      '#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.'),
1134
      '#default_value' => isset($item['row_separation']) ? $item['row_separation'] : '0.5em',
1135
    );
1136
  }
1137
  else {
1138
    $form['class'] = array(
1139
      '#title' => t('CSS class'),
1140
      '#type' => 'textfield',
1141
      '#default_value' => isset($item['class']) ? $item['class'] : '',
1142
      '#description' => t('Enter a CSS class that will be used. This can be used to apply automatic styling from your theme, for example.'),
1143
    );
1144

    
1145
    if ($item['type'] != 'row') {
1146
      // Test to see if there are fluid items to the left or the right. If there
1147
      // are fluid items on both sides, this item cannot be set to fixed.
1148
      $left = $right = FALSE;
1149
      $current = 'left';
1150
      foreach ($siblings as $sibling) {
1151
        if ($sibling == $id) {
1152
          $current = 'right';
1153
        }
1154
        else if ($settings['items'][$sibling]['width_type'] == '%') {
1155
          $$current = TRUE; // Indirection.
1156
        }
1157
      }
1158

    
1159
      $form['width_type'] = array(
1160
        '#type' => 'select',
1161
        '#title' => t('Width'),
1162
        '#default_value' => $item['width_type'],
1163
        '#options' => array(
1164
          '%' => t('Fluid'),
1165
          'px' => t('Fixed'),
1166
        ),
1167
        '#disabled' => TRUE,
1168
      );
1169
    }
1170
    else {
1171
      $form['contains'] = array(
1172
        '#type' => 'select',
1173
        '#title' => t('Contains'),
1174
        '#default_value' => $item['contains'],
1175
        '#options' => array(
1176
          'region' => t('Regions'),
1177
          'column' => t('Columns'),
1178
        ),
1179
      );
1180

    
1181
      if (!empty($item['children'])) {
1182
        $form['contains']['#disabled'] = TRUE;
1183
        $form['contains']['#value'] = $item['contains'];
1184
        $form['contains']['#description'] = t('You must remove contained items to change the row container type.');
1185
      }
1186
    }
1187
  }
1188

    
1189
  $form['save'] = array(
1190
    '#type' => 'submit',
1191
    '#value' => t('Save'),
1192
  );
1193

    
1194
  return $form;
1195
}
1196

    
1197
/**
1198
 * Submit handler for editing a flexible item.
1199
 */
1200
function panels_flexible_config_item_form_submit(&$form, &$form_state) {
1201
  $item = &$form_state['item'];
1202
  if ($item['type'] == 'region') {
1203
    $item['title'] = $form_state['values']['title'];
1204
  }
1205

    
1206
  $item['class'] = $form_state['values']['class'];
1207

    
1208
  if ($form_state['id'] == 'canvas') {
1209
    $item['column_class'] = $form_state['values']['column_class'];
1210
    $item['row_class'] = $form_state['values']['row_class'];
1211
    $item['region_class'] = $form_state['values']['region_class'];
1212
    // Reverse this as the checkbox is backward from how we actually store
1213
    // it to make it simpler to default to scaling.
1214
    $item['no_scale'] = !$form_state['values']['no_scale'];
1215
    $item['fixed_width'] = $form_state['values']['fixed_width'];
1216
    $item['column_separation'] = $form_state['values']['column_separation'];
1217
    $item['region_separation'] = $form_state['values']['region_separation'];
1218
    $item['row_separation'] = $form_state['values']['row_separation'];
1219
  }
1220
  else if ($item['type'] != 'row') {
1221
    $item['width_type'] = $form_state['values']['width_type'];
1222
  }
1223
  else {
1224
    $item['contains'] = $form_state['values']['contains'];
1225
  }
1226

    
1227
}
1228

    
1229
/**
1230
 * AJAX responder to add a new row, column or region to a flexible layout.
1231
 */
1232
function panels_ajax_flexible_edit_add($handler, $id, $location = 'left') {
1233
  ctools_include('modal');
1234
  ctools_include('ajax');
1235
  $settings = &$handler->display->layout_settings;
1236
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
1237

    
1238
  if (empty($settings['items'][$id])) {
1239
    ctools_modal_render(t('Error'), t('Invalid item id.'));
1240
  }
1241

    
1242
  $parent = &$settings['items'][$id];
1243

    
1244
  switch ($parent['type']) {
1245
    case 'column':
1246
      $title = t('Add row');
1247
      // Create the new item with defaults.
1248
      $item = array(
1249
        'type' => 'row',
1250
        'contains' => 'region',
1251
        'children' => array(),
1252
        'parent' => $id,
1253
      );
1254
      break;
1255
    case 'row':
1256
      switch ($parent['contains']) {
1257
        case 'region':
1258
          $title = $location == 'left' ? t('Add region to left') : t('Add region to right');
1259
          $item = array(
1260
            'type' => 'region',
1261
            'title' => '',
1262
            'width' => 100,
1263
            'width_type' => '%',
1264
            'parent' => $id,
1265
          );
1266
          break;
1267
        case 'column':
1268
          $title = $location == 'left' ? t('Add column to left') : t('Add column to right');
1269
          $item = array(
1270
            'type' => 'column',
1271
            'width' => 100,
1272
            'width_type' => '%',
1273
            'parent' => $id,
1274
            'children' => array(),
1275
          );
1276
          break;
1277
      }
1278
      // Create the new item with defaults.
1279
      break;
1280
    case 'region':
1281
      // Cannot add items to regions.
1282
      break;
1283
  }
1284

    
1285
  $form_state = array(
1286
    'display' => &$handler->display,
1287
    'parent' => &$parent,
1288
    'item' => &$item,
1289
    'id' => $id,
1290
    'settings' => &$settings,
1291
    'ajax' => TRUE,
1292
    'title' => $title,
1293
    'location' => $location,
1294
  );
1295

    
1296
  $output = ctools_modal_form_wrapper('panels_flexible_add_item_form', $form_state);
1297
  if (!empty($form_state['executed'])) {
1298
    // If the width type changed then other nearby items will have
1299
    // to have their widths adjusted.
1300
    panels_edit_cache_set($handler->cache);
1301
    $output = array();
1302

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

    
1307
    $content = '';
1308
    if ($item['type'] == 'region') {
1309
      $handler->plugins['layout']['regions'][$form_state['key']] = $item['title'];
1310

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

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

    
1317
    }
1318
    else {
1319
      // We need to make sure the left/middle/right divs exist inside this
1320
      // so that more stuff can be added inside it as needed.
1321
      foreach (array('left', 'middle', 'right') as $position) {
1322
        if (!empty($content) || $renderer->admin) {
1323
          $content .= '<div class="' . $renderer->base[$item['type']] . '-' . $form_state['key'] . '-' . $position . '"></div>';
1324
        }
1325
      }
1326

    
1327
    }
1328

    
1329
    // render the item
1330
    $parent_class =  $renderer->base[$parent['type']] . '-' . $id;
1331
    $item_output = panels_flexible_render_item($renderer, $item, $content, $form_state['key'], 0, 0, $item['type'] == 'row');
1332

    
1333
    // Get all the CSS necessary for the entire row (as width adjustments may
1334
    // have cascaded).
1335
    $css = array();
1336
    panels_flexible_get_css_group($css, $renderer, $parent['children'], '.' . $parent_class, $item['type'], $id);
1337

    
1338
    $position = isset($renderer->positions[$form_state['key']]) ? $renderer->positions[$form_state['key']] : 'middle';
1339
    // If there's a nearby item, add the splitter and rewrite the width
1340
    // of the nearby item as it probably got adjusted.
1341
    // The blocks of code in this else look very similar but are not actually
1342
    // duplicated because the order changes based on left or right.
1343
    switch ($position) {
1344
      case 'left':
1345
        if ($location == 'left') {
1346
          $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], $form_state['sibling']);
1347
          $output[] = ajax_command_prepend('#panels-dnd-main .' . $parent_class . '-left', $item_output);
1348
        }
1349
        else if ($location == 'right') {
1350
          // If we are adding to the right side of the left box, there is
1351
          // a splitter that we have to remove; then we add our box normally,
1352
          // and then add a new splitter for just our guy.
1353
          $output[] = ajax_command_remove('panels-flexible-splitter-for-' . $renderer->base[$item['type']] . '-' . $form_state['key']);
1354
          $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) .  $item_output;
1355
          $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], NULL);
1356
          $output[] = ajax_command_append('#panels-dnd-main .' . $parent_class . '-left', $item_output);
1357
        }
1358
        break;
1359
      case 'right':
1360
        if (!empty($form_state['sibling'])) {
1361
          $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) .  $item_output;
1362
        }
1363
        $output[] = ajax_command_append('#panels-dnd-main .' . $parent_class . '-right', $item_output);
1364
        break;
1365
      case 'middle':
1366
        if ($location == 'left') {
1367
          if (!empty($form_state['sibling'])) {
1368
            $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], $form_state['sibling']);
1369
          }
1370
          $output[] = ajax_command_prepend('#panels-dnd-main .' . $parent_class . '-middle', $item_output);
1371
        }
1372
        else {
1373
          if (!empty($form_state['sibling'])) {
1374
            $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) .  $item_output;
1375
          }
1376
          $output[] = ajax_command_append('#panels-dnd-main .' . $parent_class . '-middle', $item_output);
1377
        }
1378
        break;
1379

    
1380
    }
1381

    
1382
    // Send our fix height command.
1383
    $output[] = array('command' => 'flexible_fix_height');
1384

    
1385
    if (!empty($form_state['sibling'])) {
1386
      $sibling_width = '#panels-dnd-main .' . $renderer->base[$item['type']] . '-' . $form_state['sibling'] . '-width';
1387
      $output[] = array(
1388
        'command' => 'flexible_set_width',
1389
        'selector' => $sibling_width,
1390
        'width' => $settings['items'][$form_state['sibling']]['width'],
1391
      );
1392
    }
1393
    foreach ($css as $selector => $data) {
1394
      $output[] = ajax_command_css($selector, $data);
1395
    }
1396

    
1397
    // Rerender our parent item links:
1398
    $output[] = ajax_command_replace('.flexible-links-' . $id,
1399
      panels_flexible_render_item_links($renderer, $id, $parent));
1400

    
1401
    $output[] = array(
1402
      'command' => 'flexible_fix_firstlast',
1403
      'selector' => '.' . $parent_class . '-inside',
1404
      'base' => 'panels-flexible-' . $item['type'],
1405
    );
1406

    
1407
    $output[] = ctools_modal_command_dismiss();
1408
  }
1409

    
1410
  $handler->commands = $output;
1411
}
1412
/**
1413
 * Form to add a row, column or region to a flexible layout.
1414
 * @param <type> $form_state
1415
 * @return <type>
1416
 */
1417
function panels_flexible_add_item_form($form, &$form_state) {
1418
  $display = &$form_state['display'];
1419
  $item = &$form_state['item'];
1420
  $parent = &$form_state['parent'];
1421
  $settings = &$form_state['settings'];
1422
  $location = &$form_state['location'];
1423
  $id = &$form_state['id'];
1424

    
1425
  if ($item['type'] == 'region') {
1426
    $form['title'] = array(
1427
      '#title' => t('Region title'),
1428
      '#type' => 'textfield',
1429
      '#default_value' => $item['title'],
1430
      '#required' => TRUE,
1431
    );
1432
  }
1433

    
1434
  $form['class'] = array(
1435
    '#title' => t('CSS Class'),
1436
    '#type' => 'textfield',
1437
    '#default_value' => isset($item['class']) ? $item['class'] : '',
1438
    '#description' => t('Enter a CSS class that will be used. This can be used to apply automatic styling from your theme, for example.'),
1439
  );
1440

    
1441
  if ($item['type'] != 'row') {
1442
    // If there is a 'fixed' type on the side we're adding to, then this
1443
    // must also be fixed. Otherwise it can be either and should default to
1444
    // fluid.
1445
    $restrict = FALSE;
1446

    
1447
    if (!empty($parent['children'])) {
1448
      if ($location == 'left') {
1449
        $sibling = reset($parent['children']);
1450
      }
1451
      else {
1452
        $sibling = end($parent['children']);
1453
      }
1454
      if ($settings['items'][$sibling]['width_type'] == 'px') {
1455
        $restrict = TRUE;
1456
        $item['width_type'] = 'px';
1457
      }
1458
    }
1459

    
1460
    $form['width_type'] = array(
1461
      '#type' => 'select',
1462
      '#title' => t('Width'),
1463
      '#default_value' => $item['width_type'],
1464
      '#options' => array(
1465
        '%' => t('Fluid'),
1466
        'px' => t('Fixed'),
1467
      ),
1468
      '#disabled' => $restrict,
1469
    );
1470
    if ($restrict) {
1471
      // This forces the value because disabled items don't always send
1472
      // their data back.
1473
      $form['width_type']['#value'] = $item['width_type'];
1474
      $form['width_type']['#description'] = t('Items cannot be set to fluid if there are fixed items already on that side.');
1475
    }
1476
  }
1477
  else {
1478
    $form['contains'] = array(
1479
      '#type' => 'select',
1480
      '#title' => t('Contains'),
1481
      '#default_value' => $item['contains'],
1482
      '#options' => array(
1483
        'region' => t('Regions'),
1484
        'column' => t('Columns'),
1485
      ),
1486
    );
1487
  }
1488

    
1489
  $form['save'] = array(
1490
    '#type' => 'submit',
1491
    '#value' => t('Save'),
1492
  );
1493

    
1494
  return $form;
1495
}
1496

    
1497
/**
1498
 * Submit handler for editing a flexible item.
1499
 */
1500
function panels_flexible_add_item_form_submit(&$form, &$form_state) {
1501
  $item = &$form_state['item'];
1502
  $parent = &$form_state['parent'];
1503
  $location = &$form_state['location'];
1504
  $settings = &$form_state['settings'];
1505

    
1506
  $item['class'] = $form_state['values']['class'];
1507

    
1508
  if ($item['type'] == 'region') {
1509
    $item['title'] = $form_state['values']['title'];
1510
  }
1511

    
1512
  if ($item['type'] != 'row') {
1513
    $item['width_type'] = $form_state['values']['width_type'];
1514
  }
1515
  else {
1516
    $item['contains'] = $form_state['values']['contains'];
1517
  }
1518

    
1519
  if ($item['type'] == 'region') {
1520
    // derive the region key from the title
1521
    $key = preg_replace("/[^a-z0-9]/", '_', drupal_strtolower($item['title']));
1522
    while (isset($settings['items'][$key])) {
1523
      $key .= '_';
1524
    }
1525
    $form_state['key'] = $key;
1526
  }
1527
  else {
1528
    $form_state['key'] = $key = max(array_keys($settings['items'])) + 1;
1529
  }
1530

    
1531
  $form_state['sibling'] = NULL;
1532
  if ($item['type'] != 'row' && !empty($parent['children'])) {
1533
    // Figure out what the width should be and adjust our sibling if
1534
    // necessary.
1535
    if ($location == 'left') {
1536
      $form_state['sibling'] = reset($parent['children']);
1537
    }
1538
    else {
1539
      $form_state['sibling'] = end($parent['children']);
1540

    
1541
    }
1542

    
1543
    // If there is no sibling, or the sibling is of a different type,
1544
    // the default 100 will work for either fixed or fluid.
1545
    if ($form_state['sibling'] && $settings['items'][$form_state['sibling']]['width_type'] == $item['width_type']) {
1546
      // steal half of the sibling's space.
1547
      $width = $settings['items'][$form_state['sibling']]['width'] / 2;
1548
      $settings['items'][$form_state['sibling']]['width'] = $width;
1549
      $item['width'] = $width;
1550
    }
1551
  }
1552

    
1553
  // Place the item.
1554
  $settings['items'][$key] = $item;
1555
  if ($location == 'left') {
1556
    array_unshift($parent['children'], $key);
1557
  }
1558
  else {
1559
    $parent['children'][] = $key;
1560
  }
1561
}
1562

    
1563
/**
1564
 * AJAX responder to remove an existing row, column or region from a flexible
1565
 * layout.
1566
 */
1567
function panels_ajax_flexible_edit_remove($handler, $id) {
1568
  $settings = &$handler->display->layout_settings;
1569
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
1570

    
1571
  if (empty($settings['items'][$id])) {
1572
    ajax_render_error(t('Invalid item id.'));
1573
  }
1574

    
1575
  $item = &$settings['items'][$id];
1576
  $css_id = isset($handler->display->css_id) ? $handler->display->css_id : '';
1577
  // Create a renderer object so we can render our new stuff.
1578
  $renderer = panels_flexible_create_renderer(TRUE, $css_id, array(), $settings, $handler->display, $handler->plugins['layout'], $handler);
1579

    
1580

    
1581
  $siblings = &$settings['items'][$item['parent']]['children'];
1582
  $parent_class = '.'  . $renderer->base[$settings['items'][$item['parent']]['type']] . '-' . $item['parent'];
1583

    
1584
  // Find the offset of our array. This will also be the key because
1585
  // this is a simple array.
1586
  $offset = array_search($id, $siblings);
1587

    
1588
  // Only bother with this stuff if our item is fluid, since fixed is
1589
  // as fixed does.
1590
  if ($item['type'] != 'row') {
1591
    if (isset($siblings[$offset + 1])) {
1592
      $next = $siblings[$offset + 1];
1593
    }
1594
    if (isset($siblings[$offset - 1])) {
1595
      $prev = $siblings[$offset - 1];
1596
    }
1597

    
1598
    if ($item['width_type'] == '%') {
1599
      // First, try next.
1600
      if (isset($next) && $settings['items'][$next]['width_type'] == '%') {
1601
        $settings['items'][$next]['width'] += $item['width'];
1602
      }
1603
      // If that failed, try the previous one.
1604
      else if (isset($prev) && $settings['items'][$prev]['width_type'] == '%') {
1605
        $settings['items'][$prev]['width'] += $item['width'];
1606
      }
1607
    }
1608
    // Not sure what happens if they both failed. Maybe nothing.
1609
  }
1610

    
1611
  // Remove the item.
1612
  array_splice($siblings, $offset, 1);
1613

    
1614
  unset($settings['items'][$id]);
1615

    
1616
  // Save our new state.
1617
  panels_edit_cache_set($handler->cache);
1618
  $class = $renderer->base[$item['type']] . '-' . $id;
1619
  $output = array();
1620

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

    
1623
  // Regenerate the CSS for siblings.
1624
  if (!empty($siblings)) {
1625
    // Get all the CSS necessary for the entire row (as width adjustments may
1626
    // have cascaded).
1627
    $css = array();
1628
    panels_flexible_get_css_group($css, $renderer, $siblings, $parent_class, $item['type'], $item['parent']);
1629
    foreach ($css as $selector => $data) {
1630
      $output[] = ajax_command_css($selector, $data);
1631
    }
1632
  }
1633

    
1634
  // There are potentially two splitters linked to this item to be removed.
1635
  if (!empty($prev)) {
1636
    $output[] = ajax_command_remove('.flexible-splitter-for-' . $renderer->base[$item['type']] . '-' . $prev);
1637
  }
1638

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

    
1642
  if (!empty($prev) && !empty($next)) {
1643
    // Add a new splitter that links $prev and $next:
1644
    $splitter = panels_flexible_render_splitter($renderer, $prev, $next);
1645
    $prev_class = '#panels-dnd-main .' . $renderer->base[$item['type']] . '-' . $prev;
1646
    $output[] = ajax_command_after($prev_class, $splitter);
1647
    // Send our fix height command.
1648
    $output[] = array('command' => 'flexible_fix_height');
1649
  }
1650
  // Rerender our parent item links:
1651
  $output[] = ajax_command_replace('.flexible-links-' . $item['parent'],
1652
    panels_flexible_render_item_links($renderer, $item['parent'], $settings['items'][$item['parent']]));
1653

    
1654
  $output[] = array(
1655
    'command' => 'flexible_fix_firstlast',
1656
    'selector' => $parent_class . '-inside',
1657
    'base' => 'panels-flexible-' . $item['type'],
1658
  );
1659

    
1660
  $handler->commands = $output;
1661
}
1662

    
1663
/**
1664
 * AJAX responder to store resize information when the user adjusts the
1665
 * splitter.
1666
 */
1667
function panels_ajax_flexible_edit_resize($handler) {
1668
  ctools_include('ajax');
1669
  $settings = &$handler->display->layout_settings;
1670
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
1671

    
1672
  $settings['items'][$_POST['left']]['width'] = $_POST['left_width'];
1673
  if (!empty($_POST['right']) && $_POST['right'] != $_POST['left']) {
1674
    $settings['items'][$_POST['right']]['width'] = $_POST['right_width'];
1675
  }
1676

    
1677
  // Save our new state.
1678
  panels_edit_cache_set($handler->cache);
1679

    
1680
  $handler->commands = array('ok');
1681
}
1682

    
1683
/**
1684
 * AJAX form to bring up the "reuse" modal.
1685
 */
1686
function panels_ajax_flexible_edit_reuse($handler) {
1687
  $settings = &$handler->display->layout_settings;
1688
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
1689

    
1690
  $form_state = array(
1691
    'display' => &$handler->display,
1692
    'settings' => &$settings,
1693
    'ajax' => TRUE,
1694
    'title' => t('Save this layout for reuse'),
1695
  );
1696

    
1697
  $output = ctools_modal_form_wrapper('panels_flexible_reuse_form', $form_state);
1698
  if (!empty($form_state['executed'])) {
1699
    // Create the new layout.
1700
    ctools_include('export');
1701
    $layout = ctools_export_crud_new('panels_layout');
1702
    $layout->plugin = 'flexible';
1703
    $layout->name = $form_state['values']['name'];
1704
    $layout->admin_title = $form_state['values']['admin_title'];
1705
    $layout->admin_description = $form_state['values']['admin_description'];
1706
    $layout->category = $form_state['values']['category'];
1707
    $layout->settings = $handler->display->layout_settings;
1708

    
1709
    // Save it.
1710
    ctools_export_crud_save('panels_layout', $layout);
1711

    
1712
    if (empty($form_state['values']['keep'])) {
1713
      // Set the actual layout_settings to now use the newly minted layout:
1714
      $handler->display->layout = 'flexible:' . $layout->name;
1715
      $handler->display->layout_settings = array();
1716

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

    
1721
    // Dismiss the modal.
1722
    $output[] = ctools_modal_command_dismiss();
1723
  }
1724

    
1725
  $handler->commands = $output;
1726
}
1727

    
1728
function panels_flexible_reuse_form($form, &$form_state) {
1729
  $form['markup'] = array(
1730
    '#prefix' => '<div class="description">',
1731
    '#suffix' => '</div>',
1732
    '#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.'),
1733
  );
1734

    
1735
  $form['admin_title'] = array(
1736
    '#type' => 'textfield',
1737
    '#title' => t('Administrative title'),
1738
    '#description' => t('This will appear in the administrative interface to easily identify it.'),
1739
  );
1740

    
1741
  $form['name'] = array(
1742
    '#type' => 'machine_name',
1743
    '#title' => t('Machine name'),
1744
    '#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!'),
1745
    '#machine_name' => array(
1746
      'exists' => 'panels_flexible_edit_name_exists',
1747
      'source' => array('admin_title'),
1748
    ),
1749
  );
1750

    
1751
  $form['category'] = array(
1752
    '#type' => 'textfield',
1753
    '#title' => t('Category'),
1754
    '#description' => t('What category this layout should appear in. If left blank the category will be "Miscellaneous".'),
1755
  );
1756

    
1757
  $form['admin_description'] = array(
1758
    '#type' => 'textarea',
1759
    '#title' => t('Administrative description'),
1760
    '#description' => t('A description of what this layout is, does or is for, for administrative use.'),
1761
  );
1762

    
1763
  $form['keep'] = array(
1764
    '#type' => 'checkbox',
1765
    '#title' => t('Keep current panel layout flexible'),
1766
    '#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.'),
1767
  );
1768

    
1769
  $form['submit'] = array(
1770
    '#type' => 'submit',
1771
    '#value' => t('Save'),
1772
  );
1773

    
1774
  return $form;
1775
}
1776

    
1777
function panels_flexible_reuse_form_validate(&$form, &$form_state) {
1778
  if (empty($form_state['values']['name'])) {
1779
    form_error($form['name'], t('You must choose a machine name.'));
1780
  }
1781

    
1782
  ctools_include('export');
1783
  $test = ctools_export_crud_load('panels_layout', $form_state['values']['name']);
1784
  if ($test) {
1785
    form_error($form['name'], t('That name is used by another layout: @layout', array('@layout' => $test->admin_title)));
1786
  }
1787

    
1788
  // Ensure name fits the rules:
1789
  if (preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['name'])) {
1790
    form_error($form['name'], t('Name must be alphanumeric or underscores only.'));
1791
  }
1792
}
1793

    
1794
/**
1795
 * Test for #machine_name type to see if an export exists.
1796
 */
1797
function panels_flexible_edit_name_exists($name, $element, &$form_state) {
1798
  ctools_include('export');
1799
  $plugin = $form_state['plugin'];
1800

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