Projet

Général

Profil

Paste
Télécharger (20,6 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / content_access / content_access.module @ 87dbc3bf

1
<?php
2

    
3
/**
4
 * @file Content access module file.
5
 */
6

    
7
/**
8
 * Implements hook_help().
9
 */
10
function content_access_help($path, $arg) {
11
  switch ($path) {
12
    case 'admin/help#content_access':
13
      $output = '<h3>' . t('About') . '</h3>';
14
      $output .= '<p>' . t('Content Access module provides flexible way to control how and who should read or control your site content. Content Access can define custom access control rules for content types and even for every piece of content.') . '</p>';
15
      $output .= '<h3>' . t('Uses') . '</h3>';
16
      $output .= '<dl>';
17
      $output .= '<dt>' . t('Default and custom settings') . '</dt>';
18
      $output .= '<dd>' . t("Each <a href='@content-type'>content type</a> can have its own default content access settings configured as: <em>View any content</em> to allow anyone to view content from this content type, <em>View own content</em> to allow only content creators to see their own content, <em>Edit any content</em> to allow anyone to edit content from this content type, <em>Edit own content</em> to allow only content creators to edit their own content, <em>Delete any content</em> to allow anyone to delete content from this content type, <em>Delete own content </em> to allow content creators to delete their own content. This default settings for each content type can be further customized per every piece of content per user if you have <a href='@acl'>ACL</a> module enabled.", array('@content-type' => url('admin/structure/types'), '@acl' => 'http://drupal.org/project/acl/')) . '</dd>';
19
      $output .= '</dl>';
20
      return $output;
21
  }
22
}
23

    
24
/**
25
 * Implements hook_admin_paths().
26
 */
27
function content_access_admin_paths() {
28
  $paths = array(
29
    'node/*/access' => TRUE,
30
  );
31
  return $paths;
32
}
33

    
34
/**
35
 * Implements hook_menu().
36
 */
37
function content_access_menu() {
38
  $items = array();
39

    
40
  $items['node/%node/access'] = array(
41
    'title' => 'Access control',
42
    'page callback' => 'drupal_get_form',
43
    'page arguments' => array('content_access_page', 1),
44
    'access callback' => 'content_access_node_page_access',
45
    'access arguments' => array(1),
46
    'file' => 'content_access.admin.inc',
47
    'theme callback' => '_node_custom_theme',
48
    'type' => MENU_LOCAL_TASK,
49
    'weight' => 3,
50
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
51
  );
52

    
53
  $items['admin/structure/types/manage/%node_type/access'] = array(
54
    'title' => 'Access control',
55
    'description' => 'Configure content access control.',
56
    'page callback' => 'drupal_get_form',
57
    'page arguments' => array('content_access_admin_settings', 4),
58
    'access callback' => 'content_access_admin_settings_access',
59
    'access arguments' => array(),
60
    'type' => MENU_LOCAL_TASK,
61
    'file' => 'content_access.admin.inc',
62
    'theme callback' => '_node_custom_theme',
63
    'weight' => 1,
64
  );
65

    
66
  return $items;
67
}
68

    
69
/**
70
 * Implements hook_perm().
71
 */
72
function content_access_permission() {
73
  return array(
74
    'grant content access' => array(
75
      'title' => t('Grant content access'),
76
      'description' => t('View and modify content access for any nodes'),
77
    ),
78
    'grant own content access' => array(
79
      'title' => t('Grant own content access'),
80
      'description' => t('View and modify content access for own nodes'),
81
    ),
82
  );
83
}
84

    
85
/**
86
 * Get access tab page for the viewed node.
87
 */
88
function content_access_node_page_access($node) {
89
  global $user;
90
  return content_access_get_settings('per_node', $node->type) && user_access('grant content access') ||
91
    content_access_get_settings('per_node', $node->type) && (user_access('grant own content access') && ($user->uid == $node->uid));
92
}
93

    
94
/**
95
 * Content access settings for content type.
96
 */
97
function content_access_admin_settings_access() {
98
  return user_access('administer nodes') && user_access('administer content types');
99
}
100

    
101
/**
102
 * Implements hook_node_grants().
103
 */
104
function content_access_node_grants($account, $op) {
105
  return array(
106
    'content_access_author' => array($account->uid),
107
    'content_access_rid' => array_keys($account->roles),
108
  );
109
}
110

    
111
/**
112
 * Implements hook_node_access_records().
113
 */
114
function content_access_node_access_records($node) {
115
  if (content_access_disabling() || !$node->status) {
116
    return;
117
  }
118

    
119
  // Apply per node settings if necessary.
120
  if (content_access_get_settings('per_node', $node->type)) {
121
    $grants = array();
122
    foreach (array('view', 'update', 'delete') as $op) {
123
      foreach (content_access_get_rids_per_node_op($op, $node) as $rid) {
124
        $grants[$rid]['grant_' . $op] = 1;
125
      }
126
    }
127
    foreach ($grants as $rid => $grant) {
128
      $grants[$rid] = content_access_proccess_grant($grant, $rid, $node);
129
    }
130

    
131
    // Care for the author grant.
132
    $grant = array();
133
    foreach (array('view', 'update', 'delete') as $op) {
134
      // Get all roles that have access to use $op on this node.
135
      $any_roles = drupal_map_assoc(content_access_per_node_setting($op, $node));
136
      $any_roles = $any_roles ? $any_roles : array();
137
      $any_roles += ($op != 'view') ? content_access_get_settings($op, $node->type) : array();
138
      $grant['grant_' . $op] = content_access_own_op($node, $any_roles, content_access_get_rids_per_node_op($op . '_own', $node));
139
    }
140

    
141
    if (array_filter($grant)) {
142
      $grant['realm'] = 'content_access_author';
143
      $grants[] = content_access_proccess_grant($grant, $node->uid, $node);
144
    }
145
  }
146
  else {
147
    // Apply the content type defaults.
148
    $grants = content_access_get_type_grant($node);
149
  }
150

    
151
  if (empty($grants)) {
152
    // This means we grant no access.
153
    $grants[] = content_access_proccess_grant(array(), 0, $node);
154
  }
155
  else {
156
    content_access_optimize_grants($grants, $node);
157
  }
158

    
159
  return $grants;
160
}
161

    
162
/**
163
 * Implements hook_node_delete().
164
 */
165
function content_access_node_delete($node) {
166
  db_delete('content_access')
167
    ->condition('nid', $node->nid)
168
    ->execute();
169
}
170

    
171
/**
172
 * Used by the ACL module.
173
 */
174
function content_access_enabled() {
175
  return !content_access_disabling();
176
}
177

    
178
/**
179
 * Implements hook_disable().
180
 */
181
function content_access_disable() {
182
  content_access_disabling(TRUE);
183
}
184

    
185
/**
186
 * Remembers if we have disabled access.
187
 */
188
function content_access_disabling($set = NULL) {
189
  static $disabling = FALSE;
190

    
191
  if (isset($set)) {
192
    $disabling = $set;
193
  }
194
  return $disabling;
195
}
196

    
197
/**
198
 * Return content_access' settings.
199
 *
200
 * @param $setting
201
 *   One of the content_access_available_settings(), e.g. 'view' or 'per_node'.
202
 *   If 'all' is passed, all available settings are returned.
203
 * @param $type_name
204
 *   The name of the content type to return settings for.
205
 *
206
 * @return
207
 *   The value of the given setting or an array of all settings.
208
 */
209
function content_access_get_settings($setting, $type_name) {
210
  $settings = variable_get('content_access_' . $type_name, array());
211
  $settings += content_access_get_setting_defaults($type_name);
212
  if ($setting == 'all') {
213
    return $settings;
214
  }
215
  return isset($settings[$setting]) ? $settings[$setting] : NULL;
216
}
217

    
218
/**
219
 * Save content_access settings of a content type.
220
 */
221
function content_access_set_settings($settings, $type_name) {
222
  // Do not store default values so we do not have to care about synching our
223
  // settings with the permissions.
224
  foreach (content_access_get_setting_defaults($type_name) as $setting => $default_value) {
225
    if (isset($settings[$setting]) && $settings[$setting] == $default_value) {
226
      unset($settings[$setting]);
227
    }
228
  }
229
  variable_set('content_access_' . $type_name, $settings);
230
}
231

    
232
/**
233
 * Return an array containing all available content_access settings.
234
 */
235
function content_access_available_settings() {
236
  return array('view', 'update', 'delete', 'view_own', 'update_own', 'delete_own', 'per_node', 'priority');
237
}
238

    
239
/**
240
 * Defines default values for settings.
241
 */
242
function content_access_get_setting_defaults($type) {
243
  $defaults = array();
244
  $defaults['view'] = $defaults['view_own'] = array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID);
245
  foreach (array('update', 'delete') as $op) {
246
    $defaults[$op] = content_access_get_permission_access(content_access_get_permission_by_op($op, $type));
247
    $defaults[$op . '_own'] = content_access_get_permission_access(content_access_get_permission_by_op($op . '_own', $type));
248
  }
249
  $defaults['priority'] = 0;
250
  $defaults['per_node'] = FALSE;
251
  return $defaults;
252
}
253

    
254
/**
255
 * Returns an array of role ids that contain the given permission.
256
 */
