Projet

Général

Profil

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

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

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
        if (empty($renderer->settings['items'][$id]['hide_empty']) || trim($content)) {
475
          $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max);
476
        }
477
        break;
478
      case 'row':
479
        $content = panels_flexible_render_items($renderer, $item['children'], $renderer->base['row'] . '-' . $id);
480
        if (empty($renderer->settings['items'][$id]['hide_empty']) || trim($content)) {
481
          $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max, TRUE);
482
        }
483
        break;
484
      case 'region':
485
        if (empty($renderer->settings['items'][$id]['hide_empty'])) {
486
          $content = isset($renderer->content[$id]) ? $renderer->content[$id] : "&nbsp;";
487
        }
488
        else {
489
          $content = isset($renderer->content[$id]) ? trim($renderer->content[$id]) : "";
490
        }
491
        if (empty($renderer->settings['items'][$id]['hide_empty']) || $content) {
492
          $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max);
493
        }
494
        break;
495
    }
496

    
497
    // If all items are fixed then we have a special splitter on the right to
498
    // control the overall width.
499
    if (!empty($renderer->admin) && $max == $position && $location == 'left') {
500
      $groups[$location] .= panels_flexible_render_splitter($renderer, $id, NULL);
501
    }
502
    $prev = $id;
503
  }
504

    
505
  $group_count = count(array_filter($groups));
506

    
507
  // Render each group. We only render the group div if we're in admin mode
508
  // or if there are multiple groups.
509
  foreach ($groups as $position => $content) {
510
    if (!empty($content) || $renderer->admin) {
511
      if ($group_count > 1 || $renderer->admin) {
512
        $output .= '<div class="' . $owner_id . '-' . $position . '">' . $content . '</div>';
513
      }
514
      else {
515
        $output .= $content;
516
      }
517
    }
518
  }
519

    
520
  return $output;
521
}
522

    
523
/**
524
 * Render a column in the flexible layout.
525
 */
526
function panels_flexible_render_item($renderer, $item, $content, $id, $position, $max, $clear = FALSE) {
527

    
528
  // If we are rendering a row and there is just one row, we don't need to
529
  // render the row unless there is fixed_width content inside it.
530
  if (empty($renderer->admin) && $item['type'] == 'row' && $max == 0) {
531
    $fixed = FALSE;
532
    foreach ($item['children'] as $id) {
533
      if ($renderer->settings['items'][$id]['width_type'] != '%') {
534
        $fixed = TRUE;
535
        break;
536
      }
537
    }
538

    
539
    if (!$fixed) {
540
      return $content;
541
    }
542
  }
543

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

    
550
  $base = $renderer->item_class[$item['type']];
551
  $output = '<div class="' . $base . ' ' . $renderer->base[$item['type']] . '-' . $id;
552
  if ($position == 0) {
553
    $output .= ' ' . $base . '-first';
554
  }
555
  if ($position == $max) {
556
    $output .= ' ' . $base . '-last';
557
  }
558
  if ($clear) {
559
    $output .= ' clearfix';
560
  }
561

    
562
  if (isset($item['class'])) {
563
    $output .= ' ' . check_plain($item['class']);
564
  }
565

    
566
  $output .= '">' . "\n";
567

    
568
  if (!empty($renderer->admin)) {
569
    $output .= panels_flexible_render_item_links($renderer, $id, $item);
570
  }
571

    
572
  $output .= '  <div class="inside ' . $base . '-inside ' . $base . '-' . $renderer->base_class . '-' . $id . '-inside';
573
  if ($position == 0) {
574
    $output .= ' ' . $base . '-inside-first';
575
  }
576
  if ($position == $max) {
577
    $output .= ' ' . $base . '-inside-last';
578
  }
579
  if ($clear) {
580
    $output .= ' clearfix';
581
  }
582

    
583
  $output .= "\">\n";
584
  $output .= $content;
585
  $output .= '  </div>' . "\n";
586
  $output .= '</div>' . "\n";
587

    
588
  return $output;
589
}
590
/**
591
 * Render a splitter div to place between the $left and $right items.
592
 *
593
 * If the right ID is NULL that means there isn't actually a box to the
594
 * right, but we need a splitter anyway. We'll mostly use info about the
595
 * left, but pretend it's 'fluid' so that the javascript won't actually
596
 * modify the right item.
597
 */
598
function panels_flexible_render_splitter($renderer, $left_id, $right_id) {
599
  $left = $renderer->settings['items'][$left_id];
600

    
601
  $left_class = $renderer->base[$left['type']] . '-' . $left_id;
602
  if ($right_id) {
603
    $right = $renderer->settings['items'][$right_id];
604
    $right_class = $renderer->base[$left['type']] . '-' . $right_id;
605
  }
606
  else {
607
    $right = $left;
608
    $right_class = $left_class;
609
  }
610

    
611
  $output = '<div tabindex="0"
612
    class="panels-flexible-splitter flexible-splitter-for-' . $left_class . '">';
613

    
614
  // Name the left object
615
  $output .= '<span class="panels-flexible-splitter-left">';
616
  $output .= '.' . $left_class;
617
  $output .= '</span>';
618

    
619
  $output .= '<span class="panels-flexible-splitter-left-id">';
620
  $output .= $left_id;
621
  $output .= '</span>';
622

    
623
  $output .= '<span class="panels-flexible-splitter-left-width ' . $left_class . '-width">';
624
  $output .= $left['width'];
625
  $output .= '</span>';
626

    
627
  $output .= '<span class="panels-flexible-splitter-left-scale">';
628
  $output .= isset($renderer->scale[$left_id]) ? $renderer->scale[$left_id] : 1;
629
  $output .= '</span>';
630

    
631
  $output .= '<span class="panels-flexible-splitter-left-width-type">';
632
  $output .= $left['width_type'];
633
  $output .= '</span>';
634

    
635
  // Name the right object
636
  $output .= '<span class="panels-flexible-splitter-right">';
637
  $output .= '.' . $right_class;
638
  $output .= '</span>';
639

    
640
  $output .= '<span class="panels-flexible-splitter-right-id">';
641
  $output .= $right_id;
642
  $output .= '</span>';
643

    
644
  $output .= '<span class="panels-flexible-splitter-right-width ' . $right_class . '-width">';
645
  $output .= $right['width'];
646
  $output .= '</span>';
647

    
648
  $output .= '<span class="panels-flexible-splitter-right-scale">';
649
  $output .= isset($renderer->scale[$right_id]) ? $renderer->scale[$right_id] : 1;
650
  $output .= '</span>';
651

    
652
  $output .= '<span class="panels-flexible-splitter-right-width-type">';
653
  // If there is no right, make it fluid.
654
  $output .= $right_id ? $right['width_type'] : '%';
655
  $output .= '</span>';
656

    
657
  $output .= '</div>';
658
  return $output;
659
}
660

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

    
706
  if (!empty($settings)) {
707
    $links[] = array(
708
      'title' => $settings,
709
      'href' => $renderer->handler->get_url('layout', 'settings', $id),
710
      'attributes' => array('class' => array('ctools-use-modal')),
711
    );
712
  }
