panels_node.module 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. <?php
  2. /**
  3. * @file panels_node.module
  4. *
  5. * This module provides the "panel" node type.
  6. * Panel nodes are useful to add additional content to the content area
  7. * on a per-node base.
  8. */
  9. // ---------------------------------------------------------------------------
  10. // General Drupal hooks
  11. /**
  12. * Implementation of hook_permission().
  13. */
  14. function panels_node_permission() {
  15. return array(
  16. 'create panel-nodes' => array(
  17. 'title' => t('Create panel nodes'),
  18. 'description' => t('Create new panel nodes.'),
  19. ),
  20. 'edit any panel-nodes' => array(
  21. 'title' => t('Edit any panel-nodes'),
  22. 'description' => t('Edit all pre-existing panel nodes regardless of ownership.'),
  23. ),
  24. 'edit own panel-nodes' => array(
  25. 'title' => t('Edit own panel nodes'),
  26. 'description' => t('Edit panel nodes owned by this user.'),
  27. ),
  28. 'administer panel-nodes' => array(
  29. 'title' => t('Administer panel nodes'),
  30. 'description' => t('Full administrative access to panel nodes including create, update and delete all'),
  31. ),
  32. 'delete any panel-nodes' => array(
  33. 'title' => t('Delete any panel nodes'),
  34. 'description' => t('Delete any panel node regardless of ownership'),
  35. ),
  36. 'delete own panel-nodes' => array(
  37. 'title' => t('Delete own panel nodes'),
  38. 'description' => t('Delete any panel node owned by this user.'),
  39. ),
  40. );
  41. }
  42. /**
  43. * Implementation of hook_menu().
  44. */
  45. function panels_node_menu() {
  46. // Safety: go away if CTools is not at an appropriate version.
  47. if (!defined('PANELS_REQUIRED_CTOOLS_API') || !module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
  48. return array();
  49. }
  50. $items['admin/structure/panels/settings/panel-node'] = array(
  51. 'title' => 'Panel nodes',
  52. 'description' => 'Configure which content is available to add to panel node displays.',
  53. 'access arguments' => array('administer panel-nodes'),
  54. 'page callback' => 'panels_node_settings',
  55. 'type' => MENU_LOCAL_TASK,
  56. );
  57. // Avoid some repetition on these:
  58. $base = array(
  59. 'access callback' => 'panels_node_edit_node',
  60. 'access arguments' => array(1),
  61. 'page arguments' => array(1),
  62. 'type' => MENU_LOCAL_TASK,
  63. );
  64. $items['node/%node/panel_layout'] = array(
  65. 'title' => 'Panel layout',
  66. 'page callback' => 'panels_node_edit_layout',
  67. 'weight' => 2,
  68. ) + $base;
  69. $items['node/%node/panel_content'] = array(
  70. 'title' => 'Panel content',
  71. 'page callback' => 'panels_node_edit_content',
  72. 'weight' => 3,
  73. ) + $base;
  74. $items['node/add/panel/choose-layout'] = array(
  75. 'title' => 'Choose layout',
  76. 'access arguments' => array('create panel-nodes'),
  77. 'page callback' => 'panels_node_add',
  78. 'type' => MENU_CALLBACK,
  79. );
  80. return $items;
  81. }
  82. /**
  83. * Access callback to determine if a user has edit access
  84. */
  85. function panels_node_edit_node($node) {
  86. if (!isset($node->panels_node)) {
  87. return FALSE;
  88. }
  89. return node_access('update', $node);
  90. }
  91. /**
  92. * Override of node add page to force layout selection prior
  93. * to actually editing a node.
  94. */
  95. function panels_node_add() {
  96. $output = '';
  97. ctools_include('plugins', 'panels');
  98. ctools_include('common', 'panels');
  99. $layouts = panels_common_get_allowed_layouts('panels_node');
  100. return panels_common_print_layout_links($layouts, 'node/add/panel', array('query' => $_GET));
  101. }
  102. // ---------------------------------------------------------------------------
  103. // Node hooks
  104. /**
  105. * Implementation of hook_node_info().
  106. */
  107. function panels_node_node_info() {
  108. // Safety: go away if CTools is not at an appropriate version.
  109. if (!defined('PANELS_REQUIRED_CTOOLS_API') || !module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) {
  110. return array();
  111. }
  112. return array(
  113. 'panel' => array(
  114. 'name' => t('Panel'),
  115. // We use panels_node_hook so that panels_node private
  116. // callbacks do not get confused with panels versions of
  117. // nodeapi callbacks.
  118. 'base' => 'panels_node_hook',
  119. 'body_label' => t('Teaser'),
  120. 'description' => t("A panel layout broken up into rows and columns."),
  121. ),
  122. );
  123. }
  124. /**
  125. * Implementation of hook_access().
  126. */
  127. function panels_node_node_access($node, $op, $account) {
  128. if ($op == 'create' && $node != 'panel') {
  129. return NODE_ACCESS_IGNORE;
  130. }
  131. if (is_object($node) && $node->type != 'panel') {
  132. return NODE_ACCESS_IGNORE;
  133. }
  134. if (user_access('administer panel-nodes', $account)) {
  135. return NODE_ACCESS_ALLOW;
  136. }
  137. if ($op == 'create' && user_access('create panel-nodes', $account)) {
  138. return NODE_ACCESS_ALLOW;
  139. }
  140. if ($op == 'update' && (user_access('edit any panel-nodes', $account) || $node->uid == $account->uid && user_access('edit own panel-nodes', $account))) {
  141. return NODE_ACCESS_ALLOW;
  142. }
  143. if ($op == 'delete' && (user_access('delete any panel-nodes') || $node->uid == $account->uid && user_access('delete own panel-nodes'))) {
  144. return NODE_ACCESS_ALLOW;
  145. }
  146. }
  147. /**
  148. * Implementation of hook_form().
  149. */
  150. function panels_node_hook_form(&$node, &$form_state) {
  151. ctools_include('plugins', 'panels');
  152. $form['panels_node']['#tree'] = TRUE;
  153. if (empty($node->nid) && arg(0) == 'node' && arg(1) == 'add') {
  154. // Grab our selected layout from the $node, If it doesn't exist, try arg(3)
  155. // and if that doesn't work present them with a list to pick from.
  156. $panel_layout = isset($node->panel_layout) ? $node->panel_layout : arg(3);
  157. if (empty($panel_layout)) {
  158. $opts = $_GET;
  159. unset($opts['q']);
  160. return drupal_goto('node/add/panel/choose-layout', $opts);
  161. }
  162. $layout = panels_get_layout($panel_layout);
  163. if (empty($layout)) {
  164. return drupal_not_found();
  165. }
  166. $form['panels_node']['layout'] = array(
  167. '#type' => 'value',
  168. '#value' => $panel_layout,
  169. );
  170. }
  171. $type = node_type_get_type($node);
  172. $form['title'] = array(
  173. '#type' => 'textfield',
  174. '#title' => check_plain($type->title_label),
  175. '#required' => TRUE,
  176. '#default_value' => $node->title,
  177. );
  178. $css_id = '';
  179. if (!empty($node->panels_node['css_id'])) {
  180. $css_id = $node->panels_node['css_id'];
  181. }
  182. $form['panels_node']['css_id'] = array(
  183. '#type' => 'textfield',
  184. '#title' => t('CSS ID'),
  185. '#size' => 30,
  186. '#description' => t('An ID that can be used by CSS to style the panel.'),
  187. '#default_value' => $css_id,
  188. );
  189. // Support for different rendering pipelines
  190. // Mostly borrowed from panel_context.inc
  191. $pipelines = panels_get_renderer_pipelines();
  192. $options = array();
  193. foreach ($pipelines as $name => $pipeline) {
  194. $options[$name] = check_plain($pipeline->admin_title) . '<div class="description">' . check_plain($pipeline->admin_description) . '</div>';
  195. }
  196. $form['panels_node']['pipeline'] = array(
  197. '#type' => 'radios',
  198. '#options' => $options,
  199. '#title' => t('Renderer'),
  200. '#default_value' => isset($node->panels_node['pipeline']) ? $node->panels_node['pipeline'] : 'standard',
  201. );
  202. return $form;
  203. }
  204. /**
  205. * Implementation of hook_validate().
  206. */
  207. function panels_node_hook_validate($node, $form, &$form_state) {
  208. if (!$node->nid && empty($node->panels_node['layout'])) {
  209. form_error($form['panels_node']['layout'], t('Please select a layout.'));
  210. }
  211. }
  212. /**
  213. * Implementation of hook_load().
  214. *
  215. * Panels does not use revisions for nodes because that would open us up
  216. * to have completely separate displays, and we'd have to copy them,
  217. * and that's going to be a LOT of data.
  218. */
  219. function panels_node_hook_load($nodes) {
  220. // We shortcut this because only in some really drastic corruption circumstance will this
  221. // not work.
  222. $result = db_query("SELECT * FROM {panels_node} WHERE nid IN (:nids)", array(':nids' => array_keys($nodes)));
  223. foreach ($result as $record) {
  224. $nodes[$record->nid]->panels_node = (array) $record;
  225. }
  226. }
  227. /**
  228. * Implementation of hook_insert().
  229. */
  230. function panels_node_hook_insert(&$node) {
  231. // Create a new display and record that.
  232. $display = panels_new_display();
  233. $display->layout = $node->panels_node['layout'];
  234. // Special handling for nodes being imported from an export.module data dump.
  235. if (!empty($node->export_display)) {
  236. // This works by overriding the $display set above
  237. eval($node->export_display);
  238. unset($node->export_display);
  239. }
  240. panels_save_display($display);
  241. $node->panels_node['did'] = $display->did;
  242. db_insert('panels_node')
  243. ->fields(array(
  244. 'nid' => $node->nid,
  245. 'did' => $display->did,
  246. 'css_id' => $node->panels_node['css_id'],
  247. 'pipeline' => $node->panels_node['pipeline'],
  248. ))
  249. ->execute();
  250. }
  251. /**
  252. * Implementation of hook_delete().
  253. */
  254. function panels_node_hook_delete(&$node) {
  255. db_delete('panels_node')->condition('nid', $node->nid)->execute();
  256. if (!empty($node->panels_node['did'])) {
  257. panels_delete_display($node->panels_node['did']);
  258. }
  259. }
  260. /**
  261. * Implementation of hook_update().
  262. */
  263. function panels_node_hook_update($node) {
  264. db_update('panels_node')
  265. ->condition('nid', $node->nid)
  266. ->fields(array(
  267. 'css_id' => $node->panels_node['css_id'],
  268. 'pipeline' => $node->panels_node['pipeline'],
  269. ))
  270. ->execute();
  271. }
  272. /**
  273. * Implementation of hook_view().
  274. */
  275. function panels_node_hook_view($node, $view_mode) {
  276. static $rendering = array();
  277. // Prevent loops if someone foolishly puts the node inside itself:
  278. if (!empty($rendering[$node->nid])) {
  279. return $node;
  280. }
  281. $rendering[$node->nid] = TRUE;
  282. ctools_include('plugins', 'panels');
  283. if ($view_mode == 'teaser') {
  284. // Because our teasier is never the same as our content, *always* provide
  285. // the read more flag.
  286. $node->readmore = TRUE;
  287. }
  288. else {
  289. if (!empty($node->panels_node['did'])) {
  290. $display = panels_load_display($node->panels_node['did']);
  291. $display->css_id = $node->panels_node['css_id'];
  292. // TODO: Find a way to make sure this can't node_view.
  293. $display->context = panels_node_get_context($node);
  294. $display->cache_key = 'panels_node:' . $node->nid;
  295. $renderer = panels_get_renderer($node->panels_node['pipeline'], $display);
  296. $node->content['body'] = array(
  297. '#markup' => panels_render_display($display, $renderer),
  298. '#weight' => 0,
  299. );
  300. }
  301. }
  302. unset($rendering[$node->nid]);
  303. return $node;
  304. }
  305. // ---------------------------------------------------------------------------
  306. // Administrative pages
  307. /**
  308. * Settings for panel nodes.
  309. */
  310. function panels_node_settings() {
  311. ctools_include('common', 'panels');
  312. return drupal_get_form('panels_common_settings', 'panels_node');
  313. }
  314. // ---------------------------------------------------------------------------
  315. // Meat of the Panels API; almost completely passing through to panels.module
  316. /**
  317. * Pass through to the panels layout editor.
  318. */
  319. function panels_node_edit_layout($node) {
  320. // ctools_include('plugins', 'panels');
  321. ctools_include('context');
  322. $display = panels_load_display($node->panels_node['did']);
  323. $display->context = panels_node_get_context($node);
  324. return panels_edit_layout($display, t('Save'), "node/$node->nid/panel_layout", 'panels_node');
  325. }
  326. /**
  327. * Pass through to the panels content editor.
  328. */
  329. function panels_node_edit_content($node) {
  330. ctools_include('context');
  331. $display = panels_load_display($node->panels_node['did']);
  332. $display->context = panels_node_get_context($node);
  333. ctools_include('common', 'panels');
  334. $content_types = panels_common_get_allowed_types('panels_node', $display->context);
  335. return panels_edit($display, "node/$node->nid/panel_content", $content_types);
  336. }
  337. /**
  338. * Build the context to use for a panel node.
  339. */
  340. function panels_node_get_context(&$node) {
  341. ctools_include('context');
  342. $context = ctools_context_create('node', $node);
  343. $context->identifier = t('This node');
  344. $context->keyword = 'node';
  345. return array('panel-node' => $context);
  346. }
  347. /**
  348. * Implementation of hook_export_node_alter()
  349. *
  350. * Integrate with export.module for saving panel_nodes into code.
  351. */
  352. function panels_node_export_node_alter(&$node, $original_node, $method) {
  353. if ($method == 'export') {
  354. $node_export_omitted = variable_get('node_export_omitted', array());
  355. if (variable_get('node_export_method', '') != 'save-edit' && (array_key_exists('panel', $node_export_omitted) && !$node_export_omitted['panel'])) {
  356. drupal_set_message(t("NOTE: in order to import panel_nodes you must first set the export.module settings to \"Save as a new node then edit\", otherwise it won't work."));
  357. }
  358. $display = panels_load_display($node->panels_node['did']);
  359. $export = panels_export_display($display);
  360. $node->export_display = $export;
  361. }
  362. }
  363. /**
  364. * Implementation of hook_panels_dashboard_blocks().
  365. *
  366. * Adds panel nodes information to the Panels dashboard.
  367. */
  368. function panels_node_panels_dashboard_blocks(&$vars) {
  369. $vars['links']['panels_node'] = array(
  370. 'title' => l(t('Panel node'), 'node/add/panel'),
  371. 'description' => t('Panel nodes are node content and appear in your searches, but are more limited than panel pages.'),
  372. 'weight' => -1,
  373. );
  374. }
  375. // ---------------------------------------------------------------------------
  376. // Callbacks for panel caching.
  377. /**
  378. * Get display edit cache for a panel node being edited.
  379. *
  380. * The key is the second half of the key in this form:
  381. * panels_node:NID;
  382. */
  383. function panels_node_panels_cache_get($nid) {
  384. ctools_include('object-cache');
  385. $cache = ctools_object_cache_get('panels_node_display_cache', $nid);
  386. if (empty($cache)) {
  387. $cache = new stdClass();
  388. $node = node_load($nid);
  389. if (empty($node)) {
  390. return;
  391. }
  392. ctools_include('common', 'panels');
  393. $cache->display = panels_load_display($node->panels_node['did']);
  394. $cache->display->css_id = $node->panels_node['css_id'];
  395. $cache->display->context = panels_node_get_context($node);
  396. $cache->display->cache_key = 'panels_node:' . $node->nid;
  397. $cache->content_types = panels_common_get_allowed_types('panels_node', $cache->display->context);
  398. $cache->allwed_layouts = panels_common_get_allowed_layouts('panels_node');
  399. }
  400. return $cache;
  401. }
  402. /**
  403. * Store a display edit in progress in the panels cache.
  404. */
  405. function panels_node_panels_cache_set($nid, $cache) {
  406. ctools_include('object-cache');
  407. ctools_object_cache_set('panels_node_display_cache', $nid, $cache);
  408. }
  409. /**
  410. * Clear all changes made to a display using the panels cache.
  411. */
  412. function panels_node_panels_cache_clear($nid, $cache) {
  413. ctools_include('object-cache');
  414. ctools_object_cache_clear('panels_node_display_cache', $nid);
  415. }
  416. /**
  417. * React to a cache save and save the display and clear cache.
  418. */
  419. function panels_node_panels_cache_save($nid, $cache) {
  420. panels_save_display($cache->display);
  421. ctools_include('object-cache');
  422. ctools_object_cache_clear('panels_node_display_cache', $nid);
  423. }