tca.module 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. <?php
  2. /**
  3. * @file
  4. * The Token Content Access module file.
  5. *
  6. * This is a module that will prevent users from viewing an entity page w/o
  7. * providing an access token via the URL.
  8. */
  9. /**
  10. * Implements hook_schema_alter().
  11. */
  12. function tca_schema_alter(&$schema) {
  13. // Get the Token Content Access fields.
  14. $fields = tca_schema_fields();
  15. // Get the modules that are implementing hook_tca(),
  16. // and add the fields to the base table of that entity.
  17. $modules = module_invoke_all('tca');
  18. foreach ($modules as $module => $info) {
  19. $schema[$info['base table']]['fields'] += $fields;
  20. }
  21. }
  22. /**
  23. * Helper function that defines the Token Content Access database fields.
  24. *
  25. * @return array
  26. * An array with the field specifications, keyed by the field name.
  27. */
  28. function tca_schema_fields() {
  29. $fields = array(
  30. 'tca_active' => array(
  31. 'description' => 'Specifies whether or not Token Content Access is active.',
  32. 'type' => 'int',
  33. 'default' => NULL,
  34. ),
  35. 'tca_token' => array(
  36. 'description' => 'The tca token value.',
  37. 'type' => 'text',
  38. 'default' => NULL,
  39. 'size' => 'normal',
  40. ),
  41. );
  42. return $fields;
  43. }
  44. /**
  45. * Implements hook_modules_enabled().
  46. */
  47. function tca_modules_enabled($modules) {
  48. foreach ($modules as $module) {
  49. // If this module implements hook_tca(), we should add the Token Content
  50. // Access fields to the base table of the entity type that this module is
  51. // altering.
  52. $function = $module . '_tca';
  53. if (function_exists($function)) {
  54. // Gather info about the module, entity and get the Token Content Access
  55. // fields.
  56. $tca_info = $function();
  57. $entity_info = entity_get_info($tca_info[$module]['entity type']);
  58. $fields = tca_schema_fields();
  59. // Add each field to the base table for the entity.
  60. foreach ($fields as $name => $spec) {
  61. if (!db_field_exists($entity_info['base table'], $name)) {
  62. db_add_field($entity_info['base table'], $name, $spec);
  63. }
  64. }
  65. }
  66. }
  67. }
  68. /**
  69. * Implements hook_modules_uninstalled().
  70. */
  71. function tca_modules_uninstalled($modules) {
  72. foreach ($modules as $module) {
  73. // If this module implements hook_tca(), we should remove the Token Content
  74. // Access fields from the base table of the entity type that this module is
  75. // altering. We need to include the .module file manually, since the module
  76. // hsa been uninstalled and therefore, isn't reachable the normal way.
  77. require_once DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . '/' . $module . '.module';
  78. $function = $module . '_tca';
  79. if (function_exists($function)) {
  80. // Gather info about the module, entity and get the Token Content Access
  81. // fields.
  82. $tca_info = $function();
  83. $entity_info = entity_get_info($tca_info[$module]['entity type']);
  84. $fields = tca_schema_fields();
  85. // Remove each field from the base table for the entity.
  86. foreach ($fields as $name => $spec) {
  87. if (db_field_exists($entity_info['base table'], $name)) {
  88. db_drop_field($entity_info['base table'], $name);
  89. }
  90. }
  91. // Delete any variables that are set for this entity.
  92. foreach ($entity_info['bundles'] as $bundle => $info) {
  93. tca_delete_variables($tca_info[$module]['entity type'], $bundle);
  94. }
  95. }
  96. }
  97. }
  98. /**
  99. * Implements hook_permission().
  100. */
  101. function tca_permission() {
  102. $permissions = array();
  103. // Load information from any module that implements hook_tca().
  104. $modules = module_invoke_all('tca');
  105. foreach ($modules as $module => $info) {
  106. // Get information about the entity.
  107. $entity_info = entity_get_info($info['entity type']);
  108. $entity_label = strtolower(isset($entity_info['plural label']) ? $entity_info['plural label'] : $entity_info['label']);
  109. // Add an administer permission.
  110. $permissions['administer ' . $module] = array(
  111. 'title' => t('Administer Token Content Access settings for @entity_type', array('@entity_type' => $entity_label)),
  112. );
  113. // Add a bypass permission.
  114. $permissions['bypass ' . $module] = array(
  115. 'title' => t('Bypass Token Content Access action for @entity_type', array('@entity_type' => $entity_label)),
  116. 'description' => t('Allows user to bypass the action that has been configured for @entity_type.', array('@entity_type' => $entity_label)),
  117. 'restrict access' => TRUE,
  118. );
  119. }
  120. return $permissions;
  121. }
  122. /**
  123. * Implements hook_menu_local_tasks_alter().
  124. */
  125. function tca_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  126. $primary = &$data['tabs'][0];
  127. if (!is_array($primary['output'])) {
  128. // There are no tabs present, exit early.
  129. return;
  130. }
  131. // Get the modules that implements hook_tca().
  132. $modules = module_invoke_all('tca');
  133. // Iterate through the primary tabs, and look for the View tab for any entity
  134. // that is handled by Token Content Access.
  135. foreach ($primary['output'] as $delta => $element) {
  136. // If path is not set on this item, just continue to the next item.
  137. if (!isset($element['#link']['path'])) {
  138. continue;
  139. }
  140. foreach ($modules as $module => $info) {
  141. if ($element['#link']['path'] == $info['view path']) {
  142. // Found the View tab, get the Token Content Access action for this
  143. // entity, and remove the tab if any Token Content Access action has
  144. // been set.
  145. $entity_position = array_search('%', explode('/', $info['view path']));
  146. $entity = menu_get_object($info['entity type'], $entity_position, $router_item['tab_root_href']);
  147. if (isset($entity)) {
  148. if (tca_get_active_entity($info['entity type'], $entity) && !user_access('bypass ' . $module)) {
  149. unset($primary['output'][$delta]);
  150. }
  151. }
  152. }
  153. }
  154. }
  155. // Reset the count and keys for the existing tabs.
  156. $primary['output'] = array_values($primary['output']);
  157. $primary['count'] = count($primary['output']);
  158. }
  159. /**
  160. * Form structure for the Token Content Access configuration.
  161. *
  162. * This should be used by other modules that wish to implement the Token Content
  163. * Access configurations in any form.
  164. *
  165. * @param array $attach
  166. * The form that the Token Content Access form should be attached to.
  167. * @param string $entity_type
  168. * The entity type that we're adding the form for, e.g. 'node'.
  169. * @param string $bundle
  170. * The bundle that we're adding the form to, e.g. the content type for nodes.
  171. * This might be an empty string if we're creating a new bundle.
  172. * @param string $module
  173. * The name of the module that invokes this function.
  174. * @param object $entity
  175. * The entity that we're adding the form to, e.g. a node. This will be NULL if
  176. * the form is being attached to the bundle configuration form.
  177. */
  178. function tca_form(array &$attach, $form_state, $entity_type, $bundle, $module, $entity = NULL) {
  179. if (!user_access('administer ' . $module)) {
  180. // The user doesn't have access, exit.
  181. return;
  182. }
  183. if (isset($entity) && !tca_get_active_bundle($entity_type, $bundle)) {
  184. // The form is about to be attached to an entity, but the bundle isn't
  185. // allowed to use Token Content Access, exit.
  186. return;
  187. }
  188. // Get information about the entity.
  189. $entity_info = entity_get_info($entity_type);
  190. $entity_label = strtolower(isset($entity_info['plural label']) ? $entity_info['plural label'] : $entity_info['label']);
  191. // Get the label for the bundle. This won't be set when the user is creating a
  192. // new bundle. In that case, fallback to "this bundle".
  193. $bundle_label = isset($entity_info['bundles'][$bundle]['label']) ? $entity_info['bundles'][$bundle]['label'] : 'this bundle';
  194. // Wrap everything in a fieldset.
  195. $form['tca'] = array(
  196. '#type' => 'fieldset',
  197. '#title' => t('Token Content Access settings'),
  198. '#collapsed' => FALSE,
  199. '#collapsible' => TRUE,
  200. '#group' => 'additional_settings',
  201. '#attributes' => array('class' => array('tca-settings-form')),
  202. );
  203. // Add the invoking module to the internal values.
  204. $form['tca']['tca_module'] = array(
  205. '#type' => 'value',
  206. '#value' => $module,
  207. );
  208. // Add the entity type to the internal values.
  209. $form['tca']['tca_entity_type'] = array(
  210. '#type' => 'value',
  211. '#value' => $entity_type,
  212. );
  213. // Add override setting if we're editing a bundle.
  214. if (!isset($entity)) {
  215. $form['tca']['tca_' . $entity_type . '_active'] = array(
  216. '#type' => 'checkbox',
  217. '#title' => t('Enable Token Content Access protection.'),
  218. '#default_value' => tca_get_active_bundle($entity_type, $bundle),
  219. '#description' => t('If this is checked, users with the %permission permission will be able to enable Token Content Access protection for individual entities.', array('%permission' => t('Administer Token Content Access settings for @entity_type', array('@entity_type' => $entity_label)))),
  220. );
  221. }
  222. // Add Token activation if we're editing a new or existing entity.
  223. if (isset($entity)) {
  224. // Add activation settings.
  225. $form['tca']['tca_active'] = array(
  226. '#type' => 'checkbox',
  227. '#title' => t('Enable Token Content Access protection.'),
  228. '#default_value' => isset($entity) ? tca_get_active_entity($entity_type, $entity) : FALSE,
  229. '#description' => t('Prevent users from viewing an this @bundle page w/o providing an access token via the URL.', array('@bundle' => strtolower(isset($entity_info['plural label']) ? $entity_info['plural label'] : $bundle_label))),
  230. '#attributes' => array('class' => array('tca-active-setting')),
  231. );
  232. // Only populate token information if we are editing an existing entity.
  233. list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity);
  234. if ($entity_id) {
  235. // Wrap the token settings in a fieldset.
  236. $form['tca']['token'] = array(
  237. '#prefix' => '<div id="tca-token-form-entity">',
  238. '#suffix' => '</div>',
  239. '#type' => 'fieldset',
  240. '#title' => t('Token settings'),
  241. '#description' => t('Token settings'),
  242. '#attributes' => array('class' => array('tca-redirect-options')),
  243. '#states' => array(
  244. 'visible' => array(
  245. ':input[name="tca_active"]' => array('checked' => TRUE),
  246. ),
  247. ),
  248. );
  249. if (!isset($form_state['values']['tca_token'])) {
  250. $token = tca_get_token_entity($entity_type, $entity);
  251. $token = !empty($token)
  252. ? tca_get_token_entity($entity_type, $entity)
  253. : tca_get_token($entity_type, $entity, $value = '');
  254. }
  255. else {
  256. $token = $form_state['values']['tca_token'];
  257. }
  258. $entity_uri = (entity_uri($entity_type, $entity));
  259. $description = array();
  260. $description[] = t('Append this access token to the URL as the value for the "tca" query parameter, for example:');
  261. $description[] = '<pre>' . url($entity_uri['path'], array('absolute' => TRUE, 'query' => array('tca' => $token))) . '</pre>';
  262. $form['tca']['token']['tca_token'] = array(
  263. '#type' => 'textfield',
  264. '#title' => t('Access Token'),
  265. '#default_value' => $token,
  266. '#description' => '<p>' . implode('</p><p>', $description) . '</p>',
  267. '#disabled' => TRUE,
  268. '#attributes' => array(
  269. 'class' => array('tca-access-token'),
  270. ),
  271. );
  272. // Add replace token functionality.
  273. $form['tca']['tca_token_replace'] = array(
  274. '#type' => 'submit',
  275. '#value' => t('Regenerate Token'),
  276. '#description' => t('Generate a new token.'),
  277. '#submit' => array('tca_token_replace'),
  278. '#ajax' => array(
  279. 'callback' => 'tca_token_replace_callback',
  280. 'wrapper' => 'tca-token-form-entity',
  281. ),
  282. );
  283. }
  284. }
  285. // Attach the Token Content Access form to the main form, and add a custom
  286. // validation callback.
  287. $attach += $form;
  288. $attach['#validate'][] = 'tca_form_validate';
  289. // If the implementing module provides a submit function for the bundle form,
  290. // we'll add it as a submit function for the attached form. We'll also make
  291. // sure that this won't be added for entity forms.
  292. $submit_function = $module . '_bundle_form_submit';
  293. if (function_exists($submit_function) && !isset($entity)) {
  294. $attach['#submit'][] = $submit_function;
  295. }
  296. }
  297. /**
  298. * Callback for "Regenerate Token" button.
  299. *
  300. * Selects and returns the fieldset with the token up in it.
  301. */
  302. function tca_token_replace_callback(&$form, &$form_state) {
  303. return $form['tca']['token'];
  304. }
  305. /**
  306. * Submit handler for the "Regenerate Token" button.
  307. */
  308. function tca_token_replace($form, &$form_state) {
  309. $entity_type = $form_state['values']['tca_entity_type'];
  310. $entity = $form_state[$entity_type];
  311. $form_state['values']['tca_token'] = tca_get_token($entity_type, $entity, $value = '');
  312. $form_state['rebuild'] = TRUE;
  313. }
  314. /**
  315. * Validation callback for the Token Content Access form.
  316. */
  317. function tca_form_validate($form, &$form_state) {
  318. }
  319. /**
  320. * Gets the Token Content Access active state for a bundle.
  321. *
  322. * @param string $entity_type
  323. * The entity type of the bundle.
  324. * @param string $bundle
  325. * The bundle that we're checking.
  326. *
  327. * @return bool
  328. * TRUE or FALSE depending on whether or not Token Content Access
  329. * is allowed for this bundle
  330. */
  331. function tca_get_active_bundle($entity_type, $bundle) {
  332. return variable_get('tca_' . $entity_type . '_active_' . $bundle, FALSE);
  333. }
  334. /**
  335. * Gets the Token Content Access active state for an entity.
  336. *
  337. * @param string $entity_type
  338. * The entity type of the entity.
  339. * @param object $entity
  340. * The entity that we're checking.
  341. *
  342. * @return int
  343. * TRUE or FALSE depending on whether or not Token Content Access
  344. * is allowed for this entity
  345. */
  346. function tca_get_active_entity($entity_type, $entity) {
  347. return isset($entity->tca_active) ? $entity->tca_active : FALSE;
  348. }
  349. /**
  350. * Gets the Token Content Access token for an entity.
  351. *
  352. * @param string $entity_type
  353. * The entity type of the entity.
  354. * @param object $entity
  355. * The entity that we're checking.
  356. *
  357. * @return string
  358. * token value for this entity or FALSE if not set
  359. */
  360. function tca_get_token_entity($entity_type, $entity) {
  361. return isset($entity->tca_token) ? $entity->tca_token : FALSE;
  362. }
  363. /**
  364. * Gets the bundle of an entity.
  365. *
  366. * @param string $entity_type
  367. * The entity type for the entity.
  368. * @param object $entity
  369. * The entity that we're checking.
  370. *
  371. * @return string
  372. * The machine name for the bundle.
  373. */
  374. function tca_entity_get_bundle($entity_type, $entity) {
  375. $entity_info = entity_get_info($entity_type);
  376. // Use the bundle key to get the bundle for this entity if the bundle key has
  377. // been defined. If it hasn't, it means that this entity only provides one
  378. // bundle, and that bundle will always have the same name as the entoty type.
  379. // E.g, the user entity is built by one bundle, and that bundle is also called
  380. // user.
  381. //
  382. // @see hook_entity_info()
  383. return !empty($entity_info['entity keys']['bundle']) ? $entity->{$entity_info['entity keys']['bundle']} : $entity_type;
  384. }
  385. /**
  386. * Deletes variables associated with an entity type and bundle.
  387. *
  388. * This should be executed when a module in uninstalled or a bundle is deleted.
  389. */
  390. function tca_delete_variables($entity_type, $bundle) {
  391. variable_del('tca_' . $entity_type . '_active_' . $bundle);
  392. }
  393. /**
  394. * Determines the action that should be executed.
  395. *
  396. * This will actually execute the action, and should be used when the entity
  397. * is being viewed.
  398. *
  399. * @param string $entity_type
  400. * The entity type that's being viewed, e.g. 'node'.
  401. * @param object $entity
  402. * The entity that is being viewed.
  403. */
  404. function tca_execute($entity_type, $entity) {
  405. // Make sure Token Content Access is enabled for this entity.
  406. if (tca_get_active_entity($entity_type, $entity)) {
  407. // Get query parameters.
  408. $query = drupal_get_query_parameters();
  409. // Get the token value for this entity.
  410. $token = tca_get_token_entity($entity_type, $entity);
  411. // Check to see that a token was provided via the URL and that this node has
  412. // a token set and check to see if these values match.
  413. if (isset($query['tca']) && !empty($token) && $query['tca'] == $token) {
  414. // Proceed with viewing the entity.
  415. return;
  416. }
  417. else {
  418. // Deliver a 403, and exit.
  419. drupal_access_denied();
  420. drupal_exit();
  421. }
  422. }
  423. }
  424. /**
  425. * Generates and Token Content Access Token access token for use in URLs.
  426. *
  427. * This is a modified version of drupal_get_token that uses a combination
  428. * of the entity id, current revision id, bundle.
  429. *
  430. * @param string $entity_type
  431. * The entity type; e.g. 'node' or 'user'.
  432. * @param object $entity
  433. * The entity from which to extract values.
  434. * @param mixed $value
  435. * An additional value to base the token on.
  436. *
  437. * @return mixed
  438. * string A 43-character URL-safe token for validation, based on the
  439. * properties from an entity, the current time, the hash salt provided from
  440. * drupal_get_hash_salt(), and the 'drupal_private_key' configuration
  441. * variable. Else, return FALSE.
  442. */
  443. function tca_get_token($entity_type, $entity, $value = '') {
  444. list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity);
  445. if (!empty($entity_id)) {
  446. return drupal_hmac_base64($value, $entity_id . $revision_id . $entity_type . microtime() . drupal_get_private_key() . drupal_get_hash_salt());
  447. }
  448. else {
  449. return FALSE;
  450. }
  451. }