flexible.inc 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841
  1. <?php
  2. /**
  3. * @file
  4. * Flexible layout plugin.
  5. */
  6. /**
  7. * Implementation of hook_panels_layouts().
  8. */
  9. // Plugin definition.
  10. $plugin = array(
  11. 'title' => t('Flexible'),
  12. 'category' => t('Builders'),
  13. 'icon' => 'flexible.png',
  14. 'theme' => 'panels_flexible',
  15. 'admin theme' => 'panels_flexible_admin',
  16. 'css' => 'flexible.css',
  17. 'admin css' => 'flexible-admin.css',
  18. 'settings form' => 'panels_flexible_settings_form',
  19. 'settings submit' => 'panels_flexible_settings_submit',
  20. 'settings validate' => 'panels_flexible_settings_validate',
  21. 'regions function' => 'panels_flexible_panels',
  22. 'hook menu' => 'panels_flexible_menu',
  23. // Reusable layout Builder specific directives.
  24. 'builder' => TRUE,
  25. 'builder tab title' => 'Add flexible layout',
  26. 'get child' => 'panels_flexible_get_sublayout',
  27. 'get children' => 'panels_flexible_get_sublayouts',
  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. * Merge the main flexible plugin with a layout to create a sub plugin.
  39. *
  40. * This is used for both panels_flexible_get_sublayout and
  41. * panels_flexible_get_sublayouts.
  42. */
  43. function panels_flexible_merge_plugin($plugin, $layout) {
  44. $plugin['name'] = 'flexible:' . $layout->name;
  45. $plugin['category'] = !empty($layout->category) ? check_plain($layout->category) : t('Miscellaneous');
  46. $plugin['title'] = check_plain($layout->admin_title);
  47. $plugin['description'] = check_plain($layout->admin_description);
  48. $plugin['layout'] = $layout;
  49. $plugin['builder'] = FALSE;
  50. $plugin['builder tab title'] = NULL;
  51. return $plugin;
  52. }
  53. /**
  54. * Callback to provide a single stored flexible layout.
  55. */
  56. function panels_flexible_get_sublayout($plugin, $layout_name, $sublayout_name) {
  57. // Do not worry about caching; Panels is handling that for us.
  58. ctools_include('export');
  59. $item = ctools_export_crud_load('panels_layout', $sublayout_name);
  60. if ($item) {
  61. return panels_flexible_merge_plugin($plugin, $item);
  62. }
  63. }
  64. /**
  65. * Callback to provide all stored flexible layouts.
  66. */
  67. function panels_flexible_get_sublayouts($plugin, $layout_name) {
  68. $layouts[$layout_name] = $plugin;
  69. ctools_include('export');
  70. $items = ctools_export_load_object('panels_layout', 'conditions', array('plugin' => 'flexible'));
  71. foreach ($items as $name => $item) {
  72. $layouts['flexible:' . $name] = panels_flexible_merge_plugin($plugin, $item);
  73. }
  74. return $layouts;
  75. }
  76. /**
  77. * Flexible panel settings converter.
  78. *
  79. * Convert settings from old style to new, or provide defaults for
  80. * empty settings.
  81. *
  82. * @param array $settings
  83. * Drupal settings for the layout.
  84. *
  85. * @return null
  86. * Nothing to return.
  87. */
  88. function panels_flexible_convert_settings(&$settings, &$layout) {
  89. // This indicates that this is a layout that they used the checkbox
  90. // on. The layout is still 'flexible' but it's actually pointing
  91. // to another stored one and we have to load it.
  92. if (!empty($settings['layout'])) {
  93. $layout = panels_get_layout('flexible:' . $settings['layout']);
  94. }
  95. if (!empty($layout['layout'])) {
  96. $settings = $layout['layout']->settings;
  97. if ($settings) {
  98. return $settings;
  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. elseif (!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. $settings['items']['canvas']['children'][] = 'main';
  171. if (!empty($old['sidebars']['right'])) {
  172. $settings['items']['canvas']['children'][] = 'sidebar-right';
  173. $settings['items']['sidebar-right'] = array(
  174. 'type' => 'column',
  175. 'width' => $old['sidebars']['right_width'],
  176. 'width_type' => $old['sidebars']['width_type'],
  177. 'children' => array('sidebar-right-row'),
  178. 'parent' => 'canvas',
  179. );
  180. $settings['items']['sidebar-right-row'] = array(
  181. 'type' => 'row',
  182. 'contains' => 'region',
  183. 'children' => array('sidebar_right'),
  184. 'parent' => 'sidebar-right',
  185. );
  186. $settings['items']['sidebar_right'] = array(
  187. 'type' => 'region',
  188. 'title' => t('Right sidebar'),
  189. 'width' => 100,
  190. 'width_type' => '%',
  191. 'parent' => 'sidebar-right-row',
  192. );
  193. }
  194. // Add the main column.
  195. $settings['items']['main'] = array(
  196. 'type' => 'column',
  197. 'width' => 100,
  198. 'width_type' => '%',
  199. 'children' => array(),
  200. 'parent' => 'canvas',
  201. );
  202. // Add rows and regions.
  203. for ($row = 1; $row <= intval($old['rows']); $row++) {
  204. // Create entry for the row:
  205. $settings['items']["row_$row"] = array(
  206. 'type' => 'row',
  207. 'contains' => 'region',
  208. 'children' => array(),
  209. 'parent' => 'main',
  210. );
  211. // Add the row to the parent's children:
  212. $settings['items']['main']['children'][] = "row_$row";
  213. for ($col = 1; $col <= intval($old["row_$row"]['columns']); $col++) {
  214. // Create entry for the region:
  215. $settings['items']["row_${row}_$col"] = array(
  216. 'type' => 'region',
  217. 'width' => $old["row_$row"]["width_$col"],
  218. 'width_type' => '%',
  219. 'parent' => "row_$row",
  220. );
  221. // Add entry for the region to the row's children:
  222. $settings['items']["row_$row"]['children'][] = "row_${row}_$col";
  223. // Apply the proper title to the region:
  224. if (!empty($old["row_$row"]['names'][$col - 1])) {
  225. $settings['items']["row_${row}_$col"]['title'] = $old["row_$row"]['names'][$col - 1];
  226. }
  227. else {
  228. $settings['items']["row_${row}_$col"]['title'] = t("Row @row, Column @col", array('@row' => $row, '@col' => $col));
  229. }
  230. }
  231. }
  232. }
  233. elseif (isset($settings['canvas'])) {
  234. // Convert the old 'canvas' to the new canvas row.
  235. $settings['items']['canvas'] = array(
  236. 'type' => 'row',
  237. 'contains' => 'column',
  238. 'children' => $settings['canvas'],
  239. 'parent' => NULL,
  240. );
  241. unset($settings['canvas']);
  242. }
  243. }
  244. /**
  245. * Define the actual list of columns and rows for this flexible panel.
  246. */
  247. function panels_flexible_panels($display, $settings, $layout) {
  248. $items = array();
  249. panels_flexible_convert_settings($settings, $layout);
  250. foreach ($settings['items'] as $id => $item) {
  251. // Remove garbage values.
  252. if (!isset($item['type'])) {
  253. unset($items[$id]);
  254. }
  255. elseif ($item['type'] == 'region') {
  256. $items[$id] = $item['title'];
  257. }
  258. }
  259. return $items;
  260. }
  261. /**
  262. * Create a renderer object.
  263. *
  264. * The renderer object contains data that is passed around from function
  265. * to function allowing us to render our CSS and HTML easily.
  266. *
  267. * @todo Convert the functions to methods and make this properly OO.
  268. */
  269. function panels_flexible_create_renderer($admin, $css_id, $content, $settings, &$display, $layout, $handler) {
  270. $renderer = new stdClass();
  271. $renderer->settings = $settings;
  272. $renderer->content = $content;
  273. $renderer->css_id = $css_id;
  274. $renderer->did = &$display->did;
  275. if ($admin) {
  276. // Always scale in admin mode.
  277. $renderer->scale_base = 99.0;
  278. }
  279. else {
  280. $renderer->scale_base = !empty($settings['items']['canvas']['no_scale']) ? 100.0 : 99.0;
  281. }
  282. $renderer->id_str = $css_id ? 'id="' . $css_id . '"' : '';
  283. $renderer->admin = $admin;
  284. $renderer->handler = $handler;
  285. // Set up basic classes for all of our components.
  286. $renderer->name = !empty($layout['layout']) ? $layout['layout']->name : $display->did;
  287. $renderer->base_class = $renderer->name;
  288. $renderer->item_class['column'] = 'panels-flexible-column';
  289. $renderer->item_class['row'] = 'panels-flexible-row';
  290. $renderer->item_class['region'] = 'panels-flexible-region';
  291. $renderer->base['canvas'] = 'panels-flexible-' . $renderer->base_class;
  292. // Override these if selected from the UI and not in admin mode.
  293. if (!$admin) {
  294. if (!empty($settings['items']['canvas']['class'])) {
  295. $renderer->base_class = $settings['items']['canvas']['class'];
  296. $renderer->base['canvas'] = $renderer->base_class;
  297. }
  298. if (!empty($settings['items']['canvas']['column_class'])) {
  299. $renderer->item_class['column'] = $settings['items']['canvas']['column_class'];
  300. }
  301. if (!empty($settings['items']['canvas']['row_class'])) {
  302. $renderer->item_class['row'] = $settings['items']['canvas']['row_class'];
  303. }
  304. if (!empty($settings['items']['canvas']['region_class'])) {
  305. $renderer->item_class['region'] = $settings['items']['canvas']['region_class'];
  306. }
  307. }
  308. // Get the separation values out of the canvas settings.
  309. $renderer->column_separation = !empty($settings['items']['canvas']['column_separation']) ? $settings['items']['canvas']['column_separation'] : '0.5em';
  310. $renderer->region_separation = !empty($settings['items']['canvas']['region_separation']) ? $settings['items']['canvas']['region_separation'] : '0.5em';
  311. $renderer->row_separation = !empty($settings['items']['canvas']['row_separation']) ? $settings['items']['canvas']['row_separation'] : '0.5em';
  312. // Make some appended classes so it's easier to reference them.
  313. $renderer->base['column'] = $renderer->item_class['column'] . '-' . $renderer->base_class;
  314. $renderer->base['row'] = $renderer->item_class['row'] . '-' . $renderer->base_class;
  315. $renderer->base['region'] = $renderer->item_class['region'] . '-' . $renderer->base_class;
  316. if ($renderer->name != 'new') {
  317. // Use v2 to guarantee all CSS gets regenerated to account for changes in
  318. // how some divs will be rendered.
  319. $renderer->css_cache_name = 'flexiblev2:' . $renderer->name;
  320. if ($admin) {
  321. ctools_include('css');
  322. ctools_css_clear($renderer->css_cache_name);
  323. }
  324. }
  325. return $renderer;
  326. }
  327. /**
  328. * Draw the flexible layout.
  329. */
  330. function theme_panels_flexible($vars) {
  331. $css_id = $vars['css_id'];
  332. $content = $vars['content'];
  333. $settings = $vars['settings'];
  334. $display = $vars['display'];
  335. $layout = $vars['layout'];
  336. $handler = $vars['renderer'];
  337. panels_flexible_convert_settings($settings, $layout);
  338. $renderer = panels_flexible_create_renderer(FALSE, $css_id, $content, $settings, $display, $layout, $handler);
  339. // CSS must be generated because it reports back left/middle/right
  340. // positions.
  341. $css = panels_flexible_render_css($renderer);
  342. if (!empty($renderer->css_cache_name) && empty($display->editing_layout)) {
  343. ctools_include('css');
  344. // Generate an id based upon rows + columns:
  345. $filename = ctools_css_retrieve($renderer->css_cache_name);
  346. if (!$filename) {
  347. $filename = ctools_css_store($renderer->css_cache_name, $css, FALSE);
  348. }
  349. // Give the CSS to the renderer to put where it wants.
  350. if ($handler) {
  351. $handler->add_css($filename, 'module', 'all', FALSE);
  352. }
  353. else {
  354. drupal_add_css($filename);
  355. }
  356. }
  357. else {
  358. // If the id is 'new' we can't reliably cache the CSS in the filesystem
  359. // because the display does not truly exist, so we'll stick it in the
  360. // head tag. We also do this if we've been told we're in the layout
  361. // editor so that it always gets fresh CSS.
  362. drupal_add_css($css, array('type' => 'inline', 'preprocess' => FALSE));
  363. }
  364. // Also store the CSS on the display in case the live preview or something
  365. // needs it.
  366. $display->add_css = $css;
  367. $output = "<div class=\"panel-flexible " . $renderer->base['canvas'] . " clearfix\" $renderer->id_str>\n";
  368. $output .= "<div class=\"panel-flexible-inside " . $renderer->base['canvas'] . "-inside\">\n";
  369. $output .= panels_flexible_render_items($renderer, $settings['items']['canvas']['children'], $renderer->base['canvas']);
  370. // Wrap the whole thing up nice and snug.
  371. $output .= "</div>\n</div>\n";
  372. return $output;
  373. }
  374. /**
  375. * Draw the flexible layout.
  376. */
  377. function theme_panels_flexible_admin($vars) {
  378. $css_id = $vars['css_id'];
  379. $content = $vars['content'];
  380. $settings = $vars['settings'];
  381. $display = $vars['display'];
  382. $layout = $vars['layout'];
  383. $handler = $vars['renderer'];
  384. // We never draw stored flexible layouts in admin mode; they must be edited
  385. // from the stored layout UI at that point.
  386. if (!empty($layout['layout'])) {
  387. return theme_panels_flexible(array('css_id' => $css_id, 'content' => $content, 'settings' => $settings, 'display' => $display, 'layout' => $layout, 'renderer' => $handler));
  388. }
  389. panels_flexible_convert_settings($settings, $layout);
  390. $renderer = panels_flexible_create_renderer(TRUE, $css_id, $content, $settings, $display, $layout, $handler);
  391. $css = panels_flexible_render_css($renderer);
  392. // For the administrative view, add CSS directly to head.
  393. drupal_add_css($css, array('type' => 'inline', 'preprocess' => FALSE));
  394. if (empty($display->editing_layout)) {
  395. $output = '<input type="submit" id="panels-flexible-toggle-layout" class="form-submit" value ="' .
  396. t('Show layout designer') . '">';
  397. if (user_access('administer panels layouts')) {
  398. $output .= '<input type="hidden" class="panels-flexible-reuse-layout-url" value="' . url($handler->get_url('layout', 'reuse'), array('absolute' => TRUE)) . '">';
  399. $output .= '<input type="submit" id="panels-flexible-reuse-layout" class="form-submit ctools-use-modal" value ="' .
  400. t('Reuse layout') . '">';
  401. }
  402. $output .= "<div class=\"panel-flexible " . $renderer->base['canvas'] . " clearfix panel-flexible-admin panel-flexible-no-edit-layout\" $renderer->id_str>\n";
  403. }
  404. else {
  405. $output = "<div class=\"panel-flexible " . $renderer->base['canvas'] . " clearfix panel-flexible-admin panel-flexible-edit-layout\" $renderer->id_str>\n";
  406. }
  407. $output .= "<div class=\"panel-flexible-inside " . $renderer->base['canvas'] . "-inside \">\n";
  408. $content = panels_flexible_render_items($renderer, $settings['items']['canvas']['children'], $renderer->base['row'] . '-canvas');
  409. $output .= panels_flexible_render_item($renderer, $settings['items']['canvas'], $content, 'canvas', 0, 0, TRUE);
  410. // Wrap the whole thing up nice and snug.
  411. $output .= "</div>\n</div>\n";
  412. drupal_add_js($layout['path'] . '/flexible-admin.js');
  413. drupal_add_js(array('flexible' => array('resize' => url($handler->get_url('layout', 'resize'), array('absolute' => TRUE)))), 'setting');
  414. return $output;
  415. }
  416. /**
  417. * Render a piece of a flexible layout.
  418. */
  419. function panels_flexible_render_items($renderer, $list, $owner_id) {
  420. $output = '';
  421. $groups = array('left' => '', 'middle' => '', 'right' => '');
  422. $max = count($list) - 1;
  423. $prev = NULL;
  424. foreach ($list as $position => $id) {
  425. $item = $renderer->settings['items'][$id];
  426. $location = isset($renderer->positions[$id]) ? $renderer->positions[$id] : 'middle';
  427. if ($renderer->admin && $item['type'] != 'row' && $prev) {
  428. $groups[$location] .= panels_flexible_render_splitter($renderer, $prev, $id);
  429. }
  430. switch ($item['type']) {
  431. case 'column':
  432. $content = panels_flexible_render_items($renderer, $item['children'], $renderer->base['column'] . '-' . $id);
  433. if (empty($renderer->settings['items'][$id]['hide_empty']) || trim($content)) {
  434. $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max);
  435. }
  436. break;
  437. case 'row':
  438. $content = panels_flexible_render_items($renderer, $item['children'], $renderer->base['row'] . '-' . $id);
  439. if (empty($renderer->settings['items'][$id]['hide_empty']) || trim($content)) {
  440. $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max, TRUE);
  441. }
  442. break;
  443. case 'region':
  444. if (empty($renderer->settings['items'][$id]['hide_empty'])) {
  445. $content = isset($renderer->content[$id]) ? $renderer->content[$id] : "&nbsp;";
  446. }
  447. else {
  448. $content = isset($renderer->content[$id]) ? trim($renderer->content[$id]) : "";
  449. }
  450. if (empty($renderer->settings['items'][$id]['hide_empty']) || $content) {
  451. $groups[$location] .= panels_flexible_render_item($renderer, $item, $content, $id, $position, $max);
  452. }
  453. break;
  454. }
  455. // If all items are fixed then we have a special splitter on the right to
  456. // control the overall width.
  457. if (!empty($renderer->admin) && $max == $position && $location == 'left') {
  458. $groups[$location] .= panels_flexible_render_splitter($renderer, $id, NULL);
  459. }
  460. $prev = $id;
  461. }
  462. $group_count = count(array_filter($groups));
  463. // Render each group. We only render the group div if we're in admin mode
  464. // or if there are multiple groups.
  465. foreach ($groups as $position => $content) {
  466. if (!empty($content) || $renderer->admin) {
  467. if ($group_count > 1 || $renderer->admin) {
  468. $output .= '<div class="' . $owner_id . '-' . $position . '">' . $content . '</div>';
  469. }
  470. else {
  471. $output .= $content;
  472. }
  473. }
  474. }
  475. return $output;
  476. }
  477. /**
  478. * Render a column in the flexible layout.
  479. */
  480. function panels_flexible_render_item($renderer, $item, $content, $id, $position, $max, $clear = FALSE) {
  481. // If we are rendering a row and there is just one row, we don't need to
  482. // render the row unless there is fixed_width content inside it.
  483. if (empty($renderer->admin) && $item['type'] == 'row' && $max == 0) {
  484. $fixed = FALSE;
  485. foreach ($item['children'] as $id) {
  486. if ($renderer->settings['items'][$id]['width_type'] != '%') {
  487. $fixed = TRUE;
  488. break;
  489. }
  490. }
  491. if (!$fixed) {
  492. return $content;
  493. }
  494. }
  495. // If we are rendering a column and there is just one column, we don't
  496. // need to render the column unless it has a fixed_width.
  497. if (empty($renderer->admin) && $item['type'] == 'column' && $max == 0 && $item['width_type'] == '%') {
  498. return $content;
  499. }
  500. $base = $renderer->item_class[$item['type']];
  501. $output = '<div class="' . $base . ' ' . $renderer->base[$item['type']] . '-' . $id;
  502. if ($position == 0) {
  503. $output .= ' ' . $base . '-first';
  504. }
  505. if ($position == $max) {
  506. $output .= ' ' . $base . '-last';
  507. }
  508. if ($clear) {
  509. $output .= ' clearfix';
  510. }
  511. if (isset($item['class'])) {
  512. $output .= ' ' . check_plain($item['class']);
  513. }
  514. $output .= '">' . "\n";
  515. if (!empty($renderer->admin)) {
  516. $output .= panels_flexible_render_item_links($renderer, $id, $item);
  517. }
  518. $output .= ' <div class="inside ' . $base . '-inside ' . $base . '-' . $renderer->base_class . '-' . $id . '-inside';
  519. if ($position == 0) {
  520. $output .= ' ' . $base . '-inside-first';
  521. }
  522. if ($position == $max) {
  523. $output .= ' ' . $base . '-inside-last';
  524. }
  525. if ($clear) {
  526. $output .= ' clearfix';
  527. }
  528. $output .= "\">\n";
  529. $output .= $content;
  530. $output .= ' </div>' . "\n";
  531. $output .= '</div>' . "\n";
  532. return $output;
  533. }
  534. /**
  535. * Render a splitter div to place between the $left and $right items.
  536. *
  537. * If the right ID is NULL that means there isn't actually a box to the
  538. * right, but we need a splitter anyway. We'll mostly use info about the
  539. * left, but pretend it's 'fluid' so that the javascript won't actually
  540. * modify the right item.
  541. */
  542. function panels_flexible_render_splitter($renderer, $left_id, $right_id) {
  543. $left = $renderer->settings['items'][$left_id];
  544. $left_class = $renderer->base[$left['type']] . '-' . $left_id;
  545. if ($right_id) {
  546. $right = $renderer->settings['items'][$right_id];
  547. $right_class = $renderer->base[$left['type']] . '-' . $right_id;
  548. }
  549. else {
  550. $right = $left;
  551. $right_class = $left_class;
  552. }
  553. $output = '<div tabindex="0"
  554. class="panels-flexible-splitter flexible-splitter-for-' . $left_class . '">';
  555. // Name the left object:
  556. $output .= '<span class="panels-flexible-splitter-left">';
  557. $output .= '.' . $left_class;
  558. $output .= '</span>';
  559. $output .= '<span class="panels-flexible-splitter-left-id">';
  560. $output .= $left_id;
  561. $output .= '</span>';
  562. $output .= '<span class="panels-flexible-splitter-left-width ' . $left_class . '-width">';
  563. $output .= $left['width'];
  564. $output .= '</span>';
  565. $output .= '<span class="panels-flexible-splitter-left-scale">';
  566. $output .= isset($renderer->scale[$left_id]) ? $renderer->scale[$left_id] : 1;
  567. $output .= '</span>';
  568. $output .= '<span class="panels-flexible-splitter-left-width-type">';
  569. $output .= $left['width_type'];
  570. $output .= '</span>';
  571. // Name the right object:
  572. $output .= '<span class="panels-flexible-splitter-right">';
  573. $output .= '.' . $right_class;
  574. $output .= '</span>';
  575. $output .= '<span class="panels-flexible-splitter-right-id">';
  576. $output .= $right_id;
  577. $output .= '</span>';
  578. $output .= '<span class="panels-flexible-splitter-right-width ' . $right_class . '-width">';
  579. $output .= $right['width'];
  580. $output .= '</span>';
  581. $output .= '<span class="panels-flexible-splitter-right-scale">';
  582. $output .= isset($renderer->scale[$right_id]) ? $renderer->scale[$right_id] : 1;
  583. $output .= '</span>';
  584. $output .= '<span class="panels-flexible-splitter-right-width-type">';
  585. // If there is no right, make it fluid.
  586. $output .= $right_id ? $right['width_type'] : '%';
  587. $output .= '</span>';
  588. $output .= '</div>';
  589. return $output;
  590. }
  591. /**
  592. * Render the dropdown links for an item.
  593. */
  594. function panels_flexible_render_item_links($renderer, $id, $item) {
  595. $links = array();
  596. $remove = '';
  597. $add = '';
  598. if ($item['type'] == 'column') {
  599. $title = t('Column');
  600. $settings = t('Column settings');
  601. if (empty($item['children'])) {
  602. $remove = t('Remove column');
  603. $add = t('Add row');
  604. }
  605. else {
  606. $add = t('Add row to top');
  607. $add2 = t('Add row to bottom');
  608. }
  609. }
  610. elseif ($item['type'] == 'row') {
  611. if ($id == 'canvas') {
  612. $title = t('Canvas');
  613. $settings = t('Canvas settings');
  614. }
  615. else {
  616. $title = t('Row');
  617. $settings = t('Row settings');
  618. }
  619. if (empty($item['children'])) {
  620. if ($id != 'canvas') {
  621. $remove = t('Remove row');
  622. }
  623. $add = $item['contains'] == 'region' ? t('Add region') : t('Add column');
  624. }
  625. else {
  626. $add = $item['contains'] == 'region' ? t('Add region to left') : t('Add column to left');
  627. $add2 = $item['contains'] == 'region' ? t('Add region to right') : t('Add column to right');
  628. }
  629. }
  630. elseif ($item['type'] == 'region') {
  631. $title = t('Region');
  632. $settings = t('Region settings');
  633. $remove = t('Remove region');
  634. }
  635. if (!empty($settings)) {
  636. $links[] = array(
  637. 'title' => $settings,
  638. 'href' => $renderer->handler->get_url('layout', 'settings', $id),
  639. 'attributes' => array('class' => array('ctools-use-modal')),
  640. );
  641. }
  642. if ($add) {
  643. $links[] = array(
  644. 'title' => $add,
  645. 'href' => $renderer->handler->get_url('layout', 'add', $id),
  646. 'attributes' => array('class' => array('ctools-use-modal')),
  647. );
  648. }
  649. if (isset($add2)) {
  650. $links[] = array(
  651. 'title' => $add2,
  652. 'href' => $renderer->handler->get_url('layout', 'add', $id, 'right'),
  653. 'attributes' => array('class' => array('ctools-use-modal')),
  654. );
  655. }
  656. if ($remove) {
  657. $links[] = array(
  658. 'title' => $remove,
  659. 'href' => $renderer->handler->get_url('layout', 'remove', $id),
  660. 'attributes' => array('class' => array('use-ajax')),
  661. );
  662. }
  663. return theme('ctools_dropdown', array('title' => $title, 'links' => $links, 'class' => 'flexible-layout-only flexible-links flexible-title flexible-links-' . $id));
  664. }
  665. /**
  666. * Provide CSS for a flexible layout.
  667. */
  668. function panels_flexible_render_css($renderer) {
  669. if ($renderer->admin) {
  670. $parent_class = '.' . $renderer->base['row'] . '-canvas';
  671. }
  672. else {
  673. $parent_class = '.' . $renderer->base['canvas'];
  674. }
  675. return panels_flexible_render_css_group($renderer, $renderer->settings['items']['canvas']['children'], $parent_class, 'column', 'canvas');
  676. }
  677. /**
  678. * Render the CSS for a group of items to be displayed together.
  679. *
  680. * Columns and regions, when displayed as a group, need to cooperate in
  681. * order to share margins and make sure that percent widths add up
  682. * to the right total.
  683. */
  684. function panels_flexible_render_css_group($renderer, $list, $owner_id, $type, $id) {
  685. $css = array();
  686. // Start off with some generic CSS to properly pad regions.
  687. $css[$owner_id . ' .' . $renderer->item_class['region']] = array(
  688. 'padding' => '0',
  689. );
  690. $css[$owner_id . ' .' . $renderer->item_class['region'] . '-inside'] = array(
  691. 'padding-right' => $renderer->region_separation,
  692. 'padding-left' => $renderer->region_separation,
  693. );
  694. $css[$owner_id . ' .' . $renderer->item_class['region'] . '-inside-first'] = array(
  695. 'padding-left' => '0',
  696. );
  697. $css[$owner_id . ' .' . $renderer->item_class['region'] . '-inside-last'] = array(
  698. 'padding-right' => '0',
  699. );
  700. $css[$owner_id . ' .' . $renderer->item_class['column']] = array(
  701. 'padding' => '0',
  702. );
  703. $css[$owner_id . ' .' . $renderer->item_class['column'] . '-inside'] = array(
  704. 'padding-right' => $renderer->column_separation,
  705. 'padding-left' => $renderer->column_separation,
  706. );
  707. $css[$owner_id . ' .' . $renderer->item_class['column'] . '-inside-first'] = array(
  708. 'padding-left' => '0',
  709. );
  710. $css[$owner_id . ' .' . $renderer->item_class['column'] . '-inside-last'] = array(
  711. 'padding-right' => '0',
  712. );
  713. // And properly pad rows too:
  714. $css[$owner_id . ' .' . $renderer->item_class['row']] = array(
  715. 'padding' => '0 0 ' . $renderer->row_separation . ' 0',
  716. 'margin' => '0',
  717. );
  718. $css[$owner_id . ' .' . $renderer->item_class['row'] . '-last'] = array(
  719. 'padding-bottom' => '0',
  720. );
  721. panels_flexible_get_css_group($css, $renderer, $list, $owner_id, $type, $id);
  722. ctools_include('css');
  723. return ctools_css_assemble($css);
  724. }
  725. /**
  726. * Construct an array with all of the CSS properties for a group.
  727. *
  728. * This will parse down into children and produce all of the CSS needed if you
  729. * start from the top.
  730. */
  731. function panels_flexible_get_css_group(&$css, $renderer, $list, $owner_id, $type, $item_id) {
  732. if ($type != 'row') {
  733. // Go through our items and break up into right/center/right groups so we
  734. // can figure out our offsets.
  735. // right == any items on the right that are 'fixed'.
  736. // middle == all fluid items.
  737. // right == any items on the right that are 'fixed'.
  738. $left = $middle = $right = array();
  739. $left_total = $right_total = $middle_total = 0;
  740. $current = 'left';
  741. foreach ($list as $id) {
  742. if ($renderer->settings['items'][$id]['width_type'] == 'px') {
  743. // Fixed:
  744. if ($current == 'left') {
  745. $left[] = $id;
  746. $renderer->positions[$id] = 'left';
  747. $left_total += $renderer->settings['items'][$id]['width'];
  748. }
  749. else {
  750. $current = 'right';
  751. $right[] = $id;
  752. $renderer->positions[$id] = 'right';
  753. $right_total += $renderer->settings['items'][$id]['width'];
  754. }
  755. }
  756. else {
  757. // Fluid:
  758. if ($current != 'right') {
  759. $current = 'middle';
  760. $middle[] = $id;
  761. $renderer->positions[$id] = 'middle';
  762. $middle_total += $renderer->settings['items'][$id]['width'];
  763. }
  764. // Fall through: if current is 'right' and we ran into a 'fluid' then
  765. // it gets *dropped* because that is invalid.
  766. }
  767. }
  768. // Go through our right sides and create CSS.
  769. foreach ($left as $id) {
  770. $class = "." . $renderer->base[$type] . "-$id";
  771. $css[$class] = array(
  772. 'position' => 'relative',
  773. 'float' => 'left',
  774. 'background-color' => 'transparent',
  775. 'width' => $renderer->settings['items'][$id]['width'] . "px",
  776. );
  777. }
  778. // Do the same for right.
  779. $right_pixels = 0;
  780. foreach ($right as $id) {
  781. $class = "." . $renderer->base[$type] . "-$id";
  782. $css[$class] = array(
  783. 'float' => 'left',
  784. 'width' => $renderer->settings['items'][$id]['width'] . "px",
  785. );
  786. }
  787. $max = count($middle) - 1;
  788. if ($middle_total) {
  789. // Because we love IE so much, auto scale everything to 99%. This
  790. // means adding up the actual widths and then providing a multiplier
  791. // to each so that the total is 99%.
  792. $scale = $renderer->scale_base / $middle_total;
  793. foreach ($middle as $position => $id) {
  794. $class = "." . $renderer->base[$type] . "-$id";
  795. $css[$class] = array(
  796. 'float' => 'left',
  797. 'width' => number_format($renderer->settings['items'][$id]['width'] * $scale, 4, '.', '') . "%",
  798. );
  799. // Store this so we can use it later.
  800. // @todo: Store the scale, not the new width, so .js can adjust
  801. // bi-directionally.
  802. $renderer->scale[$id] = $scale;
  803. }
  804. }
  805. // If there is any total remaining, we need to offset the splitter
  806. // by this much too.
  807. if ($left_total) {
  808. // Add this even if it's 0 so we can handle removals.
  809. $css["$owner_id-inside"]['padding-left'] = '0px';
  810. if ($renderer->admin || count($middle)) {
  811. $css["$owner_id-middle"]['margin-left'] = $left_total . 'px';
  812. // IE hack!
  813. $css["* html $owner_id-left"]['left'] = $left_total . "px";
  814. // Make this one very specific to the admin CSS so that preview
  815. // does not stomp it.
  816. $css[".panel-flexible-admin $owner_id-inside"]['padding-left'] = '0px';
  817. }
  818. else {
  819. $css["$owner_id-inside"]['margin-left'] = '-' . $left_total . 'px';
  820. $css["$owner_id-inside"]['padding-left'] = $left_total . 'px';
  821. // IE hack!
  822. $css["* html $owner_id-inside"]['left'] = $left_total . "px";
  823. }
  824. }
  825. if ($right_total) {
  826. $css["$owner_id-middle"]['margin-right'] = $right_total . 'px';
  827. }
  828. $css["$owner_id-inside"]['padding-right'] = '0px';
  829. }
  830. // If the canvas has a fixed width set, and this is the canvas, fix the
  831. // width.
  832. if ($item_id == 'canvas') {
  833. $item = $renderer->settings['items'][$item_id];
  834. if (!empty($item['fixed_width']) && intval($item['fixed_width'])) {
  835. $css['.' . $renderer->base['canvas']]['width'] = intval($item['fixed_width']) . 'px';
  836. }
  837. else {
  838. $css['.' . $renderer->base['canvas']]['width'] = 'auto';
  839. }
  840. }
  841. // Go through each item and process children.
  842. foreach ($list as $id) {
  843. $item = $renderer->settings['items'][$id];
  844. if (empty($item['children'])) {
  845. continue;
  846. }
  847. if ($type == 'column') {
  848. // Columns can only contain rows.
  849. $child_type = 'row';
  850. }
  851. else {
  852. $child_type = isset($item['contains']) ? $item['contains'] : 'region';
  853. }
  854. $class = "." . $renderer->base[$type] . "-$id";
  855. panels_flexible_get_css_group($css, $renderer, $item['children'], $class, $child_type, $id);
  856. }
  857. }
  858. /**
  859. * AJAX responder to edit flexible settings for an item.
  860. *
  861. * @param object $handler
  862. * The display renderer handler object.
  863. * @param mixed $id
  864. * Id for the panel.
  865. */
  866. function panels_ajax_flexible_edit_settings($handler, $id) {
  867. $settings = &$handler->display->layout_settings;
  868. panels_flexible_convert_settings($settings, $handler->plugins['layout']);
  869. if (empty($settings['items'][$id])) {
  870. ctools_modal_render(t('Error'), t('Invalid item id.'));
  871. }
  872. $item = &$settings['items'][$id];
  873. $siblings = array();
  874. if ($id != 'canvas') {
  875. $siblings = $settings['items'][$item['parent']]['children'];
  876. }
  877. switch ($item['type']) {
  878. case 'column':
  879. $title = t('Configure column');
  880. break;
  881. case 'row':
  882. if ($id == 'canvas') {
  883. $title = t('Configure canvas');
  884. }
  885. else {
  886. $title = t('Configure row');
  887. }
  888. break;
  889. case 'region':
  890. $title = t('Configure region');
  891. break;
  892. }
  893. $form_state = array(
  894. 'display' => &$handler->display,
  895. 'item' => &$item,
  896. 'id' => $id,
  897. 'siblings' => $siblings,
  898. 'settings' => &$settings,
  899. 'ajax' => TRUE,
  900. 'title' => $title,
  901. 'op' => 'edit',
  902. );
  903. $output = ctools_modal_form_wrapper('panels_flexible_config_item_form', $form_state);
  904. if (!empty($form_state['executed'])) {
  905. // If the width type changed then other nearby items will have
  906. // to have their widths adjusted.
  907. panels_edit_cache_set($handler->cache);
  908. $css_id = isset($handler->display->css_id) ? $handler->display->css_id : '';
  909. $renderer = panels_flexible_create_renderer(TRUE, $css_id, array(), $settings, $handler->display, $handler->plugins['layout'], $handler);
  910. $output = array();
  911. // If the item is a region, replace the title.
  912. $class = $renderer->base[$item['type']] . '-' . $id;
  913. if ($item['type'] == 'region') {
  914. $output[] = ajax_command_replace(".$class h2.label",
  915. '<h2 class="label">' . check_plain($item['title']) . '</h2>');
  916. }
  917. // Rerender our links in case something changed.
  918. $output[] = ajax_command_replace('.flexible-links-' . $id,
  919. panels_flexible_render_item_links($renderer, $id, $item));
  920. // If editing the canvas, reset the CSS width.
  921. if ($id == 'canvas') {
  922. // Update canvas CSS.
  923. $css = array(
  924. '.' . $renderer->item_class['column'] . '-inside' => array(
  925. 'padding-left' => $renderer->column_separation,
  926. 'padding-right' => $renderer->column_separation,
  927. ),
  928. '.' . $renderer->item_class['region'] . '-inside' => array(
  929. 'padding-left' => $renderer->region_separation,
  930. 'padding-right' => $renderer->region_separation,
  931. ),
  932. '.' . $renderer->item_class['row'] => array(
  933. 'padding-bottom' => $renderer->row_separation,
  934. ),
  935. );
  936. if (!empty($item['fixed_width']) && intval($item['fixed_width'])) {
  937. $css['.' . $renderer->base['canvas']] = array('width' => intval($item['fixed_width']) . 'px');
  938. }
  939. else {
  940. $css['.' . $renderer->base['canvas']] = array('width' => 'auto');
  941. }
  942. foreach ($css as $selector => $data) {
  943. $output[] = ajax_command_css($selector, $data);
  944. }
  945. }
  946. $output[] = ctools_modal_command_dismiss();
  947. }
  948. $handler->commands = $output;
  949. }
  950. /**
  951. * Configure a row, column or region on the flexible page.
  952. */
  953. function panels_flexible_config_item_form($form, &$form_state) {
  954. $display = &$form_state['display'];
  955. $item = &$form_state['item'];
  956. $siblings = &$form_state['siblings'];
  957. $settings = &$form_state['settings'];
  958. $id = &$form_state['id'];
  959. if ($item['type'] == 'region') {
  960. $form['title'] = array(
  961. '#title' => t('Region title'),
  962. '#type' => 'textfield',
  963. '#default_value' => $item['title'],
  964. '#required' => TRUE,
  965. );
  966. }
  967. if ($id == 'canvas') {
  968. $form['class'] = array(
  969. '#title' => t('Canvas class'),
  970. '#type' => 'textfield',
  971. '#default_value' => isset($item['class']) ? $item['class'] : '',
  972. '#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.'),
  973. );
  974. $form['column_class'] = array(
  975. '#title' => t('Column class'),
  976. '#type' => 'textfield',
  977. '#default_value' => isset($item['column_class']) ? $item['column_class'] : '',
  978. '#description' => t('This class will be applied to all columns of the layout. If left blank this will be panels-flexible-column.'),
  979. );
  980. $form['row_class'] = array(
  981. '#title' => t('Row class'),
  982. '#type' => 'textfield',
  983. '#default_value' => isset($item['row_class']) ? $item['row_class'] : '',
  984. '#description' => t('This class will be applied to all rows of the layout. If left blank this will be panels-flexible-row.'),
  985. );
  986. $form['region_class'] = array(
  987. '#title' => t('Region class'),
  988. '#type' => 'textfield',
  989. '#default_value' => isset($item['region_class']) ? $item['region_class'] : '',
  990. '#description' => t('This class will be applied to all regions of the layout. If left blank this will be panels-flexible-region.'),
  991. );
  992. $form['no_scale'] = array(
  993. '#type' => 'checkbox',
  994. '#title' => t('Scale fluid widths for IE6'),
  995. '#description' => t('IE6 does not do well with 100% widths. If checked, width will be scaled to 99% to compensate.'),
  996. '#default_value' => empty($item['no_scale']),
  997. );
  998. $form['fixed_width'] = array(
  999. '#type' => 'textfield',
  1000. '#title' => t('Fixed width'),
  1001. '#description' => t('If a value is entered, the layout canvas will be fixed to the given pixel width.'),
  1002. '#default_value' => isset($item['fixed_width']) ? $item['fixed_width'] : '',
  1003. );
  1004. $form['column_separation'] = array(
  1005. '#type' => 'textfield',
  1006. '#title' => t('Column separation'),
  1007. '#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.'),
  1008. '#default_value' => isset($item['column_separation']) ? $item['column_separation'] : '0.5em',
  1009. );
  1010. $form['region_separation'] = array(
  1011. '#type' => 'textfield',
  1012. '#title' => t('Region separation'),
  1013. '#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.'),
  1014. '#default_value' => isset($item['region_separation']) ? $item['region_separation'] : '0.5em',
  1015. );
  1016. $form['row_separation'] = array(
  1017. '#type' => 'textfield',
  1018. '#title' => t('Row separation'),
  1019. '#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.'),
  1020. '#default_value' => isset($item['row_separation']) ? $item['row_separation'] : '0.5em',
  1021. );
  1022. }
  1023. else {
  1024. $form['class'] = array(
  1025. '#title' => t('CSS class'),
  1026. '#type' => 'textfield',
  1027. '#default_value' => isset($item['class']) ? $item['class'] : '',
  1028. '#description' => t('Enter a CSS class that will be used. This can be used to apply automatic styling from your theme, for example.'),
  1029. );
  1030. if ($item['type'] != 'row') {
  1031. // Test to see if there are fluid items to the left or the right. If there
  1032. // are fluid items on both sides, this item cannot be set to fixed.
  1033. $left = $right = FALSE;
  1034. $current = 'left';
  1035. foreach ($siblings as $sibling) {
  1036. if ($sibling == $id) {
  1037. $current = 'right';
  1038. }
  1039. elseif ($settings['items'][$sibling]['width_type'] == '%') {
  1040. // Indirection.
  1041. $$current = TRUE;
  1042. }
  1043. }
  1044. $form['width_type'] = array(
  1045. '#type' => 'select',
  1046. '#title' => t('Width'),
  1047. '#default_value' => $item['width_type'],
  1048. '#options' => array(
  1049. '%' => t('Fluid'),
  1050. 'px' => t('Fixed'),
  1051. ),
  1052. '#disabled' => TRUE,
  1053. );
  1054. }
  1055. else {
  1056. $form['contains'] = array(
  1057. '#type' => 'select',
  1058. '#title' => t('Contains'),
  1059. '#default_value' => $item['contains'],
  1060. '#options' => array(
  1061. 'region' => t('Regions'),
  1062. 'column' => t('Columns'),
  1063. ),
  1064. );
  1065. if (!empty($item['children'])) {
  1066. $form['contains']['#disabled'] = TRUE;
  1067. $form['contains']['#value'] = $item['contains'];
  1068. $form['contains']['#description'] = t('You must remove contained items to change the row container type.');
  1069. }
  1070. }
  1071. }
  1072. $form['hide_empty'] = array(
  1073. '#title' => t('Hide element if empty'),
  1074. '#type' => 'checkbox',
  1075. '#default_value' => !empty($item['hide_empty']) ? 1 : 0,
  1076. );
  1077. $form['save'] = array(
  1078. '#type' => 'submit',
  1079. '#value' => t('Save'),
  1080. );
  1081. return $form;
  1082. }
  1083. /**
  1084. * Submit handler for editing a flexible item.
  1085. */
  1086. function panels_flexible_config_item_form_submit(&$form, &$form_state) {
  1087. $item = &$form_state['item'];
  1088. if ($item['type'] == 'region') {
  1089. $item['title'] = $form_state['values']['title'];
  1090. }
  1091. $item['class'] = $form_state['values']['class'];
  1092. if ($form_state['id'] == 'canvas') {
  1093. $item['column_class'] = $form_state['values']['column_class'];
  1094. $item['row_class'] = $form_state['values']['row_class'];
  1095. $item['region_class'] = $form_state['values']['region_class'];
  1096. // Reverse this as the checkbox is backward from how we actually store
  1097. // it to make it simpler to default to scaling.
  1098. $item['no_scale'] = !$form_state['values']['no_scale'];
  1099. $item['fixed_width'] = $form_state['values']['fixed_width'];
  1100. $item['column_separation'] = $form_state['values']['column_separation'];
  1101. $item['region_separation'] = $form_state['values']['region_separation'];
  1102. $item['row_separation'] = $form_state['values']['row_separation'];
  1103. }
  1104. elseif ($item['type'] != 'row') {
  1105. $item['width_type'] = $form_state['values']['width_type'];
  1106. }
  1107. else {
  1108. $item['contains'] = $form_state['values']['contains'];
  1109. }
  1110. $item['hide_empty'] = $form_state['values']['hide_empty'];
  1111. }
  1112. /**
  1113. * AJAX responder to add a new row, column or region to a flexible layout.
  1114. */
  1115. function panels_ajax_flexible_edit_add($handler, $id, $location = 'left') {
  1116. ctools_include('modal');
  1117. ctools_include('ajax');
  1118. $settings = &$handler->display->layout_settings;
  1119. panels_flexible_convert_settings($settings, $handler->plugins['layout']);
  1120. if (empty($settings['items'][$id])) {
  1121. ctools_modal_render(t('Error'), t('Invalid item id.'));
  1122. }
  1123. $parent = &$settings['items'][$id];
  1124. switch ($parent['type']) {
  1125. case 'column':
  1126. $title = t('Add row');
  1127. // Create the new item with defaults.
  1128. $item = array(
  1129. 'type' => 'row',
  1130. 'contains' => 'region',
  1131. 'children' => array(),
  1132. 'parent' => $id,
  1133. );
  1134. break;
  1135. case 'row':
  1136. switch ($parent['contains']) {
  1137. case 'region':
  1138. $title = $location == 'left' ? t('Add region to left') : t('Add region to right');
  1139. $item = array(
  1140. 'type' => 'region',
  1141. 'title' => '',
  1142. 'width' => 100,
  1143. 'width_type' => '%',
  1144. 'parent' => $id,
  1145. );
  1146. break;
  1147. case 'column':
  1148. $title = $location == 'left' ? t('Add column to left') : t('Add column to right');
  1149. $item = array(
  1150. 'type' => 'column',
  1151. 'width' => 100,
  1152. 'width_type' => '%',
  1153. 'parent' => $id,
  1154. 'children' => array(),
  1155. );
  1156. break;
  1157. }
  1158. // Create the new item with defaults.
  1159. break;
  1160. case 'region':
  1161. // Cannot add items to regions.
  1162. break;
  1163. }
  1164. $form_state = array(
  1165. 'display' => &$handler->display,
  1166. 'parent' => &$parent,
  1167. 'item' => &$item,
  1168. 'id' => $id,
  1169. 'settings' => &$settings,
  1170. 'ajax' => TRUE,
  1171. 'title' => $title,
  1172. 'location' => $location,
  1173. );
  1174. $output = ctools_modal_form_wrapper('panels_flexible_add_item_form', $form_state);
  1175. if (!empty($form_state['executed'])) {
  1176. // If the width type changed then other nearby items will have
  1177. // to have their widths adjusted.
  1178. panels_edit_cache_set($handler->cache);
  1179. $output = array();
  1180. $css_id = isset($handler->display->css_id) ? $handler->display->css_id : '';
  1181. // Create a renderer object so we can render our new stuff.
  1182. $renderer = panels_flexible_create_renderer(TRUE, $css_id, array(), $settings, $handler->display, $handler->plugins['layout'], $handler);
  1183. $content = '';
  1184. if ($item['type'] == 'region') {
  1185. $handler->plugins['layout']['regions'][$form_state['key']] = $item['title'];
  1186. $content = $handler->render_region($form_state['key'], array());
  1187. // Manually add the hidden field that our region uses to store pane info.
  1188. $content .= '<input type="hidden" name="panel[pane][' .
  1189. $form_state['key'] . ']" value="" />';
  1190. }
  1191. else {
  1192. // We need to make sure the left/middle/right divs exist inside this
  1193. // so that more stuff can be added inside it as needed.
  1194. foreach (array('left', 'middle', 'right') as $position) {
  1195. if (!empty($content) || $renderer->admin) {
  1196. $content .= '<div class="' . $renderer->base[$item['type']] . '-' . $form_state['key'] . '-' . $position . '"></div>';
  1197. }
  1198. }
  1199. }
  1200. // Render the item.
  1201. $parent_class = $renderer->base[$parent['type']] . '-' . $id;
  1202. $item_output = panels_flexible_render_item($renderer, $item, $content, $form_state['key'], 0, 0, $item['type'] == 'row');
  1203. // Get all the CSS necessary for the entire row (as width adjustments may
  1204. // have cascaded).
  1205. $css = array();
  1206. panels_flexible_get_css_group($css, $renderer, $parent['children'], '.' . $parent_class, $item['type'], $id);
  1207. $position = isset($renderer->positions[$form_state['key']]) ? $renderer->positions[$form_state['key']] : 'middle';
  1208. // If there's a nearby item, add the splitter and rewrite the width
  1209. // of the nearby item as it probably got adjusted.
  1210. // The blocks of code in this else look very similar but are not actually
  1211. // duplicated because the order changes based on left or right.
  1212. switch ($position) {
  1213. case 'left':
  1214. if ($location == 'left') {
  1215. $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], $form_state['sibling']);
  1216. $output[] = ajax_command_prepend('#panels-dnd-main .' . $parent_class . '-left', $item_output);
  1217. }
  1218. elseif ($location == 'right') {
  1219. // If we are adding to the right side of the left box, there is
  1220. // a splitter that we have to remove; then we add our box normally,
  1221. // and then add a new splitter for just our guy.
  1222. $output[] = ajax_command_remove('panels-flexible-splitter-for-' . $renderer->base[$item['type']] . '-' . $form_state['key']);
  1223. $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) . $item_output;
  1224. $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], NULL);
  1225. $output[] = ajax_command_append('#panels-dnd-main .' . $parent_class . '-left', $item_output);
  1226. }
  1227. break;
  1228. case 'right':
  1229. if (!empty($form_state['sibling'])) {
  1230. $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) . $item_output;
  1231. }
  1232. $output[] = ajax_command_append('#panels-dnd-main .' . $parent_class . '-right', $item_output);
  1233. break;
  1234. case 'middle':
  1235. if ($location == 'left') {
  1236. if (!empty($form_state['sibling'])) {
  1237. $item_output .= panels_flexible_render_splitter($renderer, $form_state['key'], $form_state['sibling']);
  1238. }
  1239. $output[] = ajax_command_prepend('#panels-dnd-main .' . $parent_class . '-middle', $item_output);
  1240. }
  1241. else {
  1242. if (!empty($form_state['sibling'])) {
  1243. $item_output = panels_flexible_render_splitter($renderer, $form_state['sibling'], $form_state['key']) . $item_output;
  1244. }
  1245. $output[] = ajax_command_append('#panels-dnd-main .' . $parent_class . '-middle', $item_output);
  1246. }
  1247. break;
  1248. }
  1249. // Send our fix height command.
  1250. $output[] = array('command' => 'flexible_fix_height');
  1251. if (!empty($form_state['sibling'])) {
  1252. $sibling_width = '#panels-dnd-main .' . $renderer->base[$item['type']] . '-' . $form_state['sibling'] . '-width';
  1253. $output[] = array(
  1254. 'command' => 'flexible_set_width',
  1255. 'selector' => $sibling_width,
  1256. 'width' => $settings['items'][$form_state['sibling']]['width'],
  1257. );
  1258. }
  1259. foreach ($css as $selector => $data) {
  1260. $output[] = ajax_command_css($selector, $data);
  1261. }
  1262. // Rerender our parent item links:
  1263. $output[] = ajax_command_replace('.flexible-links-' . $id,
  1264. panels_flexible_render_item_links($renderer, $id, $parent));
  1265. $output[] = array(
  1266. 'command' => 'flexible_fix_firstlast',
  1267. 'selector' => '.' . $parent_class . '-inside',
  1268. 'base' => 'panels-flexible-' . $item['type'],
  1269. );
  1270. $output[] = ctools_modal_command_dismiss();
  1271. }
  1272. $handler->commands = $output;
  1273. }
  1274. /**
  1275. * Form to add a row, column or region to a flexible layout.
  1276. */
  1277. function panels_flexible_add_item_form($form, &$form_state) {
  1278. $display = &$form_state['display'];
  1279. $item = &$form_state['item'];
  1280. $parent = &$form_state['parent'];
  1281. $settings = &$form_state['settings'];
  1282. $location = &$form_state['location'];
  1283. $id = &$form_state['id'];
  1284. if ($item['type'] == 'region') {
  1285. $form['title'] = array(
  1286. '#title' => t('Region title'),
  1287. '#type' => 'textfield',
  1288. '#default_value' => $item['title'],
  1289. '#required' => TRUE,
  1290. );
  1291. }
  1292. $form['class'] = array(
  1293. '#title' => t('CSS Class'),
  1294. '#type' => 'textfield',
  1295. '#default_value' => isset($item['class']) ? $item['class'] : '',
  1296. '#description' => t('Enter a CSS class that will be used. This can be used to apply automatic styling from your theme, for example.'),
  1297. );
  1298. if ($item['type'] != 'row') {
  1299. // If there is a 'fixed' type on the side we're adding to, then this
  1300. // must also be fixed. Otherwise it can be either and should default to
  1301. // fluid.
  1302. $restrict = FALSE;
  1303. if (!empty($parent['children'])) {
  1304. if ($location == 'left') {
  1305. $sibling = reset($parent['children']);
  1306. }
  1307. else {
  1308. $sibling = end($parent['children']);
  1309. }
  1310. if ($settings['items'][$sibling]['width_type'] == 'px') {
  1311. $restrict = TRUE;
  1312. $item['width_type'] = 'px';
  1313. }
  1314. }
  1315. $form['width_type'] = array(
  1316. '#type' => 'select',
  1317. '#title' => t('Width'),
  1318. '#default_value' => $item['width_type'],
  1319. '#options' => array(
  1320. '%' => t('Fluid'),
  1321. 'px' => t('Fixed'),
  1322. ),
  1323. '#disabled' => $restrict,
  1324. );
  1325. if ($restrict) {
  1326. // This forces the value because disabled items don't always send
  1327. // their data back.
  1328. $form['width_type']['#value'] = $item['width_type'];
  1329. $form['width_type']['#description'] = t('Items cannot be set to fluid if there are fixed items already on that side.');
  1330. }
  1331. }
  1332. else {
  1333. $form['contains'] = array(
  1334. '#type' => 'select',
  1335. '#title' => t('Contains'),
  1336. '#default_value' => $item['contains'],
  1337. '#options' => array(
  1338. 'region' => t('Regions'),
  1339. 'column' => t('Columns'),
  1340. ),
  1341. );
  1342. }
  1343. $form['hide_empty'] = array(
  1344. '#title' => t('Hide element if empty'),
  1345. '#type' => 'checkbox',
  1346. '#default_value' => 0,
  1347. );
  1348. $form['save'] = array(
  1349. '#type' => 'submit',
  1350. '#value' => t('Save'),
  1351. );
  1352. return $form;
  1353. }
  1354. /**
  1355. * Submit handler for editing a flexible item.
  1356. */
  1357. function panels_flexible_add_item_form_submit(&$form, &$form_state) {
  1358. $item = &$form_state['item'];
  1359. $parent = &$form_state['parent'];
  1360. $location = &$form_state['location'];
  1361. $settings = &$form_state['settings'];
  1362. $item['class'] = $form_state['values']['class'];
  1363. if ($item['type'] == 'region') {
  1364. $item['title'] = $form_state['values']['title'];
  1365. }
  1366. if ($item['type'] != 'row') {
  1367. $item['width_type'] = $form_state['values']['width_type'];
  1368. }
  1369. else {
  1370. $item['contains'] = $form_state['values']['contains'];
  1371. }
  1372. $item['hide_empty'] = $form_state['values']['hide_empty'];
  1373. if ($item['type'] == 'region') {
  1374. // Derive the region key from the title.
  1375. $key = preg_replace("/[^a-z0-9]/", '_', drupal_strtolower($item['title']));
  1376. while (isset($settings['items'][$key])) {
  1377. $key .= '_';
  1378. }
  1379. $form_state['key'] = $key;
  1380. }
  1381. else {
  1382. $form_state['key'] = $key = max(array_keys($settings['items'])) + 1;
  1383. }
  1384. $form_state['sibling'] = NULL;
  1385. if ($item['type'] != 'row' && !empty($parent['children'])) {
  1386. // Figure out what the width should be and adjust our sibling if
  1387. // necessary.
  1388. if ($location == 'left') {
  1389. $form_state['sibling'] = reset($parent['children']);
  1390. }
  1391. else {
  1392. $form_state['sibling'] = end($parent['children']);
  1393. }
  1394. // If there is no sibling, or the sibling is of a different type,
  1395. // the default 100 will work for either fixed or fluid.
  1396. if ($form_state['sibling'] && $settings['items'][$form_state['sibling']]['width_type'] == $item['width_type']) {
  1397. // Steal half of the sibling's space.
  1398. $width = $settings['items'][$form_state['sibling']]['width'] / 2;
  1399. $settings['items'][$form_state['sibling']]['width'] = $width;
  1400. $item['width'] = $width;
  1401. }
  1402. }
  1403. // Place the item.
  1404. $settings['items'][$key] = $item;
  1405. if ($location == 'left') {
  1406. array_unshift($parent['children'], $key);
  1407. }
  1408. else {
  1409. $parent['children'][] = $key;
  1410. }
  1411. }
  1412. /**
  1413. * Panels remove AJAX responder.
  1414. *
  1415. * Removes an existing row, column or region from a flexible layout.
  1416. */
  1417. function panels_ajax_flexible_edit_remove($handler, $id) {
  1418. $settings = &$handler->display->layout_settings;
  1419. panels_flexible_convert_settings($settings, $handler->plugins['layout']);
  1420. if (empty($settings['items'][$id])) {
  1421. ajax_render_error(t('Invalid item id.'));
  1422. }
  1423. $item = &$settings['items'][$id];
  1424. $css_id = isset($handler->display->css_id) ? $handler->display->css_id : '';
  1425. // Create a renderer object so we can render our new stuff.
  1426. $renderer = panels_flexible_create_renderer(TRUE, $css_id, array(), $settings, $handler->display, $handler->plugins['layout'], $handler);
  1427. $siblings = &$settings['items'][$item['parent']]['children'];
  1428. $parent_class = '.' . $renderer->base[$settings['items'][$item['parent']]['type']] . '-' . $item['parent'];
  1429. // Find the offset of our array. This will also be the key because
  1430. // this is a simple array.
  1431. $offset = array_search($id, $siblings);
  1432. // Only bother with this stuff if our item is fluid, since fixed is
  1433. // as fixed does.
  1434. if ($item['type'] != 'row') {
  1435. if (isset($siblings[$offset + 1])) {
  1436. $next = $siblings[$offset + 1];
  1437. }
  1438. if (isset($siblings[$offset - 1])) {
  1439. $prev = $siblings[$offset - 1];
  1440. }
  1441. if ($item['width_type'] == '%') {
  1442. // First, try next.
  1443. if (isset($next) && $settings['items'][$next]['width_type'] == '%') {
  1444. $settings['items'][$next]['width'] += $item['width'];
  1445. }
  1446. // If that failed, try the previous one.
  1447. elseif (isset($prev) && $settings['items'][$prev]['width_type'] == '%') {
  1448. $settings['items'][$prev]['width'] += $item['width'];
  1449. }
  1450. }
  1451. // Not sure what happens if they both failed. Maybe nothing.
  1452. }
  1453. // Remove the item.
  1454. array_splice($siblings, $offset, 1);
  1455. unset($settings['items'][$id]);
  1456. // Save our new state.
  1457. panels_edit_cache_set($handler->cache);
  1458. $class = $renderer->base[$item['type']] . '-' . $id;
  1459. $output = array();
  1460. $output[] = ajax_command_remove('#panels-dnd-main .' . $class);
  1461. // Regenerate the CSS for siblings.
  1462. if (!empty($siblings)) {
  1463. // Get all the CSS necessary for the entire row (as width adjustments may
  1464. // have cascaded).
  1465. $css = array();
  1466. panels_flexible_get_css_group($css, $renderer, $siblings, $parent_class, $item['type'], $item['parent']);
  1467. foreach ($css as $selector => $data) {
  1468. $output[] = ajax_command_css($selector, $data);
  1469. }
  1470. }
  1471. // There are potentially two splitters linked to this item to be removed.
  1472. if (!empty($prev)) {
  1473. $output[] = ajax_command_remove('.flexible-splitter-for-' . $renderer->base[$item['type']] . '-' . $prev);
  1474. }
  1475. // Try to remove the 'next' one even if there isn't a $next.
  1476. $output[] = ajax_command_remove('.flexible-splitter-for-' . $renderer->base[$item['type']] . '-' . $id);
  1477. if (!empty($prev) && !empty($next)) {
  1478. // Add a new splitter that links $prev and $next:
  1479. $splitter = panels_flexible_render_splitter($renderer, $prev, $next);
  1480. $prev_class = '#panels-dnd-main .' . $renderer->base[$item['type']] . '-' . $prev;
  1481. $output[] = ajax_command_after($prev_class, $splitter);
  1482. // Send our fix height command.
  1483. $output[] = array('command' => 'flexible_fix_height');
  1484. }
  1485. // Rerender our parent item links:
  1486. $output[] = ajax_command_replace('.flexible-links-' . $item['parent'],
  1487. panels_flexible_render_item_links($renderer, $item['parent'], $settings['items'][$item['parent']]));
  1488. $output[] = array(
  1489. 'command' => 'flexible_fix_firstlast',
  1490. 'selector' => $parent_class . '-inside',
  1491. 'base' => 'panels-flexible-' . $item['type'],
  1492. );
  1493. $handler->commands = $output;
  1494. }
  1495. /**
  1496. * AJAX responder to store resize information when the user adjusts splitter.
  1497. */
  1498. function panels_ajax_flexible_edit_resize($handler) {
  1499. ctools_include('ajax');
  1500. $settings = &$handler->display->layout_settings;
  1501. panels_flexible_convert_settings($settings, $handler->plugins['layout']);
  1502. $settings['items'][$_POST['left']]['width'] = $_POST['left_width'];
  1503. if (!empty($_POST['right']) && $_POST['right'] != $_POST['left']) {
  1504. $settings['items'][$_POST['right']]['width'] = $_POST['right_width'];
  1505. }
  1506. // Save our new state.
  1507. panels_edit_cache_set($handler->cache);
  1508. $handler->commands = array('ok');
  1509. }
  1510. /**
  1511. * AJAX form to bring up the "reuse" modal.
  1512. */
  1513. function panels_ajax_flexible_edit_reuse($handler) {
  1514. $settings = &$handler->display->layout_settings;
  1515. panels_flexible_convert_settings($settings, $handler->plugins['layout']);
  1516. $form_state = array(
  1517. 'display' => &$handler->display,
  1518. 'settings' => &$settings,
  1519. 'ajax' => TRUE,
  1520. 'title' => t('Save this layout for reuse'),
  1521. );
  1522. $output = ctools_modal_form_wrapper('panels_flexible_reuse_form', $form_state);
  1523. if (!empty($form_state['executed'])) {
  1524. // Create the new layout.
  1525. ctools_include('export');
  1526. $layout = ctools_export_crud_new('panels_layout');
  1527. $layout->plugin = 'flexible';
  1528. $layout->name = $form_state['values']['name'];
  1529. $layout->admin_title = $form_state['values']['admin_title'];
  1530. $layout->admin_description = $form_state['values']['admin_description'];
  1531. $layout->category = $form_state['values']['category'];
  1532. $layout->settings = $handler->display->layout_settings;
  1533. // Save it.
  1534. ctools_export_crud_save('panels_layout', $layout);
  1535. if (empty($form_state['values']['keep'])) {
  1536. // Set the actual layout_settings to now use the newly minted layout:
  1537. $handler->display->layout = 'flexible:' . $layout->name;
  1538. $handler->display->layout_settings = array();
  1539. // Save our new state.
  1540. panels_edit_cache_set($handler->cache);
  1541. }
  1542. // Dismiss the modal.
  1543. $output[] = ctools_modal_command_dismiss();
  1544. }
  1545. $handler->commands = $output;
  1546. }
  1547. function panels_flexible_reuse_form($form, &$form_state) {
  1548. $form['markup'] = array(
  1549. '#prefix' => '<div class="description">',
  1550. '#suffix' => '</div>',
  1551. '#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.'),
  1552. );
  1553. $form['admin_title'] = array(
  1554. '#type' => 'textfield',
  1555. '#title' => t('Administrative title'),
  1556. '#description' => t('This will appear in the administrative interface to easily identify it.'),
  1557. );
  1558. $form['name'] = array(
  1559. '#type' => 'machine_name',
  1560. '#title' => t('Machine name'),
  1561. '#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!'),
  1562. '#machine_name' => array(
  1563. 'exists' => 'panels_flexible_edit_name_exists',
  1564. 'source' => array('admin_title'),
  1565. ),
  1566. );
  1567. $form['category'] = array(
  1568. '#type' => 'textfield',
  1569. '#title' => t('Category'),
  1570. '#description' => t('What category this layout should appear in. If left blank the category will be "Miscellaneous".'),
  1571. );
  1572. $form['admin_description'] = array(
  1573. '#type' => 'textarea',
  1574. '#title' => t('Administrative description'),
  1575. '#description' => t('A description of what this layout is, does or is for, for administrative use.'),
  1576. );
  1577. $form['keep'] = array(
  1578. '#type' => 'checkbox',
  1579. '#title' => t('Keep current panel layout flexible'),
  1580. '#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.'),
  1581. );
  1582. $form['submit'] = array(
  1583. '#type' => 'submit',
  1584. '#value' => t('Save'),
  1585. );
  1586. return $form;
  1587. }
  1588. function panels_flexible_reuse_form_validate(&$form, &$form_state) {
  1589. if (empty($form_state['values']['name'])) {
  1590. form_error($form['name'], t('You must choose a machine name.'));
  1591. }
  1592. ctools_include('export');
  1593. $test = ctools_export_crud_load('panels_layout', $form_state['values']['name']);
  1594. if ($test) {
  1595. form_error($form['name'], t('That name is used by another layout: @layout', array('@layout' => $test->admin_title)));
  1596. }
  1597. // Ensure name fits the rules:
  1598. if (preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['name'])) {
  1599. form_error($form['name'], t('Name must be alphanumeric or underscores only.'));
  1600. }
  1601. }
  1602. /**
  1603. * Test for #machine_name type to see if an export exists.
  1604. */
  1605. function panels_flexible_edit_name_exists($name, $element, &$form_state) {
  1606. ctools_include('export');
  1607. $plugin = $form_state['plugin'];
  1608. return (empty($form_state['item']->export_ui_allow_overwrite) && ctools_export_crud_load($plugin['schema'], $name));
  1609. }