field_permissions.module 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. <?php
  2. /**
  3. * @file
  4. * This is the main script for the Field Permissions module. It merely contains
  5. * the implementation of hooks invoked by Drupal core and CCK.
  6. * All common functions are externalized into several scripts that are included
  7. * on demand to save memory consumption during normal site operation.
  8. */
  9. /**
  10. * Indicates that a field does not have any access control.
  11. */
  12. define('FIELD_PERMISSIONS_PUBLIC', 0);
  13. /**
  14. * Indicates that a field is private.
  15. *
  16. * Private fields are never displayed, and are only editable by the author (and
  17. * by site administrators with the 'access private fields' permission).
  18. */
  19. define('FIELD_PERMISSIONS_PRIVATE', 1);
  20. /**
  21. * Indicates that a field has custom permissions.
  22. */
  23. define('FIELD_PERMISSIONS_CUSTOM', 2);
  24. /**
  25. * Implements hook_help().
  26. */
  27. function field_permissions_help($path, $arg) {
  28. switch ($path) {
  29. // Main module help for the Field Permissions module.
  30. case 'admin/help#field_permissions':
  31. return '<p>' . t('Set field-level permissions to edit or view CCK fields in any node, edit field during node creation, and edit or view permissions for nodes owned by the current user.') . '</p>';
  32. // Help for the Field Permissions overview page.
  33. case 'admin/reports/fields/permissions':
  34. return '<p>' . t('Report and troubleshoot field permissions.') . '</p>';
  35. }
  36. }
  37. /**
  38. * Implements hook_menu().
  39. */
  40. function field_permissions_menu() {
  41. $items['admin/reports/fields/list'] = array(
  42. 'title' => 'List',
  43. 'type' => MENU_DEFAULT_LOCAL_TASK,
  44. 'weight' => -10,
  45. );
  46. $items['admin/reports/fields/permissions'] = array(
  47. 'title' => 'Permissions',
  48. 'description' => 'Report and troubleshoot field permissions.',
  49. 'page callback' => 'field_permissions_overview',
  50. 'access arguments' => array('administer field permissions'),
  51. 'file' => 'field_permissions.admin.inc',
  52. 'type' => MENU_LOCAL_TASK,
  53. 'weight' => 0,
  54. );
  55. return $items;
  56. }
  57. /**
  58. * Implementation of hook_permission().
  59. */
  60. function field_permissions_permission() {
  61. module_load_include('inc', 'field_permissions', 'field_permissions.admin');
  62. return _field_permissions_permission();
  63. }
  64. /**
  65. * Implements of hook_form_FORM_ID_alter().
  66. */
  67. function field_permissions_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
  68. // Injects the Field Permissions settings on the Edit field tab.
  69. form_load_include($form_state, 'inc', 'field_permissions', 'field_permissions.admin');
  70. return _field_permissions_field_settings_form_alter($form, $form_state, $form_id);
  71. }
  72. /**
  73. * Implements hook_field_permissions_userid_ENTITY_TYPE_alter().
  74. */
  75. function field_permissions_field_permissions_userid_field_collection_item_alter(&$uid, $entity) {
  76. $uid = isset($entity->hostEntity()->uid) ? $entity->hostEntity()->uid : $uid;
  77. }
  78. /**
  79. * Implementation of hook_field_access().
  80. *
  81. * @param $op
  82. * The operation to be performed. Possible values:
  83. * - 'edit'
  84. * - 'view'
  85. * @param $field
  86. * The field on which the operation is to be performed.
  87. * @param $entity_type
  88. * The type of entity; e.g. 'node' or 'user'.
  89. * @param $entity
  90. * The entity on which the operation is to be performed.
  91. * @param $account
  92. * The account to check.
  93. *
  94. * @return
  95. * FALSE if the operation is not allowed.
  96. * Note when field_access() is invoked, access is granted unless one
  97. * implementation of hook_field_access() explicitly returns FALSE.
  98. *
  99. * @see field_access()
  100. */
  101. function field_permissions_field_access($op, $field, $entity_type, $entity, $account) {
  102. // Ignore the request if permissions have not been enabled for this field.
  103. if (!isset($field['field_permissions']['type']) || $field['field_permissions']['type'] == FIELD_PERMISSIONS_PUBLIC) {
  104. return;
  105. }
  106. // If the field is private, then only the author (and administrators with the
  107. // 'access private fields' permissions) can view and edit it.
  108. elseif ($field['field_permissions']['type'] == FIELD_PERMISSIONS_PRIVATE) {
  109. if (isset($entity)) {
  110. return _field_permissions_entity_is_owned_by_account($entity, $account) || user_access('access private fields', $account);
  111. }
  112. // If the entity does not exist, we must check if there is access to any
  113. // entity; see comments in field_permissions_empty_entity_access(). In this
  114. // case that will always be true, since private fields are always editable
  115. // by their authors and in theory any user account can be the author of
  116. // some entity on the site.
  117. else {
  118. return TRUE;
  119. }
  120. }
  121. // Otherwise, check access by permission.
  122. elseif ($field['field_permissions']['type'] == FIELD_PERMISSIONS_CUSTOM) {
  123. // Allow other modules to deny access first.
  124. $result = module_invoke_all('field_permissions_custom_field_access', $op, $field, $entity_type, $entity, $account);
  125. if (in_array(FALSE, $result)) {
  126. return FALSE;
  127. }
  128. if (!isset($entity)) {
  129. return field_permissions_empty_entity_access($op, $field['field_name'], $account);
  130. }
  131. elseif ($op == 'view') {
  132. return _field_permissions_field_view_access($field['field_name'], $entity_type, $entity, $account);
  133. }
  134. elseif ($op == 'edit') {
  135. return _field_permissions_field_edit_access($field['field_name'], $entity_type, $entity, $account);
  136. }
  137. }
  138. }
  139. /**
  140. * Determines custom field permissions access when the entity is unknown.
  141. *
  142. * When a module calls field_access() without providing an entity (which the
  143. * API allows it to do), it is doing so in order to check generic access to the
  144. * field. Therefore, we should only deny access if we know that there is no
  145. * entity anywhere on the site for which the user has access to the provided
  146. * field.
  147. *
  148. * For example, Views calls field_access('view') without providing the entity,
  149. * in order to determine if the field can be included in the query itself. So
  150. * we only want to return FALSE if we know that there are no entities for which
  151. * access will be granted. Later on, Views will invoke field_access('view')
  152. * again, indirectly, when rendering the fields using field_view_field(), and
  153. * at that point the entity will be passed along so we can do our normal checks
  154. * on it.
  155. *
  156. * As another example, the FileField Sources module uses field_access('edit')
  157. * as a menu access callback for the IMCE file browser and does not pass along
  158. * the entity. So we must return TRUE here if there is any entity for which the
  159. * user is allowed to edit the field (otherwise the user would not have access
  160. * to the IMCE file browser interface when editing the fields they do have
  161. * permission to edit).
  162. *
  163. * @param $op
  164. * The operation to be performed ('view' or 'edit').
  165. * @param $field_name
  166. * The name of the field whose access is being checked.
  167. * @param $account
  168. * The user account whose access is being checked.
  169. *
  170. * @return
  171. * TRUE if access should be allowed, or FALSE if it shouln't.
  172. */
  173. function field_permissions_empty_entity_access($op, $field_name, $account) {
  174. $all_permissions['view'] = array(
  175. 'view ' . $field_name,
  176. 'view own ' . $field_name,
  177. );
  178. $all_permissions['edit'] = array(
  179. 'create ' . $field_name,
  180. 'edit ' . $field_name,
  181. 'edit own ' . $field_name,
  182. );
  183. // If there's any scenario where the user might have permission to perform
  184. // the operation on the field, return TRUE.
  185. if (isset($all_permissions[$op])) {
  186. foreach ($all_permissions[$op] as $permission) {
  187. if (user_access($permission, $account)) {
  188. return TRUE;
  189. }
  190. }
  191. }
  192. return FALSE;
  193. }
  194. /**
  195. * Implementation of hook_field_access('view').
  196. */
  197. function _field_permissions_field_view_access($field_name, $entity_type, $entity, $account) {
  198. // Check if user has access to view this field in any entity.
  199. if (user_access('view ' . $field_name, $account)) {
  200. return TRUE;
  201. }
  202. // If the user has permission to view entities that they own, return TRUE if
  203. // they own this entity or FALSE if they don't.
  204. if (user_access('view own ' . $field_name, $account)) {
  205. return _field_permissions_entity_is_owned_by_account($entity, $account);
  206. }
  207. return FALSE;
  208. }
  209. /**
  210. * Implementation of hook_field_access('edit').
  211. */
  212. function _field_permissions_field_edit_access($field_name, $entity_type, $entity, $account) {
  213. // If this is a new entity, check if the user has access to edit the field on
  214. // entity creation.
  215. if (isset($entity->is_new)) {
  216. // Some entities provide an "is_new" property. If that is present, respect
  217. // whatever it's set to.
  218. $is_new = $entity->is_new;
  219. }
  220. else {
  221. // Otherwise, try to find out if the entity is new by checking its ID. Note
  222. // that checking empty() rather than !isset() is important here, to deal
  223. // with the case of entities that store "0" as their ID while the final
  224. // entity is in the process of being created (user accounts are a good
  225. // example of this).
  226. list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
  227. $is_new = empty($id);
  228. }
  229. if ($is_new) {
  230. return user_access('create ' . $field_name, $account);
  231. }
  232. // Check if user has access to edit this field in any entity.
  233. if (user_access('edit ' . $field_name, $account)) {
  234. return TRUE;
  235. }
  236. // If the user has permission to edit entities that they own, return TRUE if
  237. // they own this entity or FALSE if they don't.
  238. if (user_access('edit own ' . $field_name, $account)) {
  239. return _field_permissions_entity_is_owned_by_account($entity, $account);
  240. }
  241. return FALSE;
  242. }
  243. /**
  244. * Returns TRUE if an entity is owned by a user account, FALSE otherwise.
  245. */
  246. function _field_permissions_entity_is_owned_by_account($entity, $account) {
  247. // Try to get the uid of the entity owner from the entity itself. If it's not
  248. // set (for example, if the entity type does not store a uid or does not have
  249. // a concept of "ownership"), we need to assume that the provided user
  250. // account does not own it.
  251. $uid = isset($entity->uid) ? $entity->uid : FALSE;
  252. if (method_exists($entity, 'entityType')) {
  253. drupal_alter('field_permissions_userid_' . $entity->entityType(), $uid, $entity);
  254. }
  255. return $uid === $account->uid;
  256. }
  257. /**
  258. * Implements hook_features_pipe_COMPONENT_alter().
  259. *
  260. * Add field permissions to features when exporting a field_base.
  261. */
  262. function field_permissions_features_pipe_field_base_alter(&$pipe, $data, $export) {
  263. // Validate if there are field_base components that will be exported for this
  264. // feature.
  265. if (isset($export['features']['field_base'])) {
  266. module_load_include('inc', 'field_permissions', 'field_permissions.admin');
  267. // Iterate through the exported field_base components for this feature and
  268. // add the defined field permissions.
  269. foreach ($export['features']['field_base'] as $field_name) {
  270. $field = field_info_field($field_name);
  271. if (isset($field['field_permissions']['type']) && $field['field_permissions']['type'] == FIELD_PERMISSIONS_CUSTOM) {
  272. $perms = field_permissions_list_field_permissions($field, '');
  273. foreach ($perms as $perm => $info) {
  274. $pipe['user_permission'][] = $perm;
  275. }
  276. }
  277. }
  278. }
  279. }
  280. /**
  281. * Implements hook_field_attach_form().
  282. */
  283. function field_permissions_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  284. // Some fields are validated if they are #required even if field's #access
  285. // property is set to false. For example: file/image fields, options fields.
  286. foreach (element_children($form) as $key) {
  287. if (isset($form[$key]['#access']) && !$form[$key]['#access']) {
  288. _field_permissions_make_elements_non_required($form[$key]);
  289. }
  290. }
  291. }
  292. /**
  293. * Sets the #required property to FALSE recursively on form elements.
  294. */
  295. function _field_permissions_make_elements_non_required(&$elements) {
  296. if (!is_array($elements)) {
  297. return;
  298. }
  299. if (!empty($elements['#required'])) {
  300. $elements['#required'] = FALSE;
  301. }
  302. foreach (element_children($elements) as $key) {
  303. _field_permissions_make_elements_non_required($elements[$key]);
  304. }
  305. }
  306. /**
  307. * Implements hook_field_delete_field().
  308. */
  309. function field_permissions_field_delete_field($field) {
  310. // Delete any permissions related to the deleted field.
  311. $all_permissions = array_keys(field_permissions_permission());
  312. if (!empty($all_permissions)) {
  313. db_delete('role_permission')
  314. ->condition('module', 'field_permissions')
  315. ->condition('permission', $all_permissions, 'NOT IN')
  316. ->execute();
  317. }
  318. }