713
  if ($add) {
714
    $links[] = array(
715
      'title' => $add,
716
      'href' => $renderer->handler->get_url('layout', 'add', $id),
717
      'attributes' => array('class' => array('ctools-use-modal')),
718
    );
719
  }
720
  if (isset($add2)) {
721
    $links[] = array(
722
      'title' => $add2,
723
      'href' => $renderer->handler->get_url('layout', 'add', $id, 'right'),
724
      'attributes' => array('class' => array('ctools-use-modal')),
725
    );
726
  }
727
  if ($remove) {
728
    $links[] = array(
729
      'title' => $remove,
730
      'href' => $renderer->handler->get_url('layout', 'remove', $id),
731
      'attributes' => array('class' => array('use-ajax')),
732
    );
733
  }
734

    
735
  return theme('ctools_dropdown', array('title' => $title, 'links' => $links, 'class' => 'flexible-layout-only flexible-links flexible-title flexible-links-' . $id));
736
}
737
/**
738
 * Provide CSS for a flexible layout.
739
 */
740
function panels_flexible_render_css($renderer) {
741
  if ($renderer->admin) {
742
    $parent_class = '.' . $renderer->base['row'] . '-canvas';
743
  }
744
  else {
745
    $parent_class = '.' . $renderer->base['canvas'];
746
  }
747
  return panels_flexible_render_css_group($renderer, $renderer->settings['items']['canvas']['children'], $parent_class, 'column', 'canvas');
748
}
749

    
750
/**
751
 * Render the CSS for a group of items to be displayed together.
752
 *
753
 * Columns and regions, when displayed as a group, need to cooperate in
754
 * order to share margins and make sure that percent widths add up
755
 * to the right total.
756
 */
757
function panels_flexible_render_css_group($renderer, $list, $owner_id, $type, $id) {
758
  $css = array();
759

    
760
  // Start off with some generic CSS to properly pad regions
761
  $css[$owner_id . ' .' . $renderer->item_class['region']] = array(
762
    'padding' => '0',
763
  );
764

    
765
  $css[$owner_id . ' .' . $renderer->item_class['region'] . '-inside'] = array(
766
    'padding-right' => $renderer->region_separation,
767
    'padding-left' => $renderer->region_separation,
768
  );
769

    
770
  $css[$owner_id . ' .' . $renderer->item_class['region'] . '-inside-first'] = array(
771
    'padding-left' => '0',
772
  );
773

    
774
  $css[$owner_id . ' .' . $renderer->item_class['region'] . '-inside-last'] = array(
775
    'padding-right' => '0',
776
  );
777

    
778
  $css[$owner_id . ' .' . $renderer->item_class['column']] = array(
779
    'padding' => '0',
780
  );
781

    
782
  $css[$owner_id . ' .' . $renderer->item_class['column'] . '-inside'] = array(
783
    'padding-right' => $renderer->column_separation,
784
    'padding-left' => $renderer->column_separation,
785
  );
786

    
787
  $css[$owner_id . ' .' . $renderer->item_class['column'] . '-inside-first'] = array(
788
    'padding-left' => '0',
789
  );
790

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

    
795
  // And properly pad rows too
796
  $css[$owner_id . ' .' . $renderer->item_class['row']] = array(
797
    'padding' => '0 0 ' . $renderer->row_separation . ' 0',
798
    'margin' => '0',
799
  );
800

    
801
  $css[$owner_id . ' .' . $renderer->item_class['row'] . '-last'] = array(
802
    'padding-bottom' => '0',
803
  );
804

    
805
  panels_flexible_get_css_group($css, $renderer, $list, $owner_id, $type, $id);
806

    
807
  ctools_include('css');
808
  return ctools_css_assemble($css);
809
}
810

    
811
/**
812
 * Construct an array with all of the CSS properties for a group.
813
 *
814
 * This will parse down into children and produce all of the CSS needed if you
815
 * start from the top.
816
 */