257
function content_access_get_permission_access($perm, $reset = FALSE) {
258
  $roles = &drupal_static(__FUNCTION__, array());
259

    
260
  if ($reset) {
261
    $roles = array();
262
  }
263
  if (!isset($roles[$perm]) && $perm) {
264
    $roles[$perm] = array_keys(user_roles(0, $perm));
265
  }
266
  return isset($roles[$perm]) ? $roles[$perm] : array();
267
}
268

    
269
/**
270
 * Gets the name of a permission for the given operation, if there is a suiting one.
271
 */
272
function content_access_get_permission_by_op($op, $type) {
273
  switch ($op) {
274
    default:
275
      return FALSE;
276
    case 'update':
277
      return 'edit any ' . $type . ' content';
278
    case 'update_own':
279
      return 'edit own ' . $type . ' content';
280
    case 'delete':
281
      return 'delete any ' . $type . ' content';
282
    case 'delete_own':
283
      return 'delete own ' . $type . ' content';
284
  }
285
}
286

    
287
/**
288
 * Returns the default grants for a given node type.
289
 */
290
function content_access_get_type_grant($node) {
291
  // Cache per type default grants in a static array
292
  static $defaults = array();
293

    
294
  if (!isset($defaults[$node->type])) {
295
    $grants = array();
296

    
297
    // Only process the 'view' op as node_access() will take care of edit and delete
298
    foreach (content_access_get_settings('view', $node->type) as $rid) {
299
      $grants[$rid]['grant_view'] = 1;
300
      $grants[$rid] = content_access_proccess_grant($grants[$rid], $rid, $node);
301
    }
302
    $defaults[$node->type] = $grants;
303
  }
304

    
305
  // Care for the author grant.
306
  $grant = $grants = array();
307
  $grant['grant_view'] = content_access_own_op($node, content_access_get_settings('view', $node->type), content_access_get_settings('view_own', $node->type));
308
  if ($grant['grant_view']) {
309
    $grant['realm'] = 'content_access_author';
310
    $grants = array('author' => content_access_proccess_grant($grant, $node->uid, $node));
311
  }
312

    
313
  return $defaults[$node->type] + $grants;
314
}
315

    
316
/**
317
 * Process a grant, which means add priority, realm and other properties.
318
 */
