publishcontent.module 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. <?php
  2. /**
  3. * @file
  4. * Add link to publish or unpublish a node, with access control based on the
  5. * node type
  6. */
  7. define('PUBLISHCONTENT_METHOD_NONE', 0);
  8. define('PUBLISHCONTENT_METHOD_ACTION_LINKS', 1);
  9. define('PUBLISHCONTENT_METHOD_BUTTON', 2);
  10. define('PUBLISHCONTENT_METHOD_TABS', 3);
  11. define('PUBLISHCONTENT_ACCESS_ALLOW', TRUE);
  12. define('PUBLISHCONTENT_ACCESS_DENY', FALSE);
  13. define('PUBLISHCONTENT_ACCESS_IGNORE', NULL);
  14. /**
  15. * Implements hook_menu().
  16. */
  17. function publishcontent_menu() {
  18. $items = array();
  19. $menu_type = _publishcontent_get_menutype();
  20. $items['admin/config/content/publishcontent'] = array(
  21. 'title' => 'Publish content settings',
  22. 'page callback' => 'drupal_get_form',
  23. 'page arguments' => array('publishcontent_config_form'),
  24. 'access callback' => 'user_access',
  25. 'access arguments' => array('administer site configuration'),
  26. 'description' => 'Configure settings.',
  27. 'file' => 'publishcontent.admin.inc',
  28. 'type' => MENU_NORMAL_ITEM,
  29. );
  30. $items['node/%publishcontent_tab/publish/%publishcontent_security_token'] = array(
  31. 'title' => 'Publish',
  32. 'page callback' => 'publishcontent_toggle_status',
  33. 'page arguments' => array(1),
  34. 'access callback' => '_publishcontent_publish_access',
  35. 'access arguments' => array(1, 3),
  36. 'weight' => 5,
  37. 'type' => $menu_type,
  38. 'options' => array('attributes' => array('class' => array('publishcontent-link', 'publishcontent-publish'))),
  39. 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  40. );
  41. $items['node/%publishcontent_tab/unpublish/%publishcontent_security_token'] = array(
  42. 'title' => 'Unpublish',
  43. 'page callback' => 'publishcontent_toggle_status',
  44. 'page arguments' => array(1),
  45. 'access callback' => '_publishcontent_unpublish_access',
  46. 'access arguments' => array(1, 3),
  47. 'weight' => 5,
  48. 'type' => $menu_type,
  49. 'options' => array('attributes' => array('class' => array('publishcontent-link', 'publishcontent-unpublish'))),
  50. 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  51. );
  52. return $items;
  53. }
  54. /**
  55. * Decide to show the (un)publish tab or not.
  56. */
  57. function publishcontent_tab_load($nid) {
  58. if (is_numeric($nid)) {
  59. $node = node_load($nid);
  60. if (variable_get('publishcontent_' . $node->type, FALSE)) {
  61. return $node;
  62. }
  63. }
  64. return FALSE;
  65. }
  66. /**
  67. * Used to append a security token to prevent XSS.
  68. */
  69. function publishcontent_security_token_to_arg($arg, $map, $index) {
  70. return drupal_get_token();
  71. }
  72. /**
  73. * Access callback for publish action.
  74. */
  75. function _publishcontent_publish_access($node, $token = FALSE) {
  76. if (!is_object($node)) {
  77. return FALSE;
  78. }
  79. if ($token && !drupal_valid_token($token)) {
  80. return FALSE;
  81. }
  82. if (!variable_get('publishcontent_' . $node->type, FALSE)) {
  83. return FALSE;
  84. }
  85. return !$node->status && publishcontent_publish_access($node);
  86. }
  87. /**
  88. * Access callback for unpublish action.
  89. */
  90. function _publishcontent_unpublish_access($node, $token = FALSE) {
  91. if (!is_object($node)) {
  92. return FALSE;
  93. }
  94. if ($token && !drupal_valid_token($token)) {
  95. return FALSE;
  96. }
  97. if (!variable_get('publishcontent_' . $node->type, FALSE)) {
  98. return FALSE;
  99. }
  100. return $node->status && publishcontent_unpublish_access($node);
  101. }
  102. /**
  103. * Determine if a user has publish permission to a given node.
  104. *
  105. * @param node $node
  106. * The node object to check.
  107. * @param user $account
  108. * The user account to check - defaults to the logged in user.
  109. *
  110. * @return bool
  111. * TRUE if user can publish the node.
  112. */
  113. function publishcontent_publish_access($node, $account = NULL) {
  114. $access = FALSE;
  115. // Variable may be '0' or '1' or not set.
  116. if (!variable_get('publishcontent_' . $node->type, FALSE)) {
  117. return $access;
  118. }
  119. if (empty($account)) {
  120. global $user;
  121. $account = $user;
  122. }
  123. foreach (module_invoke_all('publishcontent_publish_access', $node, $account) as $module_access) {
  124. if (!is_null($module_access)) {
  125. if ($module_access === PUBLISHCONTENT_ACCESS_DENY) {
  126. // Anything denying access gets priority.
  127. return FALSE;
  128. }
  129. elseif ($module_access === PUBLISHCONTENT_ACCESS_ALLOW) {
  130. // Something grants access.
  131. $access = TRUE;
  132. }
  133. }
  134. }
  135. return $access;
  136. }
  137. /**
  138. * Implements hook_publishcontent_publish_access().
  139. */
  140. function publishcontent_publishcontent_publish_access($node, $user) {
  141. $access = (user_access('administer nodes')
  142. || user_access('publish any content')
  143. || (user_access('publish own content') && $user->uid == $node->uid)
  144. || (user_access('publish editable content') && (!isset($node->nid) || node_access('update', $node)))
  145. || (user_access('publish own ' . check_plain($node->type) . ' content', $user) && $user->uid == $node->uid)
  146. || user_access('publish any ' . check_plain($node->type) . ' content')
  147. || (user_access('publish editable ' . check_plain($node->type) . ' content') && (!isset($node->nid) || node_access('update', $node)))
  148. );
  149. return $access ? PUBLISHCONTENT_ACCESS_ALLOW : PUBLISHCONTENT_ACCESS_IGNORE;
  150. }
  151. /**
  152. * Determine if a user has unpublish rights on a node.
  153. *
  154. * @param node $node
  155. * The node object to check against
  156. * @param user $account
  157. * The user account object to check. Defaults to current user.
  158. *
  159. * @return bool
  160. * TRUE if the user has unpublish rights to the node.
  161. */
  162. function publishcontent_unpublish_access($node, $account = NULL) {
  163. if (empty($account)) {
  164. global $user;
  165. $account = $user;
  166. }
  167. $access = FALSE;
  168. foreach (module_invoke_all('publishcontent_unpublish_access', $node, $account) as $module_access) {
  169. if (!is_null($module_access)) {
  170. if ($module_access === PUBLISHCONTENT_ACCESS_DENY) {
  171. // Anything denying access gets priority.
  172. return FALSE;
  173. }
  174. elseif ($module_access === PUBLISHCONTENT_ACCESS_ALLOW) {
  175. // Something grants access.
  176. $access = TRUE;
  177. }
  178. }
  179. }
  180. return $access;
  181. }
  182. /**
  183. * Implements hook_publishcontent_unpublish_access().
  184. */
  185. function publishcontent_publishcontent_unpublish_access($node, $user) {
  186. $access = (user_access('administer nodes')
  187. || user_access('unpublish any content')
  188. || (user_access('unpublish own content') && $user->uid == $node->uid)
  189. || (user_access('unpublish editable content') && (!isset($node->nid) || node_access('update', $node)))
  190. || (user_access('unpublish own ' . check_plain($node->type) . ' content', $user) && $user->uid == $node->uid)
  191. || user_access('unpublish any ' . check_plain($node->type) . ' content')
  192. || (user_access('unpublish editable ' . check_plain($node->type) . ' content') && (!isset($node->nid) || node_access('update', $node)))
  193. );
  194. return $access ? PUBLISHCONTENT_ACCESS_ALLOW : PUBLISHCONTENT_ACCESS_IGNORE;
  195. }
  196. /**
  197. * Implements hook_permission().
  198. */
  199. function publishcontent_permission() {
  200. $perms = array(
  201. 'publish any content' => array(
  202. 'title' => t('Publish any content'),
  203. ),
  204. 'unpublish any content' => array(
  205. 'title' => t('Unpublish any content'),
  206. ),
  207. 'publish editable content' => array(
  208. 'title' => t('Publish editable content'),
  209. ),
  210. 'unpublish editable content' => array(
  211. 'title' => t('Unpublish editable content'),
  212. ),
  213. );
  214. foreach (node_type_get_types() as $type) {
  215. // Only show permissions for activated node types.
  216. if (isset($type->type) && variable_get('publishcontent_' . $type->type, FALSE)) {
  217. $perms['publish any ' . $type->type . ' content'] = array(
  218. 'title' => t('Publish any @type content', array('@type' => $type->name)));
  219. $perms['publish own ' . $type->type . ' content'] = array(
  220. 'title' => t('Publish own @type content', array('@type' => $type->name)));
  221. $perms['publish editable ' . $type->type . ' content'] = array(
  222. 'title' => t('Publish editable @type content', array('@type' => $type->name)));
  223. $perms['unpublish any ' . $type->type . ' content'] = array(
  224. 'title' => t('Unpublish any @type content', array('@type' => $type->name)));
  225. $perms['unpublish own ' . $type->type . ' content'] = array(
  226. 'title' => t('Unpublish own @type content', array('@type' => $type->name)));
  227. $perms['unpublish editable ' . $type->type . ' content'] = array(
  228. 'title' => t('Unpublish editable @type content', array('@type' => $type->name)));
  229. }
  230. }
  231. return $perms;
  232. }
  233. /**
  234. * Helper function to generate change of status message.
  235. */
  236. function _publishcontent_get_message($nid, $title, $status) {
  237. return ($status) ?
  238. t('"@title" [@nid] has been published', array('@title' => $title, '@nid' => $nid)) :
  239. t('"@title" [@nid] has been unpublished', array('@title' => $title, '@nid' => $nid));
  240. }
  241. /**
  242. * Menu callback for publish / unpublish content actions.
  243. *
  244. * @param node $node
  245. * A node object.
  246. */
  247. function publishcontent_toggle_status($node) {
  248. // XOR the current status with 1 to get the opposite value.
  249. $node->status = $node->status ^ 1;
  250. // If this content type specifies that a new revision should be created on
  251. // editing, then make sure to respect this option.
  252. $node_options = variable_get('node_options_' . $node->type, array());
  253. if (in_array('revision', $node_options)) {
  254. $node->revision = 1;
  255. }
  256. // Save the status we want to set.
  257. $status = $node->status;
  258. // Try to update the node.
  259. node_save($node);
  260. // Validate the status has changed.
  261. if ($status == $node->status) {
  262. // Everything went well.
  263. drupal_set_message(_publishcontent_get_message($node->nid, $node->title, $node->status));
  264. }
  265. else {
  266. // Prevent the user something went wrong.
  267. drupal_set_message(t('The status of the node could not be updated.'), 'error');
  268. }
  269. // Clear the page and block caches.
  270. cache_clear_all();
  271. drupal_goto($_SERVER['HTTP_REFERER']);
  272. }
  273. /**
  274. * Implements hook_form_FORM_ID_alter().
  275. *
  276. * Allow to use the 'Publishing options' on the edit/add page.
  277. */
  278. function publishcontent_form_node_type_form_alter(&$form, &$form_state, $form_id) {
  279. // Do not activate this option by default.
  280. $form['workflow']['publishcontent'] = array(
  281. '#type' => 'checkbox',
  282. '#title' => t('Enable publishcontent'),
  283. '#default_value' => variable_get('publishcontent_' . $form['#node_type']->type, FALSE),
  284. '#description' => t('Display publish or unpublish link for nodes of this type.'),
  285. );
  286. }
  287. /**
  288. * Implements hook_form_FORM_ID_alter().
  289. *
  290. * Alter the node edit forms.
  291. */
  292. function publishcontent_form_node_form_alter(&$form, &$form_state) {
  293. if ($form['#form_id'] == 'node_delete_confirm') {
  294. return;
  295. }
  296. $node = $form['#node'];
  297. if (!variable_get('publishcontent_' . $node->type, FALSE)) {
  298. // Publish content is not set or disabled for this content type.
  299. return;
  300. }
  301. if (!publishcontent_publish_access($node)) {
  302. if (!isset($node->nid)) {
  303. // Ensure users without permission to publish can't do so because of the
  304. // default setting in the content type. This setting controls the actual
  305. // value in $form_state.
  306. $form['options']['status']['#default_value'] = 0;
  307. // These won't affect $form_state but also need to be updated.
  308. $form['#node']->status = 0;
  309. $node->status = 0;
  310. }
  311. if (empty($node->status)) {
  312. // Publish content is unavailable for user without publish access.
  313. return;
  314. }
  315. }
  316. if (!empty($node->status) && !publishcontent_unpublish_access($node)) {
  317. // Publish content is unavailable for user without unpublish access.
  318. return;
  319. }
  320. if (_publishcontent_get_method() == PUBLISHCONTENT_METHOD_BUTTON) {
  321. _publishcontent_configure_publish_button($form, $form_state);
  322. }
  323. else {
  324. _publishcontent_configure_publish_checkbox($form, $form_state);
  325. }
  326. }
  327. /**
  328. * Add the publish button to the node edit form.
  329. */
  330. function _publishcontent_configure_publish_button(&$form, &$form_state) {
  331. $node = $form['#node'];
  332. if (empty($node->nid) || empty($form['actions'])) {
  333. // Don't include the publish button on node add forms.
  334. return;
  335. }
  336. // Add either the publish or unpublish buttons.
  337. $form['actions']['publish'] = empty($node->status) ?
  338. publishcontent_render_publish_button() :
  339. publishcontent_render_unpublish_button();
  340. }
  341. /**
  342. * Configure the node form to include the publish checkbox.
  343. */
  344. function _publishcontent_configure_publish_checkbox(&$form, &$form_state) {
  345. $form['options']['status']['#access'] = TRUE;
  346. if (!empty($form['options']['#access'])) {
  347. return;
  348. }
  349. else {
  350. $form['options']['#access'] = TRUE;
  351. }
  352. foreach (element_children($form['options']) as $key) {
  353. // If another form has afforded access to a particular option, do not
  354. // override that access. Otherwise, disable it.
  355. $form['options'][$key]['#access']
  356. = isset($form['options'][$key]['#access'])
  357. ? $form['options'][$key]['#access'] : FALSE;
  358. }
  359. }
  360. /**
  361. * Render publish button.
  362. */
  363. function publishcontent_render_publish_button() {
  364. return array(
  365. '#type' => 'submit',
  366. '#access' => TRUE,
  367. '#value' => t('Publish'),
  368. '#weight' => '30',
  369. '#submit' => array('_publishcontent_publish_node'),
  370. );
  371. }
  372. /**
  373. * Render unpublish button.
  374. */
  375. function publishcontent_render_unpublish_button() {
  376. return array(
  377. '#type' => 'submit',
  378. '#access' => TRUE,
  379. '#value' => t('Unpublish'),
  380. '#weight' => '30',
  381. '#submit' => array('_publishcontent_unpublish_node'),
  382. );
  383. }
  384. /**
  385. * Submit handler to publish the node.
  386. */
  387. function _publishcontent_publish_node($form, &$form_state) {
  388. // Set the node status as published. And that's it.
  389. $form_state['values']['status'] = 1;
  390. // Use the standard submit function. Do not go custom on me boy.
  391. node_form_submit($form, $form_state);
  392. }
  393. /**
  394. * Submit handler to unpublish the node.
  395. */
  396. function _publishcontent_unpublish_node($form, &$form_state) {
  397. // Set the status as unpublished. And there is no more to that.
  398. $form_state['values']['status'] = 0;
  399. // Use the standard submit function.
  400. node_form_submit($form, $form_state);
  401. }
  402. /**
  403. * Implements hook_views_api().
  404. */
  405. function publishcontent_views_api() {
  406. return array('api' => 3);
  407. }
  408. /**
  409. * Implements hook_views_data_alter().
  410. *
  411. * Add items to the node table that are relevant to publishcontent.
  412. */
  413. function publishcontent_views_data_alter(&$data) {
  414. $data['node']['publishcontent'] = array(
  415. 'title' => t('Publish link'),
  416. 'help' => t('Display a link to publish the node.'),
  417. 'field' => array(
  418. 'handler' => 'publishcontent_views_handler_field_node_link',
  419. ),
  420. );
  421. }
  422. /**
  423. * Get the configured publish content method.
  424. */
  425. function _publishcontent_get_method() {
  426. return variable_get('publishcontent_method', PUBLISHCONTENT_METHOD_TABS);
  427. }
  428. /**
  429. * Helper function for hook_menu to get the menu type for the current setup.
  430. */
  431. function _publishcontent_get_menutype() {
  432. $method = _publishcontent_get_method();
  433. $menu_type = MENU_CALLBACK;
  434. if ($method == PUBLISHCONTENT_METHOD_TABS) {
  435. $menu_type = MENU_LOCAL_TASK;
  436. }
  437. elseif ($method == PUBLISHCONTENT_METHOD_ACTION_LINKS) {
  438. $menu_type = MENU_LOCAL_ACTION;
  439. }
  440. return $menu_type;
  441. }
  442. /**
  443. * Implements hook_og_permission().
  444. */
  445. function publishcontent_og_permission() {
  446. $permissions = array();
  447. foreach (publishcontent_permission() as $name => $details) {
  448. $permissions[$name] = array(
  449. 'title' => $details['title'],
  450. 'description' => isset($details['description']) ? $details['description'] : '',
  451. 'default role' => array(OG_ADMINISTRATOR_ROLE),
  452. );
  453. }
  454. return $permissions;
  455. }
  456. /**
  457. * Implements hook_publishcontent_publish_access().
  458. *
  459. * Implement on behalf of organic groups.
  460. */
  461. function og_publishcontent_publish_access($node, $account) {
  462. $access = FALSE;
  463. foreach (og_get_entity_groups('node', $node) as $entity_type => $og_memberships) {
  464. foreach ($og_memberships as $entity_id) {
  465. $group_access = !$node->status &&
  466. (og_user_access($entity_type, $entity_id, 'administer nodes', $account)
  467. || og_user_access($entity_type, $entity_id, 'publish any content', $account)
  468. || (og_user_access($entity_type, $entity_id, 'publish own content', $account) && $account->uid == $node->uid)
  469. || (og_user_access($entity_type, $entity_id, 'publish editable content', $account) && node_access('update', $node))
  470. || (og_user_access($entity_type, $entity_id, 'publish own ' . check_plain($node->type) . ' content', $account) && $account->uid == $node->uid)
  471. || og_user_access($entity_type, $entity_id, 'publish any ' . check_plain($node->type) . ' content', $account)
  472. || (og_user_access($entity_type, $entity_id, 'publish editable ' . check_plain($node->type) . ' content', $account) && node_access('update', $node))
  473. );
  474. if ($group_access) {
  475. $access = TRUE;
  476. }
  477. }
  478. }
  479. return $access ? PUBLISHCONTENT_ACCESS_ALLOW : PUBLISHCONTENT_ACCESS_IGNORE;
  480. }
  481. /**
  482. * Implements hook_publishcontent_unpublish_access().
  483. *
  484. * Implement on behalf of organic groups.
  485. */
  486. function og_publishcontent_unpublish_access($node, $account) {
  487. $access = FALSE;
  488. foreach (og_get_entity_groups('node', $node) as $entity_type => $og_memberships) {
  489. foreach ($og_memberships as $entity_id) {
  490. $group_access = $node->status &&
  491. (og_user_access($entity_type, $entity_id, 'administer nodes', $account)
  492. || og_user_access($entity_type, $entity_id, 'unpublish any content', $account)
  493. || (og_user_access($entity_type, $entity_id, 'unpublish own content', $account) && $account->uid == $node->uid)
  494. || (og_user_access($entity_type, $entity_id, 'unpublish editable content') && node_access('update', $node))
  495. || (og_user_access($entity_type, $entity_id, 'unpublish own ' . check_plain($node->type) . ' content', $account) && $account->uid == $node->uid)
  496. || og_user_access($entity_type, $entity_id, 'unpublish any ' . check_plain($node->type) . ' content', $account)
  497. || (og_user_access($entity_type, $entity_id, 'unpublish editable ' . check_plain($node->type) . ' content', $account) && node_access('update', $node))
  498. );
  499. if ($group_access) {
  500. $access = TRUE;
  501. }
  502. }
  503. }
  504. return $access ? PUBLISHCONTENT_ACCESS_ALLOW : PUBLISHCONTENT_ACCESS_IGNORE;
  505. }