817
function panels_flexible_get_css_group(&$css, $renderer, $list, $owner_id, $type, $item_id) {
818
  if ($type != 'row') {
819
    // Go through our items and break up into right/center/right groups so we
820
    // can figure out our offsets.
821

    
822
    // right == any items on the right that are 'fixed'.
823
    // middle == all fluid items.
824
    // right == any items on the right that are 'fixed'.
825
    $left = $middle = $right = array();
826
    $left_total = $right_total = $middle_total = 0;
827
    $current = 'left';
828
    foreach ($list as $id) {
829
      if ($renderer->settings['items'][$id]['width_type'] == 'px') {
830
        // fixed
831
        if ($current == 'left') {
832
          $left[] = $id;
833
          $renderer->positions[$id] = 'left';
834
          $left_total += $renderer->settings['items'][$id]['width'];
835
        }
836
        else {
837
          $current = 'right';
838
          $right[] = $id;
839
          $renderer->positions[$id] = 'right';
840
          $right_total += $renderer->settings['items'][$id]['width'];
841
        }
842
      }
843
      else {
844
        // fluid
845
        if ($current != 'right') {
846
          $current = 'middle';
847
          $middle[] = $id;
848
          $renderer->positions[$id] = 'middle';
849
          $middle_total += $renderer->settings['items'][$id]['width'];
850
        }
851
        // fall through: if current is 'right' and we ran into a 'fluid' then
852
        // it gets *dropped* because that is invalid.
853
      }
854
    }
855

    
856
    // Go through our right sides and create CSS.
857
    foreach ($left as $id) {
858
      $class = "." . $renderer->base[$type] . "-$id";
859
      $css[$class] = array(
860
        'position' => 'relative',
861
        'float' => 'left',
862
        'background-color' => 'transparent',
863
        'width' => $renderer->settings['items'][$id]['width'] . "px",
864
      );
865
    }
866

    
867
    // Do the same for right.
868
    $right_pixels = 0;
869

    
870
    foreach ($right as $id) {
871
      $class = "." . $renderer->base[$type] . "-$id";
872
      $css[$class] = array(
873
        'float' => 'left',
874
        'width' => $renderer->settings['items'][$id]['width'] . "px",
875
      );
876
    }
877

    
878
    $max = count($middle) - 1;
879

    
880
    if ($middle_total) {
881
      // Because we love IE so much, auto scale everything to 99%. This
882
      // means adding up the actual widths and then providing a multiplier
883
      // to each so that the total is 99%.
884
      $scale = $renderer->scale_base / $middle_total;
885
      foreach ($middle as $position => $id) {
886
        $class = "." . $renderer->base[$type] . "-$id";
887
        $css[$class] = array(
888
          'float' => 'left',
889
          'width' => number_format($renderer->settings['items'][$id]['width'] * $scale, 4, '.', '') . "%",
890
        );
891

    
892
        // Store this so we can use it later.
893
        // @todo: Store the scale, not the new width, so .js can adjust
894
        // bi-directionally.
895
        $renderer->scale[$id] = $scale;
896
      }
897
    }
898

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

    
925
  // If the canvas has a fixed width set, and this is the canvas, fix the
926
  // width.
927
  if ($item_id == 'canvas') {
928
    $item = $renderer->settings['items'][$item_id];
929

    
930
    if (!empty($item['fixed_width']) && intval($item['fixed_width'])) {
931
      $css['.' . $renderer->base['canvas']]['width'] = intval($item['fixed_width']) . 'px';
932
    }
933
    else {
934
      $css['.' . $renderer->base['canvas']]['width'] = 'auto';
935
    }
936
  }
937

    
938
  // Go through each item and process children.
939
  foreach ($list as $id) {
940
    $item = $renderer->settings['items'][$id];
941
    if (empty($item['children'])) {
942
      continue;
943
    }
944

    
945
    if ($type == 'column') {
946
      // Columns can only contain rows.
947
      $child_type = 'row';
948
    }
949
    else {
950
      $child_type = isset($item['contains']) ? $item['contains'] : 'region';
951
    }
952

    
953
    $class = "." . $renderer->base[$type] . "-$id";
954
    panels_flexible_get_css_group($css, $renderer, $item['children'], $class, $child_type, $id);
955
  }
956
}
957

    
958
/**
959
 * AJAX responder to edit flexible settings for an item.
960
 *
961
 * $handler object
962
 *   The display renderer handler object.
963
 */
964
function panels_ajax_flexible_edit_settings($handler, $id) {
965
  $settings = &$handler->display->layout_settings;
966
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
967

    
968
  if (empty($settings['items'][$id])) {
969
    ctools_modal_render(t('Error'), t('Invalid item id.'));
970
  }
971

    
972
  $item = &$settings['items'][$id];
973
  $siblings = array();
974

    
975
  if ($id != 'canvas') {
976
    $siblings = $settings['items'][$item['parent']]['children'];
977
  }
978

    
979

    
980
  switch ($item['type']) {
981
    case 'column':
982
      $title = t('Configure column');
983
      break;
984
    case 'row':
985
      if ($id == 'canvas') {
986
        $title = t('Configure canvas');
987
      }
988
      else {
989
        $title = t('Configure row');
990
      }
991
      break;
992
    case 'region':
993
      $title = t('Configure region');
994
      break;
995
  }
996

    
997
  $form_state = array(
998
    'display' => &$handler->display,
999
    'item' => &$item,
1000
    'id' => $id,
1001
    'siblings' => $siblings,
1002
    'settings' => &$settings,
1003
    'ajax' => TRUE,
1004
    'title' => $title,
1005
    'op' => 'edit',
1006
  );
1007

    
1008
  $output = ctools_modal_form_wrapper('panels_flexible_config_item_form', $form_state);
1009
  if (!empty($form_state['executed'])) {
1010
    // If the width type changed then other nearby items will have
1011
    // to have their widths adjusted.
1012
    panels_edit_cache_set($handler->cache);
1013

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

    
1017
    $output = array();
1018
    // If the item is a region, replace the title.
1019
    $class = $renderer->base[$item['type']] . '-' . $id;
1020
    if ($item['type'] == 'region') {
1021
      $output[] = ajax_command_replace(".$class h2.label",
1022
        '<h2 class="label">' . check_plain($item['title']) . '</h2>');
1023
    }
1024

    
1025
    // Rerender our links in case something changed.
1026
    $output[] = ajax_command_replace('.flexible-links-' . $id,
1027
      panels_flexible_render_item_links($renderer, $id, $item));
1028

    
1029
    // If editing the canvas, reset the CSS width
1030
    if ($id == 'canvas') {
1031
      // update canvas CSS.
1032
      $css = array(
1033
        '.' . $renderer->item_class['column'] . '-inside' => array(
1034
          'padding-left' => $renderer->column_separation,
1035
          'padding-right' => $renderer->column_separation,
1036
        ),
1037
        '.' . $renderer->item_class['region'] . '-inside' => array(
1038
          'padding-left' => $renderer->region_separation,
1039
          'padding-right' => $renderer->region_separation,
1040
        ),
1041
        '.' . $renderer->item_class['row'] => array(
1042
          'padding-bottom' => $renderer->row_separation,
1043
        ),
1044
      );
1045
      if (!empty($item['fixed_width']) && intval($item['fixed_width'])) {
1046
        $css['.' . $renderer->base['canvas']] = array('width' => intval($item['fixed_width']) . 'px');
1047
      }
1048
      else {
1049
        $css['.' . $renderer->base['canvas']] = array('width' => 'auto');
1050
      }
1051
      foreach ($css as $selector => $data) {
1052
        $output[] = ajax_command_css($selector, $data);
1053
      }
1054
    }
1055

    
1056
    $output[] = ctools_modal_command_dismiss();
1057
  }
1058

    
1059
  $handler->commands = $output;
1060
}
1061

    
1062
/**
1063
 * Configure a row, column or region on the flexible page.
1064
 *
1065
 * @param <type> $form_state
1066
 * @return <type>
1067
 */
1068
function panels_flexible_config_item_form($form, &$form_state) {
1069
  $display = &$form_state['display'];
1070
  $item = &$form_state['item'];
1071
  $siblings = &$form_state['siblings'];
1072
  $settings = &$form_state['settings'];
1073
  $id = &$form_state['id'];
1074

    
1075
  if ($item['type'] == 'region') {
1076
    $form['title'] = array(
1077
      '#title' => t('Region title'),
1078
      '#type' => 'textfield',
1079
      '#default_value' => $item['title'],
1080
      '#required' => TRUE,
1081
    );
1082
  }
1083

    
1084
  if ($id == 'canvas') {
1085
    $form['class'] = array(
1086
      '#title' => t('Canvas class'),
1087
      '#type' => 'textfield',
1088
      '#default_value' => isset($item['class']) ? $item['class'] : '',
1089
      '#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.'),
1090
    );
1091

    
1092
    $form['column_class'] = array(
1093
      '#title' => t('Column class'),
1094
      '#type' => 'textfield',
1095
      '#default_value' => isset($item['column_class']) ? $item['column_class'] : '',
1096
      '#description' => t('This class will be applied to all columns of the layout. If left blank this will be panels-flexible-column.'),
1097
    );
1098

    
1099
    $form['row_class'] = array(
1100
      '#title' => t('Row class'),
1101
      '#type' => 'textfield',
1102
      '#default_value' => isset($item['row_class']) ? $item['row_class'] : '',
1103
      '#description' => t('This class will be applied to all rows of the layout. If left blank this will be panels-flexible-row.'),
1104
    );
1105

    
1106
    $form['region_class'] = array(
1107
      '#title' => t('Region class'),
1108
      '#type' => 'textfield',
1109
      '#default_value' => isset($item['region_class']) ? $item['region_class'] : '',
1110
      '#description' => t('This class will be applied to all regions of the layout. If left blank this will be panels-flexible-region.'),
1111
    );
1112

    
1113
    $form['no_scale'] = array(
1114
      '#type' => 'checkbox',
1115
      '#title' => t('Scale fluid widths for IE6'),
1116
      '#description' => t('IE6 does not do well with 100% widths. If checked, width will be scaled to 99% to compensate.'),
1117
      '#default_value' => empty($item['no_scale']),
1118
    );
1119

    
1120
    $form['fixed_width'] = array(
1121
      '#type' => 'textfield',
1122
      '#title' => t('Fixed width'),
1123
      '#description' => t('If a value is entered, the layout canvas will be fixed to the given pixel width.'),
1124
      '#default_value' => isset($item['fixed_width']) ? $item['fixed_width'] : '',
1125
    );
1126

    
1127
    $form['column_separation'] = array(
1128
      '#type' => 'textfield',
1129
      '#title' => t('Column separation'),
1130
      '#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.'),
1131
      '#default_value' => isset($item['column_separation']) ? $item['column_separation'] : '0.5em',
1132
    );
1133

    
1134
    $form['region_separation'] = array(
1135
      '#type' => 'textfield',
1136
      '#title' => t('Region separation'),
1137
      '#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.'),
1138
      '#default_value' => isset($item['region_separation']) ? $item['region_separation'] : '0.5em',
1139
    );