319
function content_access_proccess_grant($grant, $gid, $node) {
320
  $grant += array('grant_view' => 0, 'grant_update' => 0, 'grant_delete' => 0, 'realm' => 'content_access_rid');
321
  $grant['gid'] = $gid;
322
  $grant['priority'] = content_access_get_settings('priority', $node->type);
323
  return $grant;
324
}
325

    
326
/**
327
 * Determines the grant for the node author and the given allowed roles of a operation.
328
 *
329
 * @param $any_roles
330
 *   The roles with which anybody has access (not optimized!)
331
 * @param $own_roles
332
 *   The roles with which only the author has acess (optimized!)
333
 */
334
function content_access_own_op($node, $any_roles, $own_roles) {
335
  static $roles = array();
336

    
337
  if (!isset($roles[$node->uid])) {
338
    $roles[$node->uid] = $node->uid ? array(DRUPAL_AUTHENTICATED_RID) : array(DRUPAL_ANONYMOUS_RID);
339
    $result = db_query('SELECT rid FROM {users_roles} WHERE uid = :uid', array(':uid' => $node->uid));
340

    
341
    foreach ($result as $role) {
342
      $roles[$node->uid][] = $role->rid;
343
    }
344
  }
345
  if (array_intersect($roles[$node->uid], $any_roles)) {
346
    // If there is access due to "any permissions" there is no need to at an author grant.
347
    return 0;
348
  }
349
  return array_intersect($roles[$node->uid], $own_roles) ? 1 : 0;
350
}
351

    
352
/**
353
 * Returns optimized role ids for the given operation and node to
354
 * grant access for.
355
 *
356
 * If to a role access is granted by permissions, it's not necessary
357
 * to write a grant for it. So it won't be returned.
358
 *
359
 * @param $op
360
 *   One of the supported operations.
361
 * @param $node
362
 *   The node object.
363
 */
