render_example.module 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. <?php
  2. /**
  3. * @file
  4. * Demonstrates render arrays.
  5. */
  6. /**
  7. * @defgroup render_example Example: Render
  8. * @ingroup examples
  9. * @{
  10. * Demonstrate how render arrays are arranged and how they can be altered.
  11. * This alters blocks and the page to show the actual render array
  12. * that is being used to create each item.
  13. *
  14. * @see drupal_render()
  15. */
  16. // This allows drupal_var_export() to work.
  17. require_once 'includes/utility.inc';
  18. /**
  19. * Implements hook_menu().
  20. */
  21. function render_example_menu() {
  22. $items['examples/render_example'] = array(
  23. 'title' => 'Render Example',
  24. 'page callback' => 'render_example_info',
  25. 'access callback' => TRUE,
  26. );
  27. $items['examples/render_example/altering'] = array(
  28. 'title' => 'Alter pages and blocks',
  29. 'page callback' => 'drupal_get_form',
  30. 'page arguments' => array('render_example_demo_form'),
  31. 'access arguments' => array('access devel information'),
  32. );
  33. $items['examples/render_example/arrays'] = array(
  34. 'title' => 'Render array examples',
  35. 'page callback' => 'render_example_arrays',
  36. 'access callback' => TRUE,
  37. );
  38. return $items;
  39. }
  40. /**
  41. * Simple basic information about the module; an entry point.
  42. */
  43. function render_example_info() {
  44. return t('The render example provides a <ul><li><a href="!arrays">demonstration of of render array usage</a></li><li><a href="!alter">using hook_page_alter()</a> to make various changes on a page.</li></ul>', array('!arrays' => url('examples/render_example/arrays'), '!alter' => url('examples/render_example/altering')));
  45. }
  46. /**
  47. * Provides a number of render arrays and show what they do.
  48. *
  49. * Each array is keyed by a description; it's returned for rendering at page
  50. * render time. It's easy to add new examples to this.
  51. *
  52. * The array items in $demos are intended to be raw, normal render arrays
  53. * that can be experimented with to end up with different outcomes.
  54. */
  55. function render_example_arrays() {
  56. // Interval in seconds for cache update with #cache.
  57. $interval = 60;
  58. $demos = array(
  59. // Demonstrate the simplest markup, a #markup element.
  60. t('Super simple #markup') => array(
  61. '#markup' => t('Some basic text in a #markup (shows basic markup and how it is rendered)'),
  62. ),
  63. // Shows how #prefix and #suffix can add markup into an array.
  64. t('Using #prefix and #suffix') => array(
  65. '#markup' => t('This one adds a prefix and suffix, which put a div around the item'),
  66. '#prefix' => '<div><br/>(prefix)<br/>',
  67. '#suffix' => '<br/>(suffix)</div>',
  68. ),
  69. // When #theme is provided, it is the #theme function's job to figure out
  70. // the meaning of the render array. The #theme function receives the entire
  71. // element in $variables and must return it, where it will be the content
  72. // of '#children'. When a #theme or other function is provided, custom
  73. // properties can be invented and used as needed, as the #separator
  74. // property provided here.
  75. //
  76. // If #theme is not provided, either explicitly or by the underlying
  77. // element, then the children are rendered using their own properties and
  78. // the results go into #children.
  79. t('theme for an element') => array(
  80. 'child' => array(
  81. t('This is some text that should be put together'),
  82. t('This is some more text that we need'),
  83. ),
  84. // An element we've created which will be used by our theming function.
  85. '#separator' => ' | ',
  86. '#theme' => 'render_example_aggregate',
  87. ),
  88. // #theme_wrappers provides an array of theme functions which theme the
  89. // envelope or "wrapper" of a set of child elements. The theme function
  90. // finds its element children (the sub-arrays) already rendered in
  91. // '#children'.
  92. t('theme_wrappers demonstration') => array(
  93. 'child1' => array('#markup' => t('Markup for child1')),
  94. 'child2' => array('#markup' => t('Markup for child2')),
  95. '#theme_wrappers' => array('render_example_add_div', 'render_example_add_notes'),
  96. ),
  97. // Add '#pre_render' and '#post_render' handlers.
  98. // - '#pre_render' functions get access to the array before it is rendered
  99. // and can change it. This is similar to a theme function, but it is a
  100. // specific fixed function and changes the array in place rather than
  101. // rendering it..
  102. // - '#post_render' functions get access to the rendered content, but also
  103. // have the original array available.
  104. t('pre_render and post_render') => array(
  105. '#markup' => '<div style="color:green">' . t('markup for pre_render and post_render example') . '</div>',
  106. '#pre_render' => array('render_example_add_suffix'),
  107. '#post_render' => array('render_example_add_prefix'),
  108. ),
  109. // Cache an element for $interval seconds using #cache.
  110. // The assumption here is that this is an expensive item to render, perhaps
  111. // large or otherwise expensive. Of course here it's just a piece of markup,
  112. // so we don't get the value.
  113. //
  114. // #cache allows us to set
  115. // - 'keys', an array of strings that will create the string cache key.
  116. // - 'bin', the cache bin
  117. // - 'expire', the expire timestamp. Note that this is actually limited
  118. // to the granularity of a cron run.
  119. // - 'granularity', a bitmask determining at what level the caching is done
  120. // (user, role, page).
  121. t('cache demonstration') => array(
  122. // If your expensive function were to be executed here it would happen
  123. // on every page load regardless of the cache. The actual markup is
  124. // added via the #pre_render function, so that drupal_render() will only
  125. // execute the expensive function if this array has not been cached.
  126. '#markup' => '',
  127. '#pre_render' => array('render_example_cache_pre_render'),
  128. '#cache' => array(
  129. 'keys' => array('render_example', 'cache', 'demonstration'),
  130. 'bin' => 'cache',
  131. 'expire' => time() + $interval,
  132. 'granularity' => DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE,
  133. ),
  134. ),
  135. );
  136. // The rest of this function just places the above arrays in a context where
  137. // they can be rendered (hopefully attractively and usefully) on the page.
  138. $page_array = array();
  139. foreach ($demos as $key => $item) {
  140. $page_array[$key]['#theme_wrappers'] = array('render_array');
  141. $page_array[$key]['#description'] = $key;
  142. $page_array[$key]['unrendered'] = array(
  143. '#prefix' => '<div class="unrendered-label">' . t('Unrendered array (as plain text and with a krumo version)') . ':</div>',
  144. '#type' => 'markup',
  145. '#markup' => htmlentities(drupal_var_export($item)),
  146. );
  147. $page_array[$key]['kpr'] = array(
  148. // The kpr() function is from devel module and is here only allow us
  149. // to output the array in a way that's easy to explore.
  150. '#markup' => kpr($item, TRUE),
  151. );
  152. $page_array[$key]['hr'] = array('#markup' => '<hr/>');
  153. $page_array[$key]['rendered'] = array($item);
  154. $page_array[$key]['rendered']['#prefix'] = '<p><em>Rendered version (light blue)</em>:</p>' . '<div class="rendered">';
  155. $page_array[$key]['rendered']['#suffix'] = '</div>';
  156. }
  157. return $page_array;
  158. }
  159. /**
  160. * A '#pre_render' function.
  161. *
  162. * @param array $element
  163. * The element which will be rendered.
  164. *
  165. * @return array
  166. * The altered element. In this case we add the #markup.
  167. */
  168. function render_example_cache_pre_render($element) {
  169. $element['#markup'] = render_example_cache_expensive();
  170. // The following line is due to the bug described in
  171. // http://drupal.org/node/914792. A #markup element's #pre_render must set
  172. // #children because it replaces the default #markup pre_render, which is
  173. // drupal_pre_render_markup().
  174. $element['#children'] = $element['#markup'];
  175. return $element;
  176. }
  177. /**
  178. * A potentially expensive function.
  179. *
  180. * @return string
  181. * Some demo text.
  182. */
  183. function render_example_cache_expensive() {
  184. $interval = 60;
  185. $time_message = t('The current time was %time when this was cached. Updated every %interval seconds', array('%time' => date('r'), '%interval' => $interval));
  186. // Uncomment the following line to demonstrate that this function is not
  187. // being run when the rendered array is cached.
  188. // drupal_set_message($time_message);
  189. return $time_message;
  190. }
  191. /**
  192. * A '#pre_render' function.
  193. *
  194. * @param array $element
  195. * The element which will be rendered.
  196. *
  197. * @return array
  198. * The altered element. In this case we add a #prefix to it.
  199. */
  200. function render_example_add_suffix($element) {
  201. $element['#suffix'] = '<div style="color:red">' . t('This #suffix was added by a #pre_render') . '</div>';
  202. // The following line is due to the bug described in
  203. // http://drupal.org/node/914792. A #markup element's #pre_render must set
  204. // #children because it replaces the default #markup pre_render, which is
  205. // drupal_pre_render_markup().
  206. $element['#children'] = $element['#markup'];
  207. return $element;
  208. }
  209. /**
  210. * A '#post_render' function to add a little markup onto the end markup.
  211. *
  212. * @param string $markup
  213. * The rendered element.
  214. * @param array $element
  215. * The element which was rendered (for reference)
  216. *
  217. * @return string
  218. * Markup altered as necessary. In this case we add a little postscript to it.
  219. */
  220. function render_example_add_prefix($markup, $element) {
  221. $markup = '<div style="color:blue">This markup was added after rendering by a #post_render</div>' . $markup;
  222. return $markup;
  223. }
  224. /**
  225. * A #theme function.
  226. *
  227. * This #theme function has the responsibility of consolidating/rendering the
  228. * children's markup and returning it, where it will be placed in the
  229. * element's #children property.
  230. */
  231. function theme_render_example_aggregate($variables) {
  232. $output = '';
  233. foreach (element_children($variables['element']['child']) as $item) {
  234. $output .= $variables['element']['child'][$item] . $variables['element']['#separator'];
  235. }
  236. return $output;
  237. }
  238. /*************** Altering Section **************************
  239. * The following section of the example builds and arranges the altering
  240. * example.
  241. */
  242. /**
  243. * Builds the form that offers options of what items to show.
  244. */
  245. function render_example_demo_form($form, &$form_state) {
  246. $form['description'] = array(
  247. '#type' => 'markup',
  248. '#markup' => t('This example shows what render arrays look like in the building of a page. It will not work unless the user running it has the "access devel information" privilege. It shows both the actual arrays used to build a page or block and also the capabilities of altering the page late in its lifecycle.'),
  249. );
  250. $form['show_arrays'] = array(
  251. '#type' => 'fieldset',
  252. '#title' => t('Show render arrays'),
  253. );
  254. foreach (array('block', 'page') as $type) {
  255. $form['show_arrays']['render_example_show_' . $type] = array(
  256. '#type' => 'checkbox',
  257. '#title' => t('Show @type render arrays', array('@type' => $type)),
  258. '#default_value' => variable_get('render_example_show_' . $type, FALSE),
  259. );
  260. }
  261. $form['page_fiddling'] = array(
  262. '#type' => 'fieldset',
  263. '#title' => t('Make changes on page via hook_page_alter()'),
  264. );
  265. $form['page_fiddling']['render_example_note_about_render_arrays'] = array(
  266. '#title' => t('Add a note about render arrays to top of sidebar_first (if it exists)'),
  267. '#description' => t('Creates a simple render array that displays the use of #pre_render, #post_render, #theme, and #theme_wrappers.'),
  268. '#type' => 'checkbox',
  269. '#default_value' => variable_get('render_example_note_about_render_arrays', FALSE),
  270. );
  271. $form['page_fiddling']['render_example_move_navigation_menu'] = array(
  272. '#title' => t('Move the navigation menu to the top of the content area'),
  273. '#description' => t('Uses hook_page_alter() to move the navigation menu into another region.'),
  274. '#type' => 'checkbox',
  275. '#default_value' => variable_get('render_example_move_navigation_menu', FALSE),
  276. );
  277. $form['page_fiddling']['render_example_reverse_sidebar'] = array(
  278. '#title' => t('Reverse ordering of sidebar_first elements (if it exists) - will affect the above'),
  279. '#description' => t('Uses hook_page_alter() to reverse the ordering of items in sidebar_first'),
  280. '#type' => 'checkbox',
  281. '#default_value' => variable_get('render_example_reverse_sidebar', FALSE),
  282. );
  283. $form['page_fiddling']['render_example_prefix'] = array(
  284. '#title' => t('Use #prefix and #suffix to wrap a div around every block'),
  285. '#description' => t('Uses hook_page_alter to wrap all blocks with a div using #prefix and #suffix'),
  286. '#type' => 'checkbox',
  287. '#default_value' => variable_get('render_example_prefix'),
  288. );
  289. return system_settings_form($form);
  290. }
  291. /**
  292. * Implements hook_page_alter().
  293. *
  294. * Alters the page in several different ways based on how the form has been
  295. * configured.
  296. */
  297. function render_example_page_alter(&$page) {
  298. // Re-sort the sidebar in reverse order.
  299. if (variable_get('render_example_reverse_sidebar', FALSE) && !empty($page['sidebar_first'])) {
  300. $page['sidebar_first'] = array_reverse($page['sidebar_first']);
  301. foreach (element_children($page['sidebar_first']) as $element) {
  302. // Reverse the weights if they exist.
  303. if (!empty($page['sidebar_first'][$element]['#weight'])) {
  304. $page['sidebar_first'][$element]['#weight'] *= -1;
  305. }
  306. }
  307. $page['sidebar_first']['#sorted'] = FALSE;
  308. }
  309. // Add a list of items to the top of sidebar_first.
  310. // This shows how #theme and #theme_wrappers work.
  311. if (variable_get('render_example_note_about_render_arrays', FALSE) && !empty($page['sidebar_first'])) {
  312. $items = array(
  313. t('Render arrays are everywhere in D7'),
  314. t('Leave content unrendered as much as possible'),
  315. t('This allows rearrangement and alteration very late in page cycle'),
  316. );
  317. $note = array(
  318. '#title' => t('Render Array Example'),
  319. '#items' => $items,
  320. // The functions in #pre_render get to alter the actual data before it
  321. // gets rendered by the various theme functions.
  322. '#pre_render' => array('render_example_change_to_ol'),
  323. // The functions in #post_render get both the element and the rendered
  324. // data and can add to the rendered data.
  325. '#post_render' => array('render_example_add_hr'),
  326. // The #theme theme operation gets the first chance at rendering the
  327. // element and its children.
  328. '#theme' => 'item_list',
  329. // Then the theme operations in #theme_wrappers can wrap more around
  330. // what #theme left in #chilren.
  331. '#theme_wrappers' => array('render_example_add_div', 'render_example_add_notes'),
  332. '#weight' => -9999,
  333. );
  334. $page['sidebar_first']['render_array_note'] = $note;
  335. $page['sidebar_first']['#sorted'] = FALSE;
  336. }
  337. // Move the navigation menu into the content area.
  338. if (variable_get('render_example_move_navigation_menu', FALSE) && !empty($page['sidebar_first']['system_navigation']) && !empty($page['content'])) {
  339. $page['content']['system_navigation'] = $page['sidebar_first']['system_navigation'];
  340. $page['content']['system_navigation']['#weight'] = -99999;
  341. unset($page['content']['#sorted']);
  342. unset($page['sidebar_first']['system_navigation']);
  343. }
  344. // Show the render array used to build the page render array display.
  345. if (variable_get('render_example_show_page', FALSE)) {
  346. $form['render_example_page_fieldset'] = array(
  347. '#type' => 'fieldset',
  348. '#title' => t('Page render array'),
  349. '#collapsible' => TRUE,
  350. '#collapsed' => TRUE,
  351. );
  352. $form['render_example_page_fieldset']['markup'] = array(
  353. // The kpr() function is from devel module and is here only allow us
  354. // to output the array in a way that's easy to explore.
  355. '#markup' => kpr($page, TRUE),
  356. );
  357. $page['content']['page_render_array'] = drupal_get_form('render_example_embedded_form', $form);
  358. $page['content']['page_render_array']['#weight'] = -999999;
  359. $page['content']['#sorted'] = FALSE;
  360. }
  361. // Add render array to the bottom of each block.
  362. if (variable_get('render_example_show_block', FALSE)) {
  363. foreach (element_children($page) as $region_name) {
  364. foreach (element_children($page[$region_name]) as $block_name) {
  365. // Push the block down a level so we can add another block after it.
  366. $old_block = $page[$region_name][$block_name];
  367. $page[$region_name][$block_name] = array(
  368. $block_name => $old_block,
  369. );
  370. $form = array();
  371. $form['render_example_block_fieldset'] = array(
  372. '#type' => 'fieldset',
  373. '#title' => t('Block render array'),
  374. '#collapsible' => TRUE,
  375. '#collapsed' => TRUE,
  376. );
  377. $form['render_example_block_fieldset']['markup'] = array(
  378. '#type' => 'item',
  379. '#title' => t('%blockname block render array', array('%blockname' => $block_name)),
  380. // The kpr() function is from devel module and is here only allow us
  381. // to output the array in a way that's easy to explore.
  382. '#markup' => kpr($old_block, TRUE),
  383. );
  384. // Add the new block that contains the render array.
  385. $page[$region_name][$block_name]['render_example_block_render_array'] = drupal_get_form('render_example_embedded_form', $form);
  386. $page[$region_name][$block_name]['render_example_block_render_array']['#weight'] = 999;
  387. }
  388. }
  389. }
  390. // Add #prefix and #suffix to a block to wrap a div around it.
  391. if (variable_get('render_example_prefix', FALSE)) {
  392. foreach (element_children($page) as $region_name) {
  393. foreach (element_children($page[$region_name]) as $block_name) {
  394. $block = &$page[$region_name][$block_name];
  395. $block['#prefix'] = '<div class="block-prefix"><p>Prefixed</p>';
  396. $block['#suffix'] = '<span class="block-suffix">Block suffix</span></div>';
  397. }
  398. }
  399. }
  400. }
  401. /**
  402. * Utility function to build a named form given a set of form elements.
  403. *
  404. * This is a standard form builder function that takes an additional array,
  405. * which is itself a form.
  406. *
  407. * @param array $form
  408. * Form API form array.
  409. * @param array $form_state
  410. * Form API form state array.
  411. * @param array $form_items
  412. * The form items to be included in this form.
  413. */
  414. function render_example_embedded_form($form, &$form_state, $form_items) {
  415. return $form_items;
  416. }
  417. /**
  418. * Implements hook_theme().
  419. */
  420. function render_example_theme() {
  421. $items = array(
  422. 'render_example_add_div' => array(
  423. 'render element' => 'element',
  424. ),
  425. 'render_example_add_notes' => array(
  426. 'render element' => 'element',
  427. ),
  428. 'render_array' => array(
  429. 'render element' => 'element',
  430. ),
  431. 'render_example_aggregate' => array(
  432. 'render element' => 'element',
  433. ),
  434. );
  435. return $items;
  436. }
  437. /**
  438. * Wraps a div around the already-rendered #children.
  439. */
  440. function theme_render_example_add_div($variables) {
  441. $element = $variables['element'];
  442. $output = '<div class="render-example-wrapper-div">';
  443. $output .= $element['#children'];
  444. $output .= '</div>';
  445. return $output;
  446. }
  447. /**
  448. * Wraps a div and add a little text after the rendered #children.
  449. */
  450. function theme_render_example_add_notes($variables) {
  451. $element = $variables['element'];
  452. $output = '<div class="render-example-notes-wrapper-div">';
  453. $output .= $element['#children'];
  454. $output .= '<em>' . t('This is a note added by a #theme_wrapper') . '</em>';
  455. $output .= '</div>';
  456. return $output;
  457. }
  458. /**
  459. * Themes the render array (from the demonstration page).
  460. */
  461. function theme_render_array($variables) {
  462. $heading = '<div class="render-header">' . $variables['element']['#description'] . '</div>';
  463. $rendered = '<div class="render-array">' . $heading . $variables['element']['#children'] . '</div>';
  464. return $rendered;
  465. }
  466. /**
  467. * Adds a #type to the element before it gets rendered.
  468. *
  469. * In this case, changes from the default 'ul' to 'ol'.
  470. *
  471. * @param array $element
  472. * The element to be altered, in this case a list, ready for theme_item_list.
  473. *
  474. * @return array
  475. * The altered list (with '#type')
  476. */
  477. function render_example_change_to_ol($element) {
  478. $element['#type'] = 'ol';
  479. return $element;
  480. }
  481. /**
  482. * Alter the rendered output after all other theming.
  483. *
  484. * This #post_render function gets to alter the rendered output after all
  485. * theme functions have acted on it, and it receives the original data, so
  486. * can make decisions based on that. In this example, no use is made of the
  487. * passed-in $element.
  488. *
  489. * @param string $markup
  490. * The already-rendered data
  491. * @param array $element
  492. * The data element that was rendered
  493. *
  494. * @return string
  495. * The altered data.
  496. */
  497. function render_example_add_hr($markup, $element) {
  498. $output = $markup . '<hr />';
  499. return $output;
  500. }
  501. /**
  502. * @} End of "defgroup render_example".
  503. */