1140

    
1141
    $form['row_separation'] = array(
1142
      '#type' => 'textfield',
1143
      '#title' => t('Row separation'),
1144
      '#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.'),
1145
      '#default_value' => isset($item['row_separation']) ? $item['row_separation'] : '0.5em',
1146
    );
1147
  }
1148
  else {
1149
    $form['class'] = array(
1150
      '#title' => t('CSS class'),
1151
      '#type' => 'textfield',
1152
      '#default_value' => isset($item['class']) ? $item['class'] : '',
1153
      '#description' => t('Enter a CSS class that will be used. This can be used to apply automatic styling from your theme, for example.'),
1154
    );
1155

    
1156
    if ($item['type'] != 'row') {
1157
      // Test to see if there are fluid items to the left or the right. If there
1158
      // are fluid items on both sides, this item cannot be set to fixed.
1159
      $left = $right = FALSE;
1160
      $current = 'left';
1161
      foreach ($siblings as $sibling) {
1162
        if ($sibling == $id) {
1163
          $current = 'right';
1164
        }
1165
        else if ($settings['items'][$sibling]['width_type'] == '%') {
1166
          $$current = TRUE; // Indirection.
1167
        }
1168
      }
1169

    
1170
      $form['width_type'] = array(
1171
        '#type' => 'select',
1172
        '#title' => t('Width'),
1173
        '#default_value' => $item['width_type'],
1174
        '#options' => array(
1175
          '%' => t('Fluid'),
1176
          'px' => t('Fixed'),
1177
        ),
1178
        '#disabled' => TRUE,
1179
      );
1180
    }
1181
    else {
1182
      $form['contains'] = array(
1183
        '#type' => 'select',
1184
        '#title' => t('Contains'),
1185
        '#default_value' => $item['contains'],
1186
        '#options' => array(
1187
          'region' => t('Regions'),
1188
          'column' => t('Columns'),
1189
        ),
1190
      );
1191

    
1192
      if (!empty($item['children'])) {
1193
        $form['contains']['#disabled'] = TRUE;
1194
        $form['contains']['#value'] = $item['contains'];
1195
        $form['contains']['#description'] = t('You must remove contained items to change the row container type.');
1196
      }
1197
    }
1198
  }