364
function content_access_get_rids_per_node_op($op, $node) {
365
  $rids = content_access_per_node_setting($op, $node);
366

    
367
  if ($permission = content_access_get_permission_by_op($op, $node->type)) {
368
    $perm_roles = content_access_get_permission_access($permission);
369
    $rids = array_diff($rids, $perm_roles);
370

    
371
    if (in_array(DRUPAL_AUTHENTICATED_RID, $perm_roles)) {
372
      return in_array(DRUPAL_ANONYMOUS_RID, $rids) ? array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID) : array(DRUPAL_AUTHENTICATED_RID);
373
    }
374
  }
375
  return $rids;
376
}
377

    
378

    
379
/**
380
 * Returns the per node role settings. If no per node settings are available,
381
 * it will return the content type settings.
382
 *
383
 * @param $op
384
 *   One of the supported operations.
385
 * @param $node
386
 *   The node object.
387
 * @param $settings
388
 *    Optional array used to update the settings cache with the given settings.
389
 * @return
390
 *   An array of role ids which have access.
391
 */
392
function content_access_per_node_setting($op, $node, $settings = NULL) {
393
  static $grants = array();
394

    
395
  if (isset($settings)) {
396
    // Update settings cache
397
    $grants[$node->nid] = $settings;
398
    return;
399
  }
400

    
401
  if (!isset($grants[$node->nid]) || $grants[$node->nid] === FALSE) {
402
    $grants[$node->nid] = content_access_get_per_node_settings($node);
403
  }
404

    
405
  // Return the content type defaults if no per node settings are available
406
  return isset($grants[$node->nid][$op]) ? $grants[$node->nid][$op] : content_access_get_settings($op, $node->type);
407
}
408

    
409
/**
410
 * Gets the per node settings of a node.
411
 *
412
 * @note
413
 *   This function won't apply defaults, so if there are no other settings
414
 *   it will return an empty array.
415
 */
416
function content_access_get_per_node_settings($node) {
417
  foreach (db_query("SELECT settings FROM {content_access} WHERE nid = :nid", array(':nid' => $node->nid)) as $record) {
418
    $settings = $record->settings;
419
    if (!$settings) {
420
      return array();
421
    }
422
    return unserialize($settings);
423
  }
424
}
425

    
426
/**
427
 * Saves custom per node settings in the own content_access table.
428
 */
