Projet

Général

Profil

Paste
Télécharger (26,7 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / forum_access / forum_access.module @ e9734207

1
<?php
2

    
3
/**
4
 * @file
5
 * forum_access.module
6
 *
7
 * This module uses form_alter to add permissions and moderator settings to
8
 * forums.
9
 *
10
 */
11

    
12
/**
13
 * Implements hook_requirements().
14
 *
15
 * Remind the user to upgrade to Chain Menu Access API 2.x.
16
 */
17
function forum_access_requirements($phase) {
18
  require_once DRUPAL_ROOT . '/includes/install.inc';
19
  $result = array();
20
  switch ( $phase ) {
21
    case 'update':
22
    case 'runtime':
23
      $t = get_t();
24
      $path = drupal_get_filename('module', 'chain_menu_access');
25
      $path = substr($path, 0, strlen($path) - 7) . '.info';
26
      $info = drupal_parse_info_file($path);
27
      $version = (isset($info['version']) ? $info['version'] : $t('Unknown'));
28
      $found = preg_match('/7\.x-([0-9]*)\./', $version, $matches);
29
      if (($found && $matches[1] == 1) || !$found) {
30
        $cma = 'Chain Menu Access API';
31
        $variables = array('@Chain_Menu_Access_API' => $cma, '@module' => url('admin/modules'));
32
        $result[] = array(
33
          'title'       => $t('@Chain_Menu_Access_API module', $variables),
34
          'value'       => $version,
35
          'description' => $t('Version 1.x is obsolete. Upgrade to version 2.x as soon as <em>all</em> installed client modules support that version.') . '<br />' .
36
                           $t('Check the <a href="@module">module administration page</a> to find out which of your modules depend on @Chain_Menu_Access_API.', $variables),
37
          'severity'    => REQUIREMENT_WARNING,
38
        );
39
      }
40
  }
41
  return $result;
42
}
43

    
44
/**
45
 * Implements hook_menu_alter().
46
 *
47
 * Remove the 'Forum' menu item if no forums are visible.
48
 */
49
function forum_access_menu_alter(&$items) {
50
  $requirements = forum_access_requirements('runtime');
51
  if (!empty($requirements) && $requirements[0]['value'] != t('Unknown')) {
52
    // Fall back to obsolete Chain Menu Access API version 7.x-1.x,
53
    // because that's what's installed.
54
    chain_menu_access_chain($items['forum'],                 'forum_access_view_any_forum', array());
55
    chain_menu_access_chain($items['node/%node'],            '_forum_access_node_access_callback', array(1, 'view'));
56
    chain_menu_access_chain($items['comment/%comment/edit'], '_forum_access_comment_edit_callback', array(1));
57
    chain_menu_access_chain($items['comment/%comment/edit'], '_forum_access_comment_access_callback', array(1, 2), TRUE);
58
    chain_menu_access_chain($items['comment/%/delete'],      '_forum_access_comment_access_callback', array(1, 2), TRUE);
59
    chain_menu_access_chain($items['comment/%/approve'],     '_forum_access_comment_access_callback', array(1, 2), TRUE);
60
    chain_menu_access_chain($items['comment/reply/%node'],   '_forum_access_comment_access_callback', array(2, 1));
61
  }
62
  else {
63
    chain_menu_access_chain($items, 'forum',                 'forum_access_view_any_forum');
64
    chain_menu_access_chain($items, 'node/%node',            '_forum_access_node_access_callback', array(1, 'view'));
65
    chain_menu_access_chain($items, 'comment/%comment/edit', '_forum_access_comment_edit_callback', array(1));
66
    chain_menu_access_chain($items, 'comment/%comment/edit', '_forum_access_comment_access_callback', array(1, 2), TRUE);
67
    chain_menu_access_chain($items, 'comment/%/delete',      '_forum_access_comment_access_callback', array(1, 2), TRUE);
68
    chain_menu_access_chain($items, 'comment/%/approve',     '_forum_access_comment_access_callback', array(1, 2), TRUE);
69
    chain_menu_access_chain($items, 'comment/reply/%node',   '_forum_access_comment_access_callback', array(2, 1));
70
  }
71
}
72

    
73
function _forum_access_node_access_callback($node, $op) {
74
  global $user;
75

    
76
  if ($op == 'view' && ($tid = _forum_access_get_tid($node))) {
77
    if (forum_access_access('update', $tid) || forum_access_access('delete', $tid)) {
78
      // Add the 'administer comments' permission to the user so that he can
79
      // see unpublished comments.
80
      forum_access_enable_moderator();
81
    }
82
    // The 'view' access check will be done by core.
83
  }
84
  return TRUE;
85
}
86

    
87
function _forum_access_comment_edit_callback($comment) {
88
  global $user;
89

    
90
  // This callback is governed by AND, return TRUE by default.
91
  $node = node_load($comment->nid);
92
  if ($tid = _forum_access_get_tid($node)) {
93
    if (!forum_access_is_moderator($user, $tid) && !forum_access_access('update', $tid, $user) && !user_access('administer comments') && !($user->uid == $comment->uid && user_access('edit own forum content'))) {
94
      return FALSE;
95
    }
96
  }
97
  return TRUE;
98
}
99

    
100
function _forum_access_comment_access_callback($comment, $op) {
101
  global $user;
102

    
103
  if ($op == 'reply') {
104
    // 'reply' is governed by AND, return TRUE by default.
105
    $node = $comment;
106
    if ($tid = _forum_access_get_tid($node)) {
107
      return forum_access_access('create', $tid);
108
    }
109
    return TRUE;
110
  }
111

    
112
  if (is_numeric($comment)) {
113
    $comment = comment_load($comment);
114
    if (empty($comment)) {
115
      return FALSE;
116
    }
117
  }
118
  elseif (is_string($comment)) {
119
    return FALSE;
120
  }
121
  $node = node_load($comment->nid);
122

    
123
  // The remaining $ops are governed by OR, return FALSE by default.
124
  if ($tid = _forum_access_get_tid($node)) {
125
    if ($op == 'approve') {
126
      return $user->uid == 1 || forum_access_is_moderator($user, $tid);
127
    }
128
    if (!user_access('administer comments')) {
129
      if ($op == 'edit' && (forum_access_access('update', $tid) || user_access('edit any forum content') || ($user->uid == $comment->uid && user_access('edit own forum content')))) {
130
        forum_access_enable_moderator();
131
        return TRUE;
132
      }
133
      if ($op == 'delete' && (forum_access_access('delete', $tid) || user_access('delete any forum content') || ($user->uid == $comment->uid && user_access('delete own forum content')))) {
134
        return TRUE;
135
      }
136
    }
137
  }
138
  return FALSE;
139
}
140

    
141
/**
142
 * Access callback for the 'forum' menu item.
143
 *
144
 * Returns 1 if the user has at least one role that can access
145
 * at least one forum, 2 if the user is moderator in at least one forum,
146
 * FALSE otherwise.
147
 */
148
function forum_access_view_any_forum($account = NULL) {
149
  global $user;
150
  $returns = &drupal_static(__FUNCTION__, array());
151

    
152
  if (!isset($account)) {
153
    $account = $user;
154
  }
155

    
156
  if (!isset($returns[$account->uid])) {
157
    if (user_access('bypass node access', $account)) {
158
      return $returns[$account->uid] = 1;
159
    }
160
    if (!user_access('access content')) {
161
      return $returns[$account->uid] = FALSE;
162
    }
163
    $rids = variable_get('forum_access_rids', NULL);
164
    if (!isset($rids)) {
165
      $rids = db_query("SELECT fa.rid FROM {forum_access} fa WHERE fa.grant_view > 0 GROUP BY fa.rid")->fetchCol();
166
      variable_set('forum_access_rids', $rids);
167
    }
168
    foreach ($rids as $rid) {
169
      if (isset($account->roles[$rid])) {
170
        return $returns[$account->uid] = 1;
171
      }
172
    }
173
    // Check moderator, too.
174
    $query = db_select('acl', 'acl');
175
    $query->join('acl_user', 'aclu', "acl.acl_id = aclu.acl_id");
176
    $count = $query
177
      ->fields('acl', array('number'))
178
      ->condition('acl.module', 'forum_access')
179
      ->condition('aclu.uid', $account->uid)
180
      ->countQuery()
181
      ->execute()
182
      ->fetchField();
183
    $returns[$account->uid] = ($count > 0 ? 2 : FALSE);
184
  }
185
  return $returns[$account->uid];
186
}
187

    
188
/**
189
 * Implements hook_node_grants().
190
 *
191
 * This function supplies the forum access grants. forum_access simply uses
192
 * roles as ACLs, so rids translate directly to gids.
193
 */
194
function forum_access_node_grants($user, $op) {
195
  $grants['forum_access'] = array_keys($user->roles);
196
  return $grants;
197
}
198

    
199
/**
200
 * Implements hook_node_access_records().
201
 *
202
 * Returns a list of grant records for the passed in node object.
203
 * Checks to see if maybe we're being disabled.
204
 */
205
function forum_access_node_access_records($node) {
206
  if (!forum_access_enabled()) {
207
    return;
208
  }
209

    
210
  static $seers;
211

    
212
  $grants = &drupal_static(__FUNCTION__, array());
213
  $seers = &drupal_static(__FUNCTION__ . '__seers');
214
  $tid = _forum_access_get_tid($node);
215

    
216
  // Set proper grants for nodecomment comment nodes.
217
  //if (isset($node->comment_target_nid)) {
218
  //  if ($changed_tid = _forum_access_changed_tid()) {
219
  //    $tid = $changed_tid; // the topic node hasn't been saved yet!
220
  //  }
221
  //  else {
222
  //    $node = node_load($node->comment_target_nid);
223
  //    $tid = _forum_access_get_tid($node);
224
  //  }
225
  //}
226

    
227
  if ($tid) {
228
    if (!isset($grants[$tid])) {
229
      if (!isset($seers)) {
230
        $seers = user_roles(FALSE, 'bypass node access');
231
      }
232
      $result = db_query('SELECT * FROM {forum_access} WHERE tid = :tid', array(
233
        ':tid' => $tid
234
      ));
235
      foreach ($result as $grant) {
236
        if (isset($seers[$grant->rid])) {
237
          continue; // Don't provide any useless grants!
238
        }
239
        $grants[$tid][] = array(
240
          'realm'        => 'forum_access',
241
          'gid'          => $grant->rid,
242
          'grant_view'   => $grant->grant_view,
243
          'grant_update' => $grant->grant_update,
244
          'grant_delete' => $grant->grant_delete,
245
          'priority'     => $grant->priority,
246
        );
247
      }
248
      //dsm("forum_access_node_access_records($node->nid) (tid=$tid) returns ". var_export($grants[$tid], TRUE), 'status');
249
    }
250
    if (isset($grants[$tid])) {
251
      return $grants[$tid];
252
    }
253
  }
254
}
255

    
256
/**
257
 * Implements hook_form_alter().
258
 *
259
 * Alter the node/comment create/edit forms and various admin forms.
260
 */
261
function forum_access_form_alter(&$form, &$form_state, $form_id) {
262
  //dpm($form, "form_id($form_id)");
263
  if ($form_id == 'forum_node_form' && !empty($form['#node_edit_form'])) {
264
    _forum_access_module_load_include('node.inc');
265
    _forum_access_node_form($form, $form_state);
266
  }
267
  elseif ($form['#id'] == 'comment-form') {
268
    _forum_access_module_load_include('node.inc');
269
    _forum_access_comment_form($form, $form_state);
270
  }
271
  elseif ($form_id == 'forum_overview') {
272
    _forum_access_module_load_include('admin.inc');
273
    _forum_access_forum_overview($form, $form_state);
274
  }
275
  elseif ($form_id == 'forum_form_container') {
276
    _forum_access_module_load_include('admin.inc');
277
    _forum_access_forum_form($form, $form_state, TRUE);
278
  }
279
  elseif ($form_id == 'forum_form_forum') {
280
    _forum_access_module_load_include('admin.inc');
281
    _forum_access_forum_form($form, $form_state, FALSE);
282
  }
283
  elseif ($form_id == 'content_access_admin_settings' && empty($_POST)) {
284
    _forum_access_module_load_include('admin.inc');
285
    _forum_access_content_access_admin_form($form_state['build_info']['args'][0]);
286
  }
287
}
288

    
289
/**
290
 * Implements hook_form_node_form_alter().
291
 *
292
 * Allows the forum field to be optional on non-forum nodes.
293
 */
294
function forum_access_form_node_form_alter(&$form, &$form_state, $form_id) {
295
  if (isset($form['taxonomy_forums'])) {
296
    $field = field_info_instance('node', 'taxonomy_forums', $form['type']['#value']);
297
    // Make the vocabulary required for 'real' forum-nodes.
298
    if (!$field['required']) {
299
      $langcode = $form['taxonomy_forums']['#language'];
300
      $form['taxonomy_forums'][$langcode]['#required'] = FALSE;
301
    }
302
  }
303
}
304

    
305
/**
306
 * Implement hook_node_load().
307
 *
308
 * Sets $node->forum_tid to avoid confusing forum_node_view().
309
 */
310
function forum_access_node_load($nodes, $types) {
311
  foreach ($nodes as $node) {
312
    if (isset($node->taxonomy_forums) && empty($node->taxonomy_forums) && !isset($node->forum_tid)) {
313
      $node->forum_tid = NULL;
314
    }
315
  }
316
}
317

    
318
/**
319
 * Implements hook_comment_load().
320
 */
321
function forum_access_comment_load($comments) {
322
  //TODO: Investigate usefulness of this hook.
323
  return;
324
}
325

    
326
/**
327
 * Implements hook_query_alter().
328
 */
329
function forum_access_query_alter($p1, $p2, $p3) {
330
  //TODO: Investigate usefulness of this hook.
331
  return;
332
}
333

    
334
/**
335
 * Implements hook_query_term_access_alter().
336
 *
337
 * @param QueryAlterableInterface $query
338
 */
339
function forum_access_query_term_access_alter(QueryAlterableInterface $query) {
340
  global $user;
341

    
342
  // Read meta-data from query, if provided.
343
  if (!$account = $query->getMetaData('account')) {
344
    $account = $user;
345
  }
346
  if (!$op = $query->getMetaData('op')) {
347
    $op = 'view';
348
  }
349

    
350
  // If $account can bypass node access, we let them administer the full forum
351
  // structure and see the nodes, i.e. we don't restrict the query.
352
  if (user_access('bypass node access', $account) && (strpos(current_path(), 'admin/structure/forum') === 0 || strpos(current_path(), 'node/') === 0)) {
353
    return;
354
  }
355

    
356
  // Prevent duplicate records.
357
  $query->distinct();
358

    
359
  // Find all instances of the {taxonomy_term_data} table being joined --
360
  // could appear more than once in the query, and could be aliased.
361
  // Join each one to the forum_access table.
362

    
363
  $tables = $query->getTables();
364
  $rids = array_keys($account->roles);
365
  foreach ($tables as $talias => $tableinfo) {
366
    $table = $tableinfo['table'];
367
    if (!($table instanceof SelectQueryInterface) && $table == 'taxonomy_term_data') {
368
      // The node_access table has the access grants for any given node.
369
      $access_alias = $query->leftJoin('forum_access', 'fa', '%alias.tid = ' . $talias . '.tid');
370
      $acl_alias = $query->leftJoin('acl', 'acl', "%alias.number = $talias.tid AND %alias.module = 'forum_access'");
371
      if (user_access('bypass node access', $account)) {
372
        // If $account can bypass node access, we allow access if any role or
373
        // account has access.
374
        $aclu_alias = $query->leftJoin('acl_user', 'aclu', "%alias.acl_id = $acl_alias.acl_id");
375
        $query->condition(db_or()
376
          ->isNull("$access_alias.rid")
377
          ->condition("$access_alias.grant_$op", 1, '>=')
378
          ->isNotNull("$aclu_alias.uid")
379
        );
380
      }
381
      else {
382
        $aclu_alias = $query->leftJoin('acl_user', 'aclu', "%alias.acl_id = $acl_alias.acl_id AND %alias.uid = $account->uid");
383
        $query->condition(db_or()
384
          ->isNull("$access_alias.rid")
385
          ->condition(db_and()
386
            ->condition("$access_alias.rid", $rids, 'IN')
387
            ->condition("$access_alias.grant_$op", 1, '>='))
388
          ->condition("$aclu_alias.uid", $account->uid));
389
      }
390
    }
391
  }
392
}
393

    
394
/**
395
 * Implements hook_node_presave().
396
 */
397
function forum_access_node_presave($node, $return_old_tid = FALSE) {
398
  $old_tid = &drupal_static('forum_access_node_presave');
399
  if (_forum_node_check_node_type($node)) {
400
    if (empty($node->nid)) {
401
      // Added for migrations, which log errors due to no nid during presave, see #3003279.
402
      $old_tid = NULL;
403
    }
404
    else {
405
      $old_tid = db_query('SELECT tid FROM {forum} WHERE nid = :nid', array(
406
        ':nid' => $node->nid,
407
      ))->fetchField();
408
    }
409
  }
410
  if (!empty($old_tid) && empty($node->taxonomy_forums['und'])) {
411
    $node->forum_tid = null;
412
  }
413
}
414

    
415
/**
416
 * Implements hook_node_update().
417
 */
418
function forum_access_node_update($node) {
419
  $old_tid = &drupal_static('forum_access_node_presave');
420
  if (_forum_node_check_node_type($node)) {
421
    $tid = _forum_access_get_tid($node);
422
    if (isset($old_tid)) {
423
      if ($tid == $old_tid) {
424
        return;
425
      }
426
      acl_node_clear_acls($node->nid, 'forum_access');
427

    
428
      /*
429
      if (module_exists('nodecomment')) {
430
        _forum_access_changed_tid($tid);
431
        $result = db_query('SELECT cid FROM {node_comments} WHERE nid = :nid', array(
432
          ':nid' => $node->nid,
433
        ));
434
        foreach ($result as $row) {
435
          acl_node_clear_acls($row->cid, 'forum_access');
436
        }
437
      }
438
      */
439
    }
440
    // For changed and for previously unassigned terms we need to fake an insert.
441
    forum_access_node_insert($node);
442
  }
443
}
444

    
445
/**
446
 * Implements hook_node_insert().
447
 */
448
function forum_access_node_insert($node) {
449
  $old_tid = &drupal_static('forum_access_node_presave');
450
  if (_forum_node_check_node_type($node)) {
451
    if ($tid = _forum_access_get_tid($node)) {
452
      $acl_id = _forum_access_get_acl($tid);
453
      acl_node_add_acl($node->nid, $acl_id, 1, 1, 1);
454

    
455
      /*
456
      if (isset($old_tid) && module_exists('nodecomment')) {
457
        $result = db_query('SELECT cid FROM {node_comments} WHERE nid = :nid', array(
458
          ':nid' => $node->nid,
459
        ));
460
        foreach ($result as $row) {
461
          acl_node_add_acl($row->cid, $acl_id, 1, 1, 1);
462
          node_access_acquire_grants(node_load($row->cid)); //TODO use node_load_multiple() here
463
        }
464
      }
465
      */
466
    }
467
    $old_tid = NULL;
468
  }
469
  /*
470
  elseif (isset($node->comment_target_nid)) {
471
    // Set moderator on nodecomment.
472
    $topic_node = node_load($node->comment_target_nid);
473
    if (_forum_node_check_node_type($topic_node) && $topic_tid = _forum_access_get_tid($topic_node)) {
474
      $acl_id = _forum_access_get_acl($topic_tid);
475
      acl_node_add_acl($node->nid, $acl_id, 1, 1, 1);
476
    }
477
  }
478
  */
479
}
480

    
481
function forum_access_enable_moderator($enable = TRUE) {
482
  global $user;
483

    
484
  if (!$enable || !user_access('administer comments')) {
485
    $perm = &drupal_static('user_access');
486
    if ($enable) {
487
      $perm[$user->uid]['administer comments'] = $enable;
488
      $user->_forum_access_moderator = $enable;
489
    }
490
    else {
491
      unset($perm[$user->uid]['administer comments']);
492
      unset($user->_forum_access_moderator);
493
    }
494
  }
495
}
496

    
497
/**
498
 * Get an array of moderator UIDs or NULL.
499
 */
500
function forum_access_get_moderator_uids($tid) {
501
  $acl_id = _forum_access_get_acl($tid);
502
  if ($uids = acl_get_uids($acl_id)) {
503
    return $uids;
504
  }
505
}
506

    
507
/**
508
 * Implements hook_custom_theme().
509
 */
510
function forum_access_custom_theme() {
511
}
512

    
513
/**
514
 * Implements hook_menu_get_item_alter().
515
 *
516
 * Saves the tid on the forum-specific pages.
517
 */
518
function forum_access_menu_get_item_alter(&$router_item, $path, $original_map) {
519
  if (forum_access_current_tid() == 0) {
520
    switch ($original_map[0]) {
521
      case 'forum':
522
        if (isset($original_map[1]) && is_numeric($original_map[1])) {
523
          forum_access_current_tid($original_map[1]);
524
        }
525
        break;
526
      case 'node':
527
        if (isset($original_map[1]) && is_numeric($nid = $original_map[1]) && ($node = node_load($nid)) && ($tid = _forum_access_get_tid($node))) {
528
          forum_access_current_tid($tid);
529
        }
530
        break;
531
    }
532
  }
533
}
534

    
535
/**
536
 * Saves and returns the forum TID, if we're on a forum-specific page.
537
 */
538
function forum_access_current_tid($tid = NULL) {
539
  static $saved_tid = 0;
540
  if (isset($tid)) {
541
    $saved_tid = $tid;
542
  }
543
  return $saved_tid;
544
}
545

    
546
/**
547
 * Implements $modulename_preprocess_$hook() for forum_list.
548
 *
549
 * Add forum_access_moderators to each forum,
550
 * containing a list of user objects.
551
 *
552
 * Note: On a site with many moderators, this function is expensive,
553
 * and thus it is disabled by default. Set the variable to TRUE to enable.
554
 */
555
function forum_access_preprocess_forum_list(&$variables) {
556
  if (variable_get('forum_access_provide_moderators_template_variable', FALSE)) {
557
    foreach ($variables['forums'] as $tid => $forum) {
558
      $forum->forum_access_moderators = NULL;
559
      if ($uids = forum_access_get_moderator_uids($tid)) {
560
        $forum->forum_access_moderators = user_load_multiple($uids);
561
      }
562
    }
563
  }
564
}
565

    
566
/**
567
 * Implements hook_node_view_alter().
568
 *
569
 * Remove 'Add new comment' link and breadcrumb if they shouldn't be there.
570
 */
571
function forum_access_node_view_alter(&$build) {
572
  if ($tid = _forum_access_get_tid($build['#node'])) {
573
    _forum_access_module_load_include('node.inc');
574
    _forum_access_node_view_alter($build, $tid);
575
  }
576

    
577
  $breadcrumb = drupal_get_breadcrumb();
578
  if (!$tid && count($breadcrumb) == 2 && strpos($breadcrumb[0], '<a href="/">') === 0 && strpos($breadcrumb[1], '<a href="/forum">') === 0) {
579
    // We must hack away the bogus breadcrumb set by forum module
580
    // (empty taxonomy_forums).
581
    $stored_breadcrumb = &drupal_static('drupal_set_breadcrumb');
582
    $stored_breadcrumb = NULL;
583
  }
584
}
585

    
586
/**
587
 * Implements hook_comment_view_alter().
588
 */
589
function forum_access_comment_view_alter(&$build) {
590
  if ($tid = _forum_access_get_tid($build['#node'])) {
591
    _forum_access_module_load_include('node.inc');
592
    _forum_access_comment_view_alter($build, $tid);
593
  }
594
}
595

    
596
/**
597
 * This is also required by ACL module.
598
 */
599
function forum_access_enabled($set = NULL) {
600
  static $enabled = TRUE; // not drupal_static!
601
  if ($set !== NULL) {
602
    $enabled = $set;
603
  }
604
  return $enabled;
605
}
606

    
607
/**
608
 * Implements hook_node_access().
609
 */
610
function forum_access_node_access($node, $op, $account) {
611
  $cache = &drupal_static(__FUNCTION__, array());
612

    
613
  $type = is_string($node) ? $node : $node->type;
614
  if ($op == 'create') {
615
    if ($type == 'forum') {
616
      $tid = forum_access_current_tid();
617
      if (isset($cache[$account->uid][$op][$tid])) {
618
        return $cache[$account->uid][$op][$tid];
619
      }
620
      if (!forum_access_access('create', $tid, $account)) {
621
        return $cache[$account->uid][$op][$tid] = NODE_ACCESS_DENY;
622
      }
623
      return $cache[$account->uid][$op][$tid] = NODE_ACCESS_IGNORE;
624
    }
625
  }
626
  else {
627
    $nid = $node->nid;
628
    if (!isset($cache[$account->uid][$op][$nid])) {
629
      if ($tid = _forum_access_get_tid($node)) {
630
        if (!forum_access_access('view', $tid, $account)) {
631
          return $cache[$account->uid][$op][$nid] = NODE_ACCESS_DENY;
632
        }
633
        if (($op == 'update' && (user_access('edit any forum content', $account) || ($node->uid == $account->uid && user_access('edit own forum content', $account))))
634
          || ($op == 'delete' && (user_access('delete any forum content', $account) || ($node->uid == $account->uid && user_access('delete own forum content', $account))))) {
635
          return $cache[$account->uid][$op][$nid] = forum_access_node_access($node, 'view', $account);
636
        }
637
        $access = forum_access_access($op, $tid, $account);
638
        if ($op == 'view' && $access == 1 && !$node->status) {
639
          if (user_access('view own unpublished content', $account) && $account->uid && $account->uid == $node->uid) {
640
            // Allow access to own unpublished node.
641
          }
642
          elseif (forum_access_is_moderator($account, $tid)) {
643
            // Allow access to moderator.
644
          }
645
          else {
646
            $access = FALSE;
647
          }
648
        }
649
        return $cache[$account->uid][$op][$nid] = ($access ? NODE_ACCESS_IGNORE : NODE_ACCESS_DENY);
650
      }
651
      else {
652
        return $cache[$account->uid][$op][$nid] = NODE_ACCESS_IGNORE;
653
      }
654
    }
655
    return $cache[$account->uid][$op][$nid];
656
  }
657
  return NODE_ACCESS_IGNORE;
658
}
659

    
660
/**
661
 * Implements access checking.
662
 *
663
 * $op -- view, update, delete or create
664
 * $tid -- the tid of the forum
665
 * $account -- the account to test for; if NULL use current user
666
 * $administer_nodes_sees_everything -- pass FALSE to ignore the 'administer nodes' permission
667
 *
668
 * Return:
669
 *   FALSE - access not granted
670
 *   1     - access granted
671
 *   2     - access granted for forum moderator
672
 */
673
function forum_access_access($op, $tid, $account = NULL, $administer_nodes_sees_everything = TRUE) {
674
  $cache = &drupal_static(__FUNCTION__, array());
675
  if (!$account) {
676
    global $user;
677
    $account = $user;
678
  }
679

    
680
  if (user_access('bypass node access', $account)) {
681
//TODO revise (including comment above)
682
//      $administer_nodes_sees_everything && user_access('administer nodes', $account) && array_search($type, array('view', 'update', 'delete')) !== FALSE) {
683
    return 1;
684
  }
685

    
686
  if ($op == 'delete' && user_access('delete any forum content', $account)) {
687
    return 1;
688
  }
689

    
690
  if ($op == 'update' && user_access('edit any forum content', $account)) {
691
    return 1;
692
  }
693

    
694
  if (!isset($cache[$account->uid][$tid][$op])) {
695
    $query = db_select('forum_access', 'fa')
696
      ->fields('fa', array('tid'))
697
      ->condition("fa.grant_$op", 1, '>=')
698
      ->condition('fa.rid', array_keys($account->roles), 'IN');
699
    if ($tid != 0) {
700
      $query = $query
701
        ->condition('fa.tid', $tid, '=');
702
    }
703
    $result = $query
704
      ->execute()
705
      ->fetchField();
706

    
707
    if ($result) {
708
      $cache[$account->uid][$tid][$op] = 1;
709
    }
710
    else {
711
      // check our moderators too
712
      $result = forum_access_is_moderator($account, $tid);
713
      $cache[$account->uid][$tid][$op] = ($result ? 2 : FALSE);
714
    }
715
  }
716
  return $cache[$account->uid][$tid][$op];
717
}
718

    
719
/**
720
 * Implements hook_taxonomy_term_delete().
721
 *
722
 * Delete {forum_access} records when forums are deleted.
723
 */
724
function forum_access_taxonomy_term_delete($term) {
725
  //dpm($array, "hook_taxonomy_term_delete($term->tid)");
726
  if ($term->vid == _forum_access_get_vid()) {
727
    db_delete('forum_access')
728
      ->condition('tid', $term->tid)
729
      ->execute();
730
    variable_del('forum_access_rids'); // clear cache
731
  }
732
}
733

    
734
/**
735
 * Implements hook_user_role_delete().
736
 */
737
function forum_access_user_role_delete($role) {
738
  db_delete('forum_access')
739
    ->condition('rid', $role->rid)
740
    ->execute();
741
  db_delete('node_access')
742
    ->condition('gid', $role->rid)
743
    ->condition('realm', 'forum_access')
744
    ->execute();
745
}
746

    
747
/**
748
 * Check whether the given user is a moderator.
749
 *
750
 * @param $account
751
 *   The user or user ID to check.
752
 * @param $tid
753
 *   ID of the forum to check or NULL to check whether the user is moderator
754
 *   in any forum at all.
755
 */
756
function forum_access_is_moderator($account, $tid = NULL) {
757
  $uid = (is_object($account) ? $account->uid : $account);
758
  return (bool) acl_get_ids_by_user('forum_access', $uid, NULL, ($tid ? $tid : NULL));
759
}
760

    
761
/**
762
 * Return forum.module's forum vocabulary ID.
763
 */
764
function _forum_access_get_vid() {
765
  return variable_get('forum_nav_vocabulary', '');
766
}
767

    
768
/**
769
 * Returns the forum tid or FALSE.
770
 */
771
function _forum_access_get_tid($node) {
772
  return (isset($node->forum_tid) ? $node->forum_tid : FALSE);
773
}
774

    
775
/**
776
 * Saves and returns the $tid.
777
 */
778
function _forum_access_changed_tid($tid = NULL) {
779
  $saved_tid = &drupal_static(__FUNCTION__);
780
  if (!empty($tid)) {
781
    $saved_tid = $tid;
782
  }
783
  return $saved_tid;
784
}
785

    
786
/**
787
 * Returns the ACL ID of the forum.
788
 */
789
function _forum_access_get_acl($tid) {
790
  $acl_id = acl_get_id_by_number('forum_access', $tid);
791
  if (!$acl_id) { // create one
792
    $acl_id = acl_create_new_acl('forum_access', NULL, $tid);
793
    $subselect = db_select('taxonomy_index', 'n');
794
    $subselect
795
      ->fields('n', array('nid'))
796
      ->condition('n.tid', $tid);
797
    acl_add_nodes($subselect, $acl_id, 1, 1, 1);
798
  }
799
  return $acl_id;
800
}
801

    
802
function _forum_access_module_load_include($type) {
803
  static $loaded = array();
804

    
805
  if (!isset($loaded[$type])) {
806
    $path = module_load_include($type, 'forum_access');
807
    $loaded[$type] = drupal_get_path('module', 'forum_access') . "/forum_access.$type";
808
  }
809
  return $loaded[$type];
810
}
811

    
812
/**
813
 * Implements hook_theme().
814
 */
815
function forum_access_theme() {
816
  return array(
817
    'forum_access_table' => array(
818
      'render element' => 'form',
819
      'file' => 'forum_access.admin.inc',
820
    ),
821
  );
822
}
823

    
824
/**
825
 * Implements hook_node_access_explain().
826
 */
827
function forum_access_node_access_explain($row) {
828
  $roles = &drupal_static(__FUNCTION__);
829
  if ($row->realm == 'forum_access') {
830
    if (!isset($roles)) {
831
      $roles = user_roles();
832
    }
833
    if (isset($roles[$row->gid])) {
834
      return array($roles[$row->gid]);
835
    }
836
    return array('(unknown gid)');
837
  }
838
}
839

    
840
/**
841
 * Implements hook_acl_explain().
842
 */
843
function forum_access_acl_explain($acl_id, $name, $number, $users = NULL) {
844
  if (empty($users)) {
845
    return "ACL (id=$acl_id) would grant access to nodes in forum/$number.";
846
  }
847
  return "ACL (id=$acl_id) grants access to nodes in forum/$number to the listed user(s).";
848
}
849