1199

    
1200
  $form['hide_empty'] = array(
1201
    '#title' => t('Hide element if empty'),
1202
    '#type' => 'checkbox',
1203
    '#default_value' => !empty($item['hide_empty']) ? 1 : 0,
1204
  );
1205

    
1206
  $form['save'] = array(
1207
    '#type' => 'submit',
1208
    '#value' => t('Save'),
1209
  );
1210

    
1211
  return $form;
1212
}
1213

    
1214
/**
1215
 * Submit handler for editing a flexible item.
1216
 */
1217
function panels_flexible_config_item_form_submit(&$form, &$form_state) {
1218
  $item = &$form_state['item'];
1219
  if ($item['type'] == 'region') {
1220
    $item['title'] = $form_state['values']['title'];
1221
  }
1222

    
1223
  $item['class'] = $form_state['values']['class'];
1224

    
1225
  if ($form_state['id'] == 'canvas') {
1226
    $item['column_class'] = $form_state['values']['column_class'];
1227
    $item['row_class'] = $form_state['values']['row_class'];
1228
    $item['region_class'] = $form_state['values']['region_class'];
1229
    // Reverse this as the checkbox is backward from how we actually store
1230
    // it to make it simpler to default to scaling.
1231
    $item['no_scale'] = !$form_state['values']['no_scale'];
1232
    $item['fixed_width'] = $form_state['values']['fixed_width'];
1233
    $item['column_separation'] = $form_state['values']['column_separation'];
1234
    $item['region_separation'] = $form_state['values']['region_separation'];
1235
    $item['row_separation'] = $form_state['values']['row_separation'];
1236
  }
1237
  else if ($item['type'] != 'row') {
1238
    $item['width_type'] = $form_state['values']['width_type'];
1239
  }
1240
  else {
1241
    $item['contains'] = $form_state['values']['contains'];
1242
  }
1243
  $item['hide_empty'] = $form_state['values']['hide_empty'];
1244

    
1245
}
1246

    
1247
/**
1248
 * AJAX responder to add a new row, column or region to a flexible layout.
1249
 */
1250
function panels_ajax_flexible_edit_add($handler, $id, $location = 'left') {
1251
  ctools_include('modal');
1252
  ctools_include('ajax');
1253
  $settings = &$handler->display->layout_settings;
1254
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
1255

    
1256
  if (empty($settings['items'][$id])) {
1257
    ctools_modal_render(t('Error'), t('Invalid item id.'));
1258
  }
1259

    
1260
  $parent = &$settings['items'][$id];
1261

    
1262
  switch ($parent['type']) {
1263
    case 'column':
1264
      $title = t('Add row');
1265
      // Create the new item with defaults.
1266
      $item = array(
1267
        'type' => 'row',
1268
        'contains' => 'region',
1269
        'children' => array(),
1270
        'parent' => $id,
1271
      );
1272
      break;
1273
    case 'row':
1274
      switch ($parent['contains']) {
1275
        case 'region':
1276
          $title = $location == 'left' ? t('Add region to left') : t('Add region to right');
1277
          $item = array(
1278
            'type' => 'region',
1279
            'title' => '',
1280
            'width' => 100,
1281
            'width_type' => '%',
1282
            'parent' => $id,
1283
          );
1284
          break;
1285
        case 'column':
1286
          $title = $location == 'left' ? t('Add column to left') : t('Add column to right');
1287
          $item = array(
1288
            'type' => 'column',
1289
            'width' => 100,
1290
            'width_type' => '%',
1291
            'parent' => $id,
1292
            'children' => array(),
1293
          );
1294
          break;
1295
      }
1296
      // Create the new item with defaults.
1297
      break;
1298
    case 'region':
1299
      // Cannot add items to regions.
1300
      break;
1301
  }
1302

    
1303
  $form_state = array(
1304
    'display' => &$handler->display,
1305
    'parent' => &$parent,
1306
    'item' => &$item,
1307
    'id' => $id,
1308
    'settings' => &$settings,
1309
    'ajax' => TRUE,
1310
    'title' => $title,
1311
    'location' => $location,
1312
  );
1313

    
1314
  $output = ctools_modal_form_wrapper('panels_flexible_add_item_form', $form_state);
1315
  if (!empty($form_state['executed'])) {
1316
    // If the width type changed then other nearby items will have
1317
    // to have their widths adjusted.
1318
    panels_edit_cache_set($handler->cache);
1319
    $output = array();
1320

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

    
1325
    $content = '';
1326
    if ($item['type'] == 'region') {
1327
      $handler->plugins['layout']['regions'][$form_state['key']] = $item['title'];
1328

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

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

    
1335
    }
1336
    else {
1337
      // We need to make sure the left/middle/right divs exist inside this
1338
      // so that more stuff can be added inside it as needed.
1339
      foreach (array('left', 'middle', 'right') as $position) {
1340
        if (!empty($content) || $renderer->admin) {
1341
          $content .= '<div class="' . $renderer->base[$item['type']] . '-' . $form_state['key'] . '-' . $position . '"></div>';
1342
        }
1343
      }
1344

    
1345
    }
1346

    
1347
    // render the item
1348
    $parent_class =  $renderer->base[$parent['type']] . '-' . $id;
1349
    $item_output = panels_flexible_render_item($renderer, $item, $content, $form_state['key'], 0, 0, $item['type'] == 'row');
1350

    
1351
    // Get all the CSS necessary for the entire row (as width adjustments may
1352
    // have cascaded).
1353
    $css = array();
1354
    panels_flexible_get_css_group($css, $renderer, $parent['children'], '.' . $parent_class, $item['type'], $id);
1355

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

    
1398
    }
1399

    
1400
    // Send our fix height command.
1401
    $output[] = array('command' => 'flexible_fix_height');
1402

    
1403
    if (!empty($form_state['sibling'])) {
1404
      $sibling_width = '#panels-dnd-main .' . $renderer->base[$item['type']] . '-' . $form_state['sibling'] . '-width';
1405
      $output[] = array(
1406
        'command' => 'flexible_set_width',
1407
        'selector' => $sibling_width,
1408
        'width' => $settings['items'][$form_state['sibling']]['width'],
1409
      );
1410
    }
1411
    foreach ($css as $selector => $data) {
1412
      $output[] = ajax_command_css($selector, $data);
1413
    }
1414

    
1415
    // Rerender our parent item links:
1416
    $output[] = ajax_command_replace('.flexible-links-' . $id,
1417
      panels_flexible_render_item_links($renderer, $id, $parent));
1418

    
1419
    $output[] = array(
1420
      'command' => 'flexible_fix_firstlast',
1421
      'selector' => '.' . $parent_class . '-inside',
1422
      'base' => 'panels-flexible-' . $item['type'],
1423
    );
1424

    
1425
    $output[] = ctools_modal_command_dismiss();
1426
  }