429
function content_access_save_per_node_settings($node, $settings) {
430
  $count = db_select('content_access')
431
            ->condition('nid', $node->nid)
432
            ->countQuery()->execute()->fetchField();
433

    
434
  if ($count > 0) {
435
    db_update('content_access')
436
      ->condition('nid', $node->nid)
437
      ->fields(array('settings' => serialize($settings)))
438
      ->execute();
439
  }
440
  else {
441
    db_insert('content_access')
442
      ->fields(array('nid' => $node->nid, 'settings' => serialize($settings)))
443
      ->execute();
444
  }
445

    
446
  // Make content_access_per_node_setting() use the new settings
447
  content_access_per_node_setting(NULL, $node, $settings);
448
}
449

    
450
/**
451
 * Deletes all custom per node settings, so that content type defaults are used again.
452
 */
453
function content_access_delete_per_node_settings($node) {
454
  db_delete('content_access')
455
    ->condition('nid', $node->nid)
456
    ->execute();
457

    
458
  // Clear the cache.
459
  content_access_per_node_setting(NULL, $node, FALSE);
460

    
461
  // Delete possible acl settings
462
  if (module_exists('acl')) {
463
    // @todo why content_access.admin.inc is not loaded before?
464
    module_load_include('inc', 'content_access', 'content_access.admin');
465
    foreach (array('view', 'update', 'delete') as $op) {
466
      $acl_id = content_access_get_acl_id($node, $op);
467
      acl_delete_acl($acl_id);
468
    }
469
  }
470
}
471

    
472
/**
473
 * Removes grants that doesn't change anything.
474
 *
475
 * @note
476
 *   The grants are compared with the normal access control settings.
477
 */
478
function content_access_optimize_grants(&$grants, $node) {
479
  $rids = array('view' => array(), 'update' => array(), 'delete' => array());
480

    
481
  foreach ($grants as $key => $grant) {
482
    foreach (array('view', 'update', 'delete') as $op) {
483
      if (is_numeric($key) && !empty($grant['grant_' . $op])) {
484
        $rids[$op][] = $key;
485
      }
486
    }
487
  }
488

    
489
  // Detect if all are allowed to view
490
  $all = array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID);
491
  if (count(array_diff($all, $rids['view'])) == 0) {
492
    //grant view access to all instead of single roles
493
    $rids['view'] = array('all');
494
    $grants['all'] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0, 'priority' => content_access_get_settings('priority', $node->type));
495
  }
496

    
497
  // If authenticated users are involved, remove unnecessary other roles.
498
  foreach (array('view', 'update', 'delete') as $op) {
499
    if (in_array(DRUPAL_AUTHENTICATED_RID, $rids[$op])) {
500
      $rids[$op] = in_array(DRUPAL_ANONYMOUS_RID, $rids[$op]) ? array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID) : array(DRUPAL_AUTHENTICATED_RID);
501
    }
502
  }
503

    
504
  // Now let's remove unnecessary grants, if any.
505
  foreach ($grants as $key => $grant) {
506
    if (!is_numeric($key)) {
507
      continue;
508
    }
509
    foreach (array('view', 'update', 'delete') as $op) {
510
      if ($grant['grant_' . $op] && in_array($key, $rids[$op])) {
511
        //it's still here, so we can't remove this grant
512
        continue 2;
513
      }
514
    }
515
    //ok, remove it
516
    unset($grants[$key]);
517
  }
518
}
519

    
520
/**
521
 * Implements hook_node_type_delete().
522
 */
523
function content_access_node_type_delete($info) {
524
  variable_del('content_access_' . $info->type);
525
}
526

    
527
/**
528
 * Implements hook_node_type_update().
529
 *
530
 * Updates settings on node type name change.
531
 */
532
function content_access_node_type_update($info) {
533
  if (!empty($info->old_type) && $info->old_type != $info->type) {
534
    $settings = content_access_get_settings('all', $info->old_type);
535
    content_access_set_settings($settings, $info->type);
536
    variable_del('content_access_' . $info->old_type);
537
  }
538
}
539

    
540
/**
541
 * Implements hook_node_access_explain().
542
 */
543
function content_access_node_access_explain($row) {
544
  static $roles;
545

    
546
  if (!isset($roles)) {
547
    $roles = user_roles();
548
  }
549
  if (!$row->gid && $row->realm == 'content_access_rid') {
550
    return t('Content access: No access is granted.');
551
  }
552
  switch ($row->realm) {
553
    case 'content_access_author':
554
      return t('Content access: author of the content can access');
555
    case 'content_access_rid':
556
      return t('Content access: %role can access', array('%role' => $roles[$row->gid]));
557
  }
558
}
559

    
560
/**
561
 * Implements hook_form_alter().
562
 */
563
function content_access_form_alter(&$form, $form_state, $form_id) {
564
  if ($form_id == 'user_admin_perm') {
565
    module_load_include('inc', 'content_access', 'content_access.admin');
566
    $form['#submit'][] = 'content_access_user_admin_perm_submit';
567
  }
568
}
569

    
570
/**
571
 * Returns an array of possible operations on content and their labels.
572
 */
573
function _content_access_get_operations($type = NULL) {
574
  $operations = array(
575
    'view' => t('View any @type content', array('@type' => $type)),
576
    'view_own' => t('View own @type content', array('@type' => $type)),
577
    'update' => t('Edit any @type content', array('@type' => $type)),
578
    'update_own' => t('Edit own @type content', array('@type' => $type)),
579
    'delete' => t('Delete any @type content', array('@type' => $type)),
580
    'delete_own' => t('Delete own @type content', array('@type' => $type)),
581
  );
582
  return $operations;
583
}
584

    
585
/**
586
 * Formapi #process callback, that disables checkboxes for roles without access to content
587
 */
588
function content_access_disable_checkboxes($element) {
589
  $access_roles = content_access_get_permission_access('access content');
590
  $admin_roles = content_access_get_permission_access('administer nodes');
591

    
592
  foreach (element_children($element) as $key) {
593
    if (!in_array($key, $access_roles) &&
594
        $key == DRUPAL_ANONYMOUS_RID &&
595
        !in_array(DRUPAL_AUTHENTICATED_RID, $access_roles)) {
596
      $element[$key]['#disabled'] = TRUE;
597
      $element[$key]['#default_value'] = FALSE;
598
      $element[$key]['#prefix'] = '<span' . drupal_attributes(array('title' => t("This role is lacking the permission '@perm', so it has no access.", array('@perm' => t('access content'))))) . '>';
599
      $element[$key]['#suffix'] = "</span>";
600
    }
601
    elseif (in_array($key, $admin_roles) ||
602
            ($key != DRUPAL_ANONYMOUS_RID && in_array(DRUPAL_AUTHENTICATED_RID, $admin_roles))) {
603
      // Fix the checkbox to be enabled for users with administer node privileges
604
      $element[$key]['#disabled'] = TRUE;
605
      $element[$key]['#default_value'] = TRUE;
606
      $element[$key]['#prefix'] = '<span' . drupal_attributes(array('title' => t("This role has '@perm' permission, so access is granted.", array('@perm' => t('administer nodes'))))) . '>';
607
      $element[$key]['#suffix'] = "</span>";
608
    }
609
  }
610

    
611
  return $element;
612
}
613

    
614
/**
615
 * Gets node's access permissions.
616
 */
617
function _content_access_get_node_permissions($type) {
618
  return array_filter(array_map('content_access_get_permission_by_op', array_flip(_content_access_get_operations()), array_fill(0, 6, $type)));
619
}
620

    
621
/**
622
 * Gets the content access acl id of the node.
623
 */
624
function content_access_get_acl_id($node, $op) {
625
  $acl_id = acl_get_id_by_name('content_access', $op . '_' . $node->nid);
626
  if (!$acl_id) {
627
    $acl_id = acl_create_new_acl('content_access', $op . '_' . $node->nid);
628
  }
629
  return $acl_id;
630
}
631

    
632
/**
633
 * Detaches all our ACLs for the nodes of the given type.
634
 */
635
function _content_access_remove_acls($type) {
636
  $result = db_query("SELECT n.nid FROM {node} n WHERE type = :type", array('type' => $type));
637
  foreach ($result as $node) {
638
    acl_node_clear_acls($node->nid, 'content_access');
639
  }
640
}