1427

    
1428
  $handler->commands = $output;
1429
}
1430
/**
1431
 * Form to add a row, column or region to a flexible layout.
1432
 * @param <type> $form_state
1433
 * @return <type>
1434
 */
1435
function panels_flexible_add_item_form($form, &$form_state) {
1436
  $display = &$form_state['display'];
1437
  $item = &$form_state['item'];
1438
  $parent = &$form_state['parent'];
1439
  $settings = &$form_state['settings'];
1440
  $location = &$form_state['location'];
1441
  $id = &$form_state['id'];
1442

    
1443
  if ($item['type'] == 'region') {
1444
    $form['title'] = array(
1445
      '#title' => t('Region title'),
1446
      '#type' => 'textfield',
1447
      '#default_value' => $item['title'],
1448
      '#required' => TRUE,
1449
    );
1450
  }
1451

    
1452
  $form['class'] = array(
1453
    '#title' => t('CSS Class'),
1454
    '#type' => 'textfield',
1455
    '#default_value' => isset($item['class']) ? $item['class'] : '',
1456
    '#description' => t('Enter a CSS class that will be used. This can be used to apply automatic styling from your theme, for example.'),
1457
  );
1458

    
1459
  if ($item['type'] != 'row') {
1460
    // If there is a 'fixed' type on the side we're adding to, then this
1461
    // must also be fixed. Otherwise it can be either and should default to
1462
    // fluid.
1463
    $restrict = FALSE;
1464

    
1465
    if (!empty($parent['children'])) {
1466
      if ($location == 'left') {
1467
        $sibling = reset($parent['children']);
1468
      }
1469
      else {
1470
        $sibling = end($parent['children']);
1471
      }
1472
      if ($settings['items'][$sibling]['width_type'] == 'px') {
1473
        $restrict = TRUE;
1474
        $item['width_type'] = 'px';
1475
      }
1476
    }
1477

    
1478
    $form['width_type'] = array(
1479
      '#type' => 'select',
1480
      '#title' => t('Width'),
1481
      '#default_value' => $item['width_type'],
1482
      '#options' => array(
1483
        '%' => t('Fluid'),
1484
        'px' => t('Fixed'),
1485
      ),
1486
      '#disabled' => $restrict,
1487
    );
1488
    if ($restrict) {
1489
      // This forces the value because disabled items don't always send
1490
      // their data back.
1491
      $form['width_type']['#value'] = $item['width_type'];
1492
      $form['width_type']['#description'] = t('Items cannot be set to fluid if there are fixed items already on that side.');
1493
    }
1494
  }
1495
  else {
1496
    $form['contains'] = array(
1497
      '#type' => 'select',
1498
      '#title' => t('Contains'),
1499
      '#default_value' => $item['contains'],
1500
      '#options' => array(
1501
        'region' => t('Regions'),
1502
        'column' => t('Columns'),
1503
      ),
1504
    );
1505
  }
1506

    
1507
  $form['hide_empty'] = array(
1508
    '#title' => t('Hide element if empty'),
1509
    '#type' => 'checkbox',
1510
    '#default_value' => 0,
1511
  );
1512

    
1513
  $form['save'] = array(
1514
    '#type' => 'submit',
1515
    '#value' => t('Save'),
1516
  );
1517

    
1518
  return $form;
1519
}
1520

    
1521
/**
1522
 * Submit handler for editing a flexible item.
1523
 */
1524
function panels_flexible_add_item_form_submit(&$form, &$form_state) {
1525
  $item = &$form_state['item'];
1526
  $parent = &$form_state['parent'];
1527
  $location = &$form_state['location'];
1528
  $settings = &$form_state['settings'];
1529

    
1530
  $item['class'] = $form_state['values']['class'];
1531

    
1532
  if ($item['type'] == 'region') {
1533
    $item['title'] = $form_state['values']['title'];
1534
  }
1535

    
1536
  if ($item['type'] != 'row') {
1537
    $item['width_type'] = $form_state['values']['width_type'];
1538
  }
1539
  else {
1540
    $item['contains'] = $form_state['values']['contains'];
1541
  }
1542

    
1543
  $item['hide_empty'] = $form_state['values']['hide_empty'];
1544

    
1545
  if ($item['type'] == 'region') {
1546
    // derive the region key from the title
1547
    $key = preg_replace("/[^a-z0-9]/", '_', drupal_strtolower($item['title']));
1548
    while (isset($settings['items'][$key])) {
1549
      $key .= '_';
1550
    }
1551
    $form_state['key'] = $key;
1552
  }
1553
  else {
1554
    $form_state['key'] = $key = max(array_keys($settings['items'])) + 1;
1555
  }
1556

    
1557
  $form_state['sibling'] = NULL;
1558
  if ($item['type'] != 'row' && !empty($parent['children'])) {
1559
    // Figure out what the width should be and adjust our sibling if
1560
    // necessary.
1561
    if ($location == 'left') {
1562
      $form_state['sibling'] = reset($parent['children']);
1563
    }
1564
    else {
1565
      $form_state['sibling'] = end($parent['children']);
1566

    
1567
    }
1568

    
1569
    // If there is no sibling, or the sibling is of a different type,
1570
    // the default 100 will work for either fixed or fluid.
1571
    if ($form_state['sibling'] && $settings['items'][$form_state['sibling']]['width_type'] == $item['width_type']) {
1572
      // steal half of the sibling's space.
1573
      $width = $settings['items'][$form_state['sibling']]['width'] / 2;
1574
      $settings['items'][$form_state['sibling']]['width'] = $width;
1575
      $item['width'] = $width;
1576
    }
1577
  }
1578

    
1579
  // Place the item.
1580
  $settings['items'][$key] = $item;
1581
  if ($location == 'left') {
1582
    array_unshift($parent['children'], $key);
1583
  }
1584
  else {
1585
    $parent['children'][] = $key;
1586
  }
1587
}
1588

    
1589
/**
1590
 * AJAX responder to remove an existing row, column or region from a flexible
1591
 * layout.
1592
 */
1593
function panels_ajax_flexible_edit_remove($handler, $id) {
1594
  $settings = &$handler->display->layout_settings;
1595
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
1596

    
1597
  if (empty($settings['items'][$id])) {
1598
    ajax_render_error(t('Invalid item id.'));
1599
  }
1600

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

    
1606

    
1607
  $siblings = &$settings['items'][$item['parent']]['children'];
1608
  $parent_class = '.'  . $renderer->base[$settings['items'][$item['parent']]['type']] . '-' . $item['parent'];
1609

    
1610
  // Find the offset of our array. This will also be the key because
1611
  // this is a simple array.
1612
  $offset = array_search($id, $siblings);
1613

    
1614
  // Only bother with this stuff if our item is fluid, since fixed is
1615
  // as fixed does.
1616
  if ($item['type'] != 'row') {
1617
    if (isset($siblings[$offset + 1])) {
1618
      $next = $siblings[$offset + 1];
1619
    }
1620
    if (isset($siblings[$offset - 1])) {
1621
      $prev = $siblings[$offset - 1];
1622
    }
1623

    
1624
    if ($item['width_type'] == '%') {
1625
      // First, try next.
1626
      if (isset($next) && $settings['items'][$next]['width_type'] == '%') {
1627
        $settings['items'][$next]['width'] += $item['width'];
1628
      }
1629
      // If that failed, try the previous one.
1630
      else if (isset($prev) && $settings['items'][$prev]['width_type'] == '%') {
1631
        $settings['items'][$prev]['width'] += $item['width'];
1632
      }
1633
    }
1634
    // Not sure what happens if they both failed. Maybe nothing.
1635
  }
1636

    
1637
  // Remove the item.
1638
  array_splice($siblings, $offset, 1);
1639

    
1640
  unset($settings['items'][$id]);
1641

    
1642
  // Save our new state.
1643
  panels_edit_cache_set($handler->cache);
1644
  $class = $renderer->base[$item['type']] . '-' . $id;
1645
  $output = array();
1646

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

    
1649
  // Regenerate the CSS for siblings.
1650
  if (!empty($siblings)) {
1651
    // Get all the CSS necessary for the entire row (as width adjustments may
1652
    // have cascaded).
1653
    $css = array();
1654
    panels_flexible_get_css_group($css, $renderer, $siblings, $parent_class, $item['type'], $item['parent']);
1655
    foreach ($css as $selector => $data) {
1656
      $output[] = ajax_command_css($selector, $data);
1657
    }
1658
  }
1659

    
1660
  // There are potentially two splitters linked to this item to be removed.
1661
  if (!empty($prev)) {
1662
    $output[] = ajax_command_remove('.flexible-splitter-for-' . $renderer->base[$item['type']] . '-' . $prev);
1663
  }
1664

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

    
1668
  if (!empty($prev) && !empty($next)) {
1669
    // Add a new splitter that links $prev and $next:
1670
    $splitter = panels_flexible_render_splitter($renderer, $prev, $next);
1671
    $prev_class = '#panels-dnd-main .' . $renderer->base[$item['type']] . '-' . $prev;
1672
    $output[] = ajax_command_after($prev_class, $splitter);
1673
    // Send our fix height command.
1674
    $output[] = array('command' => 'flexible_fix_height');
1675
  }
1676
  // Rerender our parent item links:
1677
  $output[] = ajax_command_replace('.flexible-links-' . $item['parent'],
1678
    panels_flexible_render_item_links($renderer, $item['parent'], $settings['items'][$item['parent']]));
1679

    
1680
  $output[] = array(
1681
    'command' => 'flexible_fix_firstlast',
1682
    'selector' => $parent_class . '-inside',
1683
    'base' => 'panels-flexible-' . $item['type'],
1684
  );
1685

    
1686
  $handler->commands = $output;
1687
}
1688

    
1689
/**
1690
 * AJAX responder to store resize information when the user adjusts the
1691
 * splitter.
1692
 */
1693
function panels_ajax_flexible_edit_resize($handler) {
1694
  ctools_include('ajax');
1695
  $settings = &$handler->display->layout_settings;
1696
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
1697

    
1698
  $settings['items'][$_POST['left']]['width'] = $_POST['left_width'];
1699
  if (!empty($_POST['right']) && $_POST['right'] != $_POST['left']) {
1700
    $settings['items'][$_POST['right']]['width'] = $_POST['right_width'];
1701
  }
1702

    
1703
  // Save our new state.
1704
  panels_edit_cache_set($handler->cache);
1705

    
1706
  $handler->commands = array('ok');
1707
}
1708

    
1709
/**
1710
 * AJAX form to bring up the "reuse" modal.
1711
 */
1712
function panels_ajax_flexible_edit_reuse($handler) {
1713
  $settings = &$handler->display->layout_settings;
1714
  panels_flexible_convert_settings($settings, $handler->plugins['layout']);
1715

    
1716
  $form_state = array(
1717
    'display' => &$handler->display,
1718
    'settings' => &$settings,
1719
    'ajax' => TRUE,
1720
    'title' => t('Save this layout for reuse'),
1721
  );
1722

    
1723
  $output = ctools_modal_form_wrapper('panels_flexible_reuse_form', $form_state);
1724
  if (!empty($form_state['executed'])) {
1725
    // Create the new layout.
1726
    ctools_include('export');
1727
    $layout = ctools_export_crud_new('panels_layout');
1728
    $layout->plugin = 'flexible';
1729
    $layout->name = $form_state['values']['name'];
1730
    $layout->admin_title = $form_state['values']['admin_title'];
1731
    $layout->admin_description = $form_state['values']['admin_description'];
1732
    $layout->category = $form_state['values']['category'];
1733
    $layout->settings = $handler->display->layout_settings;
1734

    
1735
    // Save it.
1736
    ctools_export_crud_save('panels_layout', $layout);
1737

    
1738
    if (empty($form_state['values']['keep'])) {
1739
      // Set the actual layout_settings to now use the newly minted layout:
1740
      $handler->display->layout = 'flexible:' . $layout->name;
1741
      $handler->display->layout_settings = array();
1742

    
1743
      // Save our new state.
1744
      panels_edit_cache_set($handler->cache);
1745
    }
1746

    
1747
    // Dismiss the modal.
1748
    $output[] = ctools_modal_command_dismiss();
1749
  }
1750

    
1751
  $handler->commands = $output;
1752
}
1753

    
1754
function panels_flexible_reuse_form($form, &$form_state) {
1755
  $form['markup'] = array(
1756
    '#prefix' => '<div class="description">',
1757
    '#suffix' => '</div>',
1758
    '#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.'),
1759
  );
1760

    
1761
  $form['admin_title'] = array(
1762
    '#type' => 'textfield',
1763
    '#title' => t('Administrative title'),
1764
    '#description' => t('This will appear in the administrative interface to easily identify it.'),
1765
  );
1766

    
1767
  $form['name'] = array(
1768
    '#type' => 'machine_name',
1769
    '#title' => t('Machine name'),
1770
    '#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!'),
1771
    '#machine_name' => array(
1772
      'exists' => 'panels_flexible_edit_name_exists',
1773
      'source' => array('admin_title'),
1774
    ),
1775
  );
1776

    
1777
  $form['category'] = array(
1778
    '#type' => 'textfield',
1779
    '#title' => t('Category'),
1780
    '#description' => t('What category this layout should appear in. If left blank the category will be "Miscellaneous".'),
1781
  );
1782

    
1783
  $form['admin_description'] = array(
1784
    '#type' => 'textarea',
1785
    '#title' => t('Administrative description'),
1786
    '#description' => t('A description of what this layout is, does or is for, for administrative use.'),
1787
  );
1788

    
1789
  $form['keep'] = array(
1790
    '#type' => 'checkbox',
1791
    '#title' => t('Keep current panel layout flexible'),
1792
    '#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.'),
1793
  );
1794

    
1795
  $form['submit'] = array(
1796
    '#type' => 'submit',
1797
    '#value' => t('Save'),
1798
  );
1799

    
1800
  return $form;
1801
}
1802

    
1803
function panels_flexible_reuse_form_validate(&$form, &$form_state) {
1804
  if (empty($form_state['values']['name'])) {
1805
    form_error($form['name'], t('You must choose a machine name.'));
1806
  }
1807

    
1808
  ctools_include('export');
1809
  $test = ctools_export_crud_load('panels_layout', $form_state['values']['name']);
1810
  if ($test) {
1811
    form_error($form['name'], t('That name is used by another layout: @layout', array('@layout' => $test->admin_title)));
1812
  }
1813

    
1814
  // Ensure name fits the rules:
1815
  if (preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['name'])) {
1816
    form_error($form['name'], t('Name must be alphanumeric or underscores only.'));
1817
  }
1818
}
1819

    
1820
/**
1821
 * Test for #machine_name type to see if an export exists.
1822
 */
1823
function panels_flexible_edit_name_exists($name, $element, &$form_state) {
1824
  ctools_include('export');
1825
  $plugin = $form_state['plugin'];
1826

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