og.module 111 KB
Newer Older
Amitaibu's avatar
Amitaibu committed
1
<?php
2

Amitaibu's avatar
Amitaibu committed
3 4 5 6 7
/**
 * @file
 * Enable users to create and manage groups with roles and permissions.
 */

8
// Add field widget related code.
9
require DRUPAL_ROOT . '/' . drupal_get_path('module', 'og') . '/includes/og.field.inc';
10

Amitaibu's avatar
Amitaibu committed
11 12
/**
 * Define active group content states.
13 14 15
 *
 * When a user has this membership state they are considered to be of
 * "member" role.
Amitaibu's avatar
Amitaibu committed
16
 */
amitaibu's avatar
amitaibu committed
17
define('OG_STATE_ACTIVE', 1);
Amitaibu's avatar
Amitaibu committed
18 19

/**
20 21 22 23 24
 * Define pending group content states. The user is subscribed to the group
 * but isn't an active member yet.
 *
 * When a user has this membership state they are considered to be of
 * "non-member" role.
Amitaibu's avatar
Amitaibu committed
25
 */
amitaibu's avatar
amitaibu committed
26
define('OG_STATE_PENDING', 2);
Amitaibu's avatar
Amitaibu committed
27 28 29

/**
 * Define blocked group content states. The user is rejected from the group.
30 31 32 33
 *
 * When a user has this membership state they are denided access to any
 * group related action. This state, however, does not prevent user to
 * access a group or group content node.
Amitaibu's avatar
Amitaibu committed
34
 */
amitaibu's avatar
amitaibu committed
35
define('OG_STATE_BLOCKED', 3);
Amitaibu's avatar
Amitaibu committed
36 37 38 39

/**
 * Group audience field.
 */
40
define('OG_AUDIENCE_FIELD', 'og_group_ref');
41

42 43 44
/**
 * Group field.
 */
45
define('OG_GROUP_FIELD', 'group_group');
46

47
/**
48
 * Group default roles and permissions field.
49
 */
amitaibu's avatar
amitaibu committed
50
define('OG_DEFAULT_ACCESS_FIELD', 'og_roles_permissions');
51

Amitaibu's avatar
Amitaibu committed
52 53 54
/**
 * The role name of group non-members.
 */
55
define('OG_ANONYMOUS_ROLE', 'non-member');
Amitaibu's avatar
Amitaibu committed
56 57 58 59

/**
 * The role name of group member.
 */
60
define('OG_AUTHENTICATED_ROLE', 'member');
Amitaibu's avatar
Amitaibu committed
61 62 63 64

/**
 * The role name of group administrator.
 */
amitaibu's avatar
amitaibu committed
65
define('OG_ADMINISTRATOR_ROLE', 'administrator member');
Amitaibu's avatar
Amitaibu committed
66

67 68 69 70 71
/**
 * The default group membership type that is the bundle of group membership.
 */
define('OG_MEMBERSHIP_TYPE_DEFAULT', 'og_membership_type_default');

72 73 74 75 76
/**
 * The name of the user's request field in the default group membership type.
 */
define('OG_MEMBERSHIP_REQUEST_FIELD', 'og_membership_request');

77 78 79
/**
 * Implements hook_help().
 */
80 81
function og_help($path, $arg) {
  switch ($path) {
82 83
    case 'admin/help#og':
      $path = drupal_get_path('module', 'og');
84 85
      $output  = '<p>' . t("Read the <a href='@url'>README.md</a> file in the Organic groups module directory.", array('@url' => "/$path/README.md")) . '</p>';
      $output .= '<p>' . t("Information about Organic Groups can also be found on the module's <a href='@og'>documentation page</a>.", array('@og' => 'http://drupal.org/documentation/modules/og')) . '</p>';
86 87 88 89
      return $output;
  }
}

90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
/**
 * Implements hook_menu().
 */
function og_menu() {
  $items = array();

  // Add our own autocomplete callback to pass also the group and
  // vocabulary info.
  $items['og/autocomplete/single/%/%/%/%'] = array(
    'title' => 'Entity Reference Autocomplete',
    'page callback' => 'og_entityreference_autocomplete_callback',
    'page arguments' => array(2, 3, 4, 5, 6),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['og/autocomplete/tags/%/%/%/%'] = array(
    'title' => 'Entity Reference Autocomplete',
    'page callback' => 'og_entityreference_autocomplete_callback',
    'page arguments' => array(2, 3, 4, 5, 6),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );

  return $items;
}

Amitai's avatar
Amitai committed
116 117 118
/**
 * Implements hook_entity_info().
 */
amitaibu's avatar
amitaibu committed
119
function og_entity_info() {
120
  $items['og_membership_type'] = array(
121
    'label' => t('OG membership type'),
122
    'controller class' => 'EntityAPIControllerExportable',
123
    'entity class' => 'OgMembershipType',
124
    'base table' => 'og_membership_type',
125 126 127
    'fieldable' => TRUE,
    'entity keys' => array(
      'id' => 'id',
128 129
      'label' => 'description',
      'name' => 'name',
130
    ),
131 132 133 134 135
    'exportable' => TRUE,
    'export' => array(
      'default hook' => 'default_og_membership_type',
    ),
    'bundle of' => 'og_membership',
136
    'module' => 'og',
137
    'metadata controller class' => 'EntityDefaultMetadataController',
138
    'views controller class' => 'EntityDefaultViewsController',
139
    'access callback' => 'og_membership_type_access',
140
    'entity cache' => module_exists('entitycache'),
141
  );
142

143
  if (class_exists('OgMembershipTypeUIController')) {
144 145 146 147 148 149 150 151 152 153 154
    $items['og_membership_type'] += array(
      // Enable the entity API's admin UI.
      'admin ui' => array(
        // TODO: This path doesn't exist before OG-ui.
        'path' => 'admin/config/group/group-membership',
        'file' => 'includes/og.admin.inc',
        'controller class' => 'OgMembershipTypeUIController',
      ),
    );
  }

155
  $items['og_membership'] = array(
156
    'label' => t('OG membership'),
Amitaibu's avatar
Amitaibu committed
157
    'entity class' => 'OgMembership',
158 159 160
    'controller class' => 'EntityAPIController',
    'base table' => 'og_membership',
    'fieldable' => TRUE,
161
    'entity keys' => array(
Amitaibu's avatar
Amitaibu committed
162
      'id' => 'id',
163 164
      // The message has no label.
      'label' => FALSE,
165
      'bundle' => 'type',
166
    ),
167
    'label callback' => 'og_membership_label',
168 169 170 171
    'bundles' => array(),
    'bundle keys' => array(
      'bundle' => 'name',
    ),
172
    'module' => 'og',
173 174
    'metadata controller class' => 'OgMembershipMetadataController',
    'views controller class' => 'OgMembershipViewsController',
175
    'access callback' => 'og_membership_access',
176
    'entity cache' => module_exists('entitycache'),
177
  );
Amitai's avatar
Amitai committed
178

179
  // Add bundle info but bypass entity_load() as we cannot use it here.
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
  if (db_table_exists('og_membership_type')) {
    $memberships = db_select('og_membership_type', 'g')
      ->fields('g')
      ->execute()
      ->fetchAllAssoc('name');

    foreach ($memberships as $type_name => $type) {
      $items['og_membership']['bundles'][$type_name] = array(
        'label' => $type->name,
        'admin' => array(
          'path' => 'admin/config/group/group-membership/manage/%og_membership_type',
          'real path' => 'admin/config/group/group-membership/manage/' . $type->name,
          'bundle argument' => 5,
          'access arguments' => array('administer group'),
        ),
      );
    }
197
  }
198 199

  return $items;
200 201
}

202 203 204 205 206
/**
 * Implements hook_entity_property_info().
 */
function og_entity_property_info() {
  $info = array();
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244

  // Add OG membership metadata for every bundle that is a group content.
  foreach (og_get_all_group_content_bundle() as $entity_type => $bundles) {
    foreach ($bundles as $bundle => $bundle_value) {
      $info[$entity_type]['bundles'][$bundle]['properties']['og_membership'] = array(
        'label' => t("OG memberships"),
        'type' => 'list<og_membership>',
        'description' => t("A list of all OG memberships of the @name entity.", array('@name' => $entity_type)),
        'getter callback' => 'og_get_og_membership_properties',
      );

      // Add per-state properties.
      $general = $info[$entity_type]['bundles'][$bundle]['properties']['og_membership'];
      foreach (og_group_content_states() as $state => $state_label) {
        $params = array('@state' => $state_label, '@name' => $entity_type);
        $info[$entity_type]['bundles'][$bundle]['properties']['og_membership__' . $state] = $general;
        $info[$entity_type]['bundles'][$bundle]['properties']['og_membership__' . $state]['label'] = t('@state OG membership', $params);
        $info[$entity_type]['bundles'][$bundle]['properties']['og_membership__' . $state]['description'] = t("A list of all OG memberships of the @name entity with @state state.", $params);
      }

      // Add OG membership per field in a bundle.
      foreach (og_get_group_audience_fields($entity_type, $bundle) as $field_name => $label) {
        $params = array('@label' => $label);
        $field_info = field_info_field($field_name);
        $group_type = $field_info['settings']['target_type'];
        $info[$entity_type]['bundles'][$bundle]['properties'][$field_name . '__og_membership'] = array(
          'label' => t('OG membership from field @label', $params),
          'type' => 'list<og_membership>',
          // The bundle in this context means the OG membership type.
          'bundle' => $field_info['settings']['handler_settings']['membership_type'],
          'description' => t('A list of all OG memberships registered in field @label.', $params),
          'getter callback' => 'og_get_field_og_membership_properties',
        );


        // Add per-state properties.
        $general = $info[$entity_type]['bundles'][$bundle]['properties'][$field_name . '__og_membership'];
        foreach (og_group_content_states() as $state => $state_label) {
245 246 247 248 249
          $params = array(
            '@label' => $label,
            '@label' => $label,
            '@state' => $state_label,
          );
250 251 252 253 254 255
          $info[$entity_type]['bundles'][$bundle]['properties'][$field_name . '__og_membership__' . $state] = $general;
          $info[$entity_type]['bundles'][$bundle]['properties'][$field_name . '__og_membership__' . $state]['label'] = t('@state OG memberships from field @label', $params);
          $info[$entity_type]['bundles'][$bundle]['properties'][$field_name . '__og_membership__' . $state]['description'] = t('A list of all OG memberships with @state registered in field @label.', $params);
        }
      }
    }
256
  }
Amitaibu's avatar
Amitaibu committed
257

258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
  foreach (og_get_all_group_bundle() as $entity_type => $bundles) {
    foreach ($bundles as $bundle => $bundle_value) {
      $info[$entity_type]['bundles'][$bundle]['properties']['members'] = array(
          'label' => t("Group members"),
          'type' => 'list<user>',
          'description' => t("A list group members of the @name entity.", array('@name' => $entity_type)),
          'getter callback' => 'og_get_group_members_properties',
        );

        // Add per-state properties.
        $general = $info[$entity_type]['bundles'][$bundle]['properties']['members'];
        foreach (og_group_content_states() as $state => $state_label) {
          $params = array('@state' => $state_label, '@name' => $entity_type);
          $info[$entity_type]['bundles'][$bundle]['properties']['members__' . $state] = $general;
          $info[$entity_type]['bundles'][$bundle]['properties']['members__' . $state]['label'] = t('@state group members', $params);
          $info[$entity_type]['bundles'][$bundle]['properties']['members__' . $state]['description'] = t("A list of all users of the @name entity with @state state.", $params);
        }
    }
Amitaibu's avatar
Amitaibu committed
276 277
  }

278 279 280 281
  return $info;
}

/**
282
 * Property getter callback for group members.
283 284
 *
 * @see og_entity_property_info()
285
 */
286 287 288 289 290 291 292 293 294 295 296
function og_get_group_members_properties($entity, array $options, $name, $type) {
  $args = explode('__', $name);
  $state = !empty($args[1]) ? $args[1] : FALSE;
  list($id) = entity_extract_ids($type, $entity);

  $cache = &drupal_static(__FUNCTION__, array());
  if (isset($cache[$type][$id][$state])) {
    // Return the cached result.
    return $cache[$type][$id][$state];
  }
  $cache[$type][$id][$state] = array();
297

298 299 300 301 302 303
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', 'og_membership')
    ->propertyCondition('group_type', $type, '=')
    ->propertyCondition('gid', $id, '=')
    ->propertyCondition('entity_type', 'user', '=');
304

305 306 307
  if ($state) {
    $query->propertyCondition('state', $state, '=');
  }
Amitaibu's avatar
Amitaibu committed
308

309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
  $result = $query->execute();
  if (!empty($result['og_membership'])) {
    $og_memberships = og_membership_load_multiple(array_keys($result['og_membership']));
    foreach ($og_memberships as $og_membership) {
      $cache[$type][$id][$state][] = $og_membership->etid;

    }
  }
  return $cache[$type][$id][$state];
}

/**
 * Property getter callback for OG membership.
 *
 * @see og_entity_property_info()
 */
function og_get_og_membership_properties($entity, array $options, $name, $type) {
  // Get the state from name, if exists.
  if ($name == 'og_membership') {
    $state = array();
  }
  else {
    $args = explode('__', $name);
    $state = array($args[1]);
  }

  $ids = array();

  if ($gids = og_get_entity_groups($type, $entity, $state)) {
    $ids = array();
    foreach ($gids as $group_type => $values) {
340
      $ids = array_merge($ids, array_keys($values));
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
    }
  }

  return $ids;
}

/**
 * Property getter callback for OG membership per field.
 *
 * @see og_entity_property_info()
 */
function og_get_field_og_membership_properties($entity, array $options, $name, $type) {
  $args = explode('__', $name);
  // Field name might have double underscore as-well, so we need to make
  // sure we get it right.
  $last_char = substr($name, -1);
  $state = is_numeric($last_char) ? $last_char : FALSE;

  // The number of characters to ignore in the name (i.e. remove the
  // "__og_membership" or "__og_membership__0").
  $remove_char = $state ? -18 : -15;
  $field_name = substr($name, 0, $remove_char);

  $field_name = $args[0];
365 366
  $field = field_info_field($field_name);
  $states = count($args) == 2 ? FALSE : array($args[2]);
367

368 369
  $result = og_get_entity_groups($type, $entity, $states, $field_name);
  $target_type = $field['settings']['target_type'];
370

371
  return !empty($result[$target_type]) ? array_keys($result[$target_type]) : array();
372 373 374
}

/**
375 376 377
 * Getter callback to load the 'entity' or 'group' property from OG membership.
 *
 * We have to return the entity wrapped.
378
 */
379 380 381 382 383 384 385
function og_entity_getter($object, array $options, $property_name) {
  switch ($property_name) {
    case 'entity':
      return entity_metadata_wrapper($object->entity_type, $object->etid);
    case 'group':
      return entity_metadata_wrapper($object->group_type, $object->gid);
  }
386 387 388
}

/**
389 390
 * Entity property info setter callback to set the "entity" property for groups
 * and memberships.
391 392 393 394 395
 *
 * As the property is of type entity, the value will be passed as a wrapped
 * entity.
 */
function og_entity_setter($object, $property_name, $wrapper) {
396 397 398 399 400 401 402 403 404 405
  switch ($property_name) {
    case 'entity':
      $object->entity_type = $wrapper->type();
      $object->etid = $wrapper->getIdentifier();
      break;
    case 'group':
      $object->group_type = $wrapper->type();
      $object->gid = $wrapper->getIdentifier();
      break;
  }
406 407
}

408
/**
409
 * Implements hook_default_og_membership_type().
410 411 412 413 414 415 416 417 418 419 420
 */
function og_default_og_membership_type() {
  $items = array();
  $items['og_membership_type_default'] = entity_import('og_membership_type', '{
    "name" : "og_membership_type_default",
    "description" : "Default",
    "rdf_mapping" : []
  }');
  return $items;
}

421 422 423
/**
 * Implements hook_modules_uninstalled().
 */
amitaibu's avatar
amitaibu committed
424
function og_modules_uninstalled($modules) {
amitaibu's avatar
amitaibu committed
425 426
  // Delete module's permissions.
  og_permissions_delete_by_module($modules);
427 428
}

429
/**
430
 * Implements hook_ctools_plugin_directory().
431 432
 */
function og_ctools_plugin_directory($module, $plugin) {
433
  if ($module == 'ctools') {
434 435
    return 'plugins/' . $plugin;
  }
436 437 438
  elseif ($module == 'entityreference') {
    return "plugins/entityreference/$plugin";
  }
439 440
}

441 442 443
/**
 * Implements hook_permission().
 */
amitaibu's avatar
amitaibu committed
444
function og_permission() {
445
  return array(
446
    'administer group' =>  array(
447
      'title' => t('Administer Organic groups permissions'),
448 449 450 451 452 453
      'description' => t('Administer all groups and permissions.'),
    ),
  );
}

/**
454
 * Implements hook_og_permission().
455
 */
amitaibu's avatar
amitaibu committed
456
function og_og_permission() {
457 458
  // Generate standard node permissions for all applicable node types.
  $perms = array();
459 460 461

  $perms['update group'] = array(
    'title' => t('Edit group'),
462
    'description' => t('Edit the group. Note: This permission controls only node entity type groups.'),
463
    'default role' => array(OG_ADMINISTRATOR_ROLE),
464 465 466
  );
  $perms['administer group'] = array(
    'title' => t('Administer group'),
467
    'description' => t('Manage group members and content in the group.'),
468
    'default role' => array(OG_ADMINISTRATOR_ROLE),
469
    'restrict access' => TRUE,
470 471
  );

472
  foreach (node_permissions_get_configured_types() as $type) {
473
    $perms = array_merge($perms, og_list_permissions($type));
474 475
  }

476
  return $perms;
477 478 479 480
}


/**
481
 * Implements hook_og_default_roles().
482
 */
amitaibu's avatar
amitaibu committed
483
function og_og_default_roles() {
amitaibu's avatar
amitaibu committed
484
  return array(OG_ADMINISTRATOR_ROLE);
485 486 487
}

/**
488
 * Implements hook_node_access().
489
 */
amitaibu's avatar
amitaibu committed
490
function og_node_access($node, $op, $account) {
491
  $type = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
492

493 494 495 496 497 498 499 500
  if ($op == 'create' && og_is_group_content_type('node', $type)) {
    // Save some legwork if the user has the core permission and strict node
    // access is not set.
    if (!variable_get('og_node_access_strict', TRUE) && user_access("create $type content", $account)) {
      // We just ignore: core access will take care of it.
      return NODE_ACCESS_IGNORE;
    }

501
    if (user_access('administer group', $account)) {
502 503
      return NODE_ACCESS_ALLOW;
    }
504 505 506 507
    // We can't check if user has create permissions using og_user_access(), as
    // there is no group context. However, we can check if there are any groups
    // the user will be able to select, and if not, we don't allow access.
    // @see OgSelectionHandler::getReferencableEntities()
508
    $required = FALSE;
509
    foreach (og_get_group_audience_fields('node', $type) as $field_name => $label) {
510 511
      $field = field_info_field($field_name);
      $instance = field_info_instance('node', $field_name, $type);
512 513 514
      // Set the "field mode" to default, before passing it to the
      // selection handler.
      $instance['field_mode'] = 'default';
515
      if (entityreference_get_selection_handler($field, $instance)->countReferencableEntities()) {
516 517 518
        return NODE_ACCESS_ALLOW;
      }

519 520 521 522 523
      // Allow users to create content outside of groups, if none of the
      // audience fields is required.
      if ($instance['required']) {
        $required = TRUE;
      }
524
    }
525 526 527 528 529 530 531
    // If no group audience field is required, we ignore.
    if (!$required) {
      return NODE_ACCESS_IGNORE;
    }

    // Otherwise, ignore or deny based on whether strict node access is set.
    return variable_get('og_node_access_strict', TRUE) ? NODE_ACCESS_DENY : NODE_ACCESS_IGNORE;
532 533
  }
  elseif (in_array($op, array('update', 'delete'))) {
534 535 536 537 538
    $access = og_user_access_entity('administer group', 'node', $node, $account);

    if (is_null($access)) {
      // The node isn't in an OG context, so no need to keep testing.
      return NODE_ACCESS_IGNORE;
539
    }
540 541 542 543 544
    else {
      $access = $access ||
        // Any content.
        og_user_access_entity("$op any $type content", 'node', $node, $account) ||
        // Own content.
545
        ($account->uid == $node->uid && og_user_access_entity("$op own $type content", 'node', $node, $account));
546
    }
547

548
    if (!$access && $op == 'update' && og_is_group('node', $node)) {
549 550 551 552 553 554 555 556 557
      // The node is a group, so check "update group" permission.
      $access = og_user_access_entity('update group', 'node', $node, $account);
    }

    if ($access) {
      return NODE_ACCESS_ALLOW;
    }

    // Check if OG should explicitly deny access or not.
Amitaibu's avatar
Amitaibu committed
558
    return variable_get('og_node_access_strict', TRUE) ? NODE_ACCESS_DENY : NODE_ACCESS_IGNORE;
559
  }
560

561
  return NODE_ACCESS_IGNORE;
562 563 564
}

/**
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
 * Implements hook_field_access().
 *
 * Hide group-audience fields from user's edit profile for non-privileged users.
 */
function og_field_access($op, $field, $entity_type, $entity, $account) {
  global $user;

  if (empty($entity)) {
    // We are in field settings page.
    return;
  }


  if (!$user->uid) {
    // User is anonymous, and user register might try to add the
    // group-audience field.
    return;
  }

  if ($op != 'edit') {
    return;
  }

  $field_name = $field['field_name'];
  list($id, $vid, $bundle_name) = entity_extract_ids($entity_type, $entity);
590
  $instance = field_info_instance($entity_type, $field_name, $bundle_name);
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
  if ($field_name == OG_GROUP_FIELD) {
    $wrapper = entity_metadata_wrapper($entity_type, $entity);
    if ($wrapper->getIdentifier() && !$wrapper->{OG_GROUP_FIELD}->value()) {
      // Entity isn't an active group.
      return;
    }
    if (!empty($instance['widget']['settings']['og_hide'])) {
      return FALSE;
    }
    return;
  }

  if (!og_is_group_audience_field($field_name)) {
    return;
  }

  $field = field_info_field($field_name);
  $settings = $field['settings']['handler_settings'];

610 611
  // Check if we are editing the user entity.
  if ($entity_type == 'user') {
612 613 614 615
    if (!empty($instance['settings']['behaviors']['og_widget']['access_override'])) {
      return;
    }

616 617 618 619 620 621 622
    return user_access('administer group', $account);
  }
}


/**
 * Implements hook_views_api().
623
 */
624 625
function og_views_api() {
  return array(
626
    'api' => 3,
627
    'path' => drupal_get_path('module', 'og') . '/includes/views',
628 629
  );
}
630

631

632
/**
633 634 635 636 637
 * Implements hook_field_create_instance().
 *
 * - Create default OG roles per entity-type and bundle.
 * - Create a group audience field on the user's entity, referencing the first
 *   group defined.
638
 */
639 640 641 642 643
function og_field_create_instance($instance) {
  if ($instance['field_name'] != OG_GROUP_FIELD) {
    return;
  }

644 645
  // Create default roles per entity-type per bundle.
  og_roles_override($instance['entity_type'], $instance['bundle'], 0);
646 647

  // Check if we need to add a group audience on the user's entity.
648
  // We add a different field, so each field can be set differently.
649 650 651 652 653 654 655 656 657 658 659 660
  $entity_type = $instance['entity_type'];
  $bundle = $instance['bundle'];
  foreach (array_keys(og_get_group_audience_fields()) as $field_name) {
    $field = field_info_field($field_name);

    if ($field['settings']['target_type'] == $entity_type  && empty($field['settings']['handler_settings']['target_bundles'])) {
      return;
    }

    if ($field['settings']['target_type'] == $entity_type && in_array($bundle, $field['settings']['handler_settings']['target_bundles'])) {
      return;
    }
661
  }
662 663 664 665 666 667 668 669 670 671 672 673

  // If we reached here, it means we need to create a field.
  // Pick an unused name.
  $field_name = substr("og_user_$entity_type", 0, 32);
  $i = 1;
  while (field_info_field($field_name)) {
    $field_name = substr("og_user_$entity_type", 0, 32 - strlen($i)) . $i;
    ++$i;
  }

  $og_field = og_fields_info(OG_AUDIENCE_FIELD);
  $og_field['field']['settings']['target_type'] = $entity_type;
674 675 676 677 678 679 680 681 682
  if ($entity_type == 'node') {
    $og_field['instance']['label'] = t('Group membership');
  }
  else {
    $entity_info = entity_get_info($entity_type);
    $og_field['instance']['label'] = t('@label group membership', array(
      '@label' => $entity_info['label'],
    ));
  }
683 684 685 686 687 688 689

  // If the user entity type has multiple bundles, make sure to attach a field
  // instance to all of them.
  $entity_info = entity_get_info('user');
  foreach (array_keys($entity_info['bundles']) as $user_bundle) {
    og_create_field($field_name, 'user', $user_bundle, $og_field);
  }
690
}
Amitaibu's avatar
Amitaibu committed
691

692
/**
693 694 695 696
 * Implements field_delete_instance().
 *
 * - Invalidate OG's static cache if a group-audience field is deleted.
 * - Delete the default OG roles per entity-type and bundle.
697
 */
698 699 700 701 702 703 704 705 706
function og_field_delete_instance($instance) {
  if (og_is_group_audience_field($instance['field_name'])) {
    og_invalidate_cache();
  }

  if ($instance['field_name'] != OG_GROUP_FIELD) {
    return;
  }

707 708
  // Get the per-bundle roles.
  $roles = og_roles($instance['entity_type'], $instance['bundle']);
709 710
  foreach ($roles as $rid => $name) {
    og_role_delete($rid);
711 712 713
  }
}

714
/**
715
 * Implements hook_field_attach_form().
716
 */
717 718 719
function og_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  list(,, $bundle) = entity_extract_ids($entity_type, $entity);

720 721 722 723
  if (!isset($form['#entity'])) {
    $form['#entity'] = $entity;
  }

724 725 726 727 728
  if (og_get_group_audience_fields($entity_type, $bundle)) {
    $form['#validate'][] = 'og_form_group_reference_validate';
  }

  if ($entity_type == 'user' || !og_is_group_type($entity_type, $bundle)) {
729 730
    return;
  }
731 732 733 734 735 736 737 738 739 740 741 742 743 744
  $form['#validate'][] = 'og_form_group_manager_validate';
}

/**
 * Validate handler; Make sure group-only content permissions are honored.
 *
 * If a user does not have site-wide node permissions, throw an error if they
 * try to post site-wide instead of within a group.
 *
 * Note: This function does not check group -access- just if a group has been
 * Selected.
 */
function og_form_group_reference_validate($form, &$form_state) {
  global $user;
745
  $entity_type = $form['#entity_type'];
746 747 748 749 750 751
  if (empty($form_state[$entity_type])) {
    // We are inside field settings page.
    return;
  }

  $account = user_load($user->uid);
752
  $bundle = $form['#bundle'];
753 754
  $entity = $form['#entity'];
  list($id) = entity_extract_ids($entity_type, $entity);
755

756 757 758 759 760 761 762 763 764 765 766 767 768
  $op = empty($id) ? 'create' : 'update';

  if ($entity_type == 'node') {
    $node = empty($id) ? $bundle : $entity;
    // We call node_node_access() directly as we just want to check the
    // permissions using user_acces().
    if (node_node_access($node, $op, $account)) {
      // User has site-wide permissions to create or edit the node.
      return;
    }
  }
  elseif (entity_access($op, $entity_type, $entity, $account)) {
    // User has site-wide permissions to create or edit the entity.
769 770
    return;
  }
771 772 773 774 775 776 777 778 779 780

  foreach (array_keys(og_get_group_audience_fields($entity_type, $bundle)) as $field_name) {
    // If there is at least one group selected, return.
    if (!empty($form_state['values'][$field_name][LANGUAGE_NONE])) {
      return;
    }
  }

  // No group selected, throw an error.
  form_set_error('og', t('You must select one or more groups for this content.'));
781 782 783 784 785 786
}

/**
 * Validate handler; Make sure a group can be created.
 *
 * We check if the group manager has a matching group-audience field for the
787
 * OG membership to be created in.
788 789 790 791
 */
function og_form_group_manager_validate($form, &$form_state) {
  $entity_type = $form['#entity_type'];
  $bundle = $form['#bundle'];
792 793 794 795
  if (empty($form_state[$entity_type])) {
    // We are inside field settings page.
    return;
  }
796
  $entity = $form_state[$entity_type];
797
  $langcode = isset($form_state['values']['language']) ? $form_state['values']['language'] : LANGUAGE_NONE;
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850

  if (!isset($form_state['values']['uid']) || !isset($entity->uid)) {
    // There is no user ID property on the entity.
    return;
  }

  if (isset($form_state['values'][OG_GROUP_FIELD]) && empty($form_state['values'][OG_GROUP_FIELD][$langcode][0]['value'])) {
    // Not a group.
    return;
  }

  if (!isset($form_state['values'][OG_GROUP_FIELD])) {
    // Field doesn't appear in the form, so it is probably hidden by
    // hook_field_access(). So check the default value of the field.
    $field = field_info_field(OG_GROUP_FIELD);
    $instance = field_info_instance($entity_type, OG_GROUP_FIELD, $bundle);

    $items = field_get_default_value($entity_type, $entity, $field, $instance, $langcode);
    if (empty($items[0]['value'])) {
      // Default value is not a group.
      return;
    }
  }

  if ($entity_type == 'node') {
    // A user might assign the node author by entering a user name in the
    // node form, which we then need to translate to a user ID.
    // However, this happens later on, in node_submit(), so we do a special
    // check for the node entity.
    if (!$account = user_load_by_name($form_state['values']['name'])) {
      // Invalid username.
      return;
    }
  }
  else {
    $account = user_load($form_state['values']['uid']);
  }

  list($id) = entity_extract_ids($entity_type, $entity);

  if ($id && $entity->uid == $account->uid) {
    // The entity's user ID hasn't changed.
    return;
  }

  if ($access = og_get_best_group_audience_field('user', $account, $entity_type, $bundle)) {
    // Matching group audience field found.
    return;
  }

  form_error($form, t("Can't save entity as group, because user @name can't be subscribed to group and become a manager.", array('@name' => format_username($account))));
}

Amitaibu's avatar
Amitaibu committed
851
/**
852
 * Implements hook_entity_insert().
Amitaibu's avatar
Amitaibu committed
853
 */
854 855 856 857 858 859 860 861
function og_entity_insert($entity, $entity_type) {
  if (!og_is_group($entity_type, $entity)) {
    return;
  }
  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
  if (!empty($entity->uid)) {
    // Subscribe the group manager.
    og_group($entity_type, $id, array('entity' => $entity->uid));
862 863 864 865 866 867 868
    // Assign roles to group manager.
    $name = 'og_group_manager_default_rids_' . $entity_type . '_' . $bundle;
    if ($rids = variable_get($name)) {
      foreach ($rids as $rid) {
        og_role_grant($entity_type, $id, $entity->uid, $rid);
      }
    }
869 870 871
  }
  if (!og_is_group_default_access($entity_type, $entity)) {
    // Override default roles.
872
    og_roles_override($entity_type, $bundle, $id);
873 874
  }
}
875

876 877 878 879 880
/**
 * Implements hook_entity_update().
 */
function og_entity_update($entity, $entity_type) {
  if (!og_is_group($entity_type, $entity)) {
881 882
    return;
  }
883

884
  list($id, , $bundle) = entity_extract_ids($entity_type, $entity);
885
  if (!empty($entity->uid) && !og_is_member($entity_type, $id, 'user', $entity->uid, array())) {
886 887
    // Subscribe the group manager, in case the owner changed.
    og_group($entity_type, $id, array('entity' => $entity->uid));
888 889 890 891 892 893 894
    // Assign roles to group manager.
    $name = 'og_group_manager_default_rids_' . $entity_type . '_' . $bundle;
    if ($rids = variable_get($name)) {
      foreach ($rids as $rid) {
        og_role_grant($entity_type, $id, $entity->uid, $rid);
      }
    }
895
  }
896
  $original_entity = $entity->original;
897 898
  $property = OG_DEFAULT_ACCESS_FIELD;

899
  if (!empty($entity->{$property}) && $entity->{$property} != $original_entity->{$property}) {
900 901
    if (!og_is_group_default_access($entity_type, $entity)) {
      // Override default roles.
902
      og_roles_override($entity_type, $bundle, $id);
903 904 905 906 907 908
    }
    else {
      // Delete overridden roles.
      og_delete_user_roles_by_group($entity_type, $entity);
    }
  }
909 910
}

911 912 913 914 915 916 917 918 919 920 921 922 923 924
/**
 * Implements hook_field_attach_insert().
 */
function og_field_attach_insert($entity_type, $entity) {
  _og_update_entity_fields($entity_type, $entity);
}

/**
 * Implements hook_field_attach_update().
 */
function og_field_attach_update($entity_type, $entity) {
  _og_update_entity_fields($entity_type, $entity);
}

925 926 927 928 929 930 931 932 933 934 935 936 937
/**
 * Update the field values in the entity, to reflect the membership.
 *
 * This is used to allow other modules that save a new/ existing entity
 * to act on the field values, even before hook_field_load() is called.
 */
function _og_update_entity_fields($entity_type, $entity) {
  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
  if (!og_is_group_content_type($entity_type, $bundle)) {
    return;
  }

  $wrapper = entity_metadata_wrapper($entity_type, $entity);
938
  foreach (og_get_group_audience_fields($entity_type, $bundle) as $field_name => $label) {
939 940 941 942
    $field = field_info_field($field_name);
    $gids = array();
    if ($field['cardinality'] == 1) {
      if ($og_membership = $wrapper->{$field_name . '__og_membership'}->value()) {
943 944
        // Wrapper return an array.
        $gids = $og_membership[0]->gid;
945 946 947
      }
    }
    else {
948
      $target_type = $field['settings']['target_type'];
949
      $gids = og_get_entity_groups($entity_type, $entity, array(), $field_name);
950
      $gids = !empty($gids[$target_type]) ? array_values($gids[$target_type]) : array();
951 952 953 954 955 956 957 958
    }
    if ($gids) {
      $wrapper->{$field_name}->set($gids);
    }
  }
}


959
/**
960
 * Implements hook_entity_delete().
961
 */
962
function og_entity_delete($entity, $entity_type) {
963 964 965
  list($id, , $bundle) = entity_extract_ids($entity_type, $entity);
  if (og_is_group($entity_type, $entity)) {
    og_delete_user_roles_by_group($entity_type, $entity);
966
    og_membership_delete_by_group($entity_type, $entity);
967 968 969 970 971 972 973
  }
  if (og_is_group_content_type($entity_type, $bundle)) {
    // As the field attachers are called after hook_entity_presave() we
    // can't delete the OG memberships here. So we just mark the entity
    // as being deleted, and we will do the actual delete in
    // OgBehaviorHandler::delete().
    $entity->delete_og_membership = TRUE;
974
  }
975
}
976

977 978 979
/**
 * Implements hook_og_membership_insert().
 */
980 981 982
function og_og_membership_insert($og_membership) {
  if ($og_membership->entity_type == 'user' && module_exists('rules')) {
    rules_invoke_event('og_user_insert', $og_membership, entity_metadata_wrapper('user', $og_membership->etid));
983 984 985 986 987 988
  }
}

/**
 * Implements hook_og_membership_update().
 */
989 990 991 992
function og_og_membership_update($og_membership) {
  if ($og_membership->entity_type == 'user' && module_exists('rules')) {
    if ($og_membership->original->state != OG_STATE_ACTIVE && $og_membership->state == OG_STATE_ACTIVE) {
      rules_invoke_event('og_user_approved', $og_membership, entity_metadata_wrapper('user', $og_membership->etid));
993
    }
994 995
    if ($og_membership->original->state != OG_STATE_BLOCKED && $og_membership->state == OG_STATE_BLOCKED) {
      rules_invoke_event('og_user_blocked', $og_membership, entity_metadata_wrapper('user', $og_membership->etid));
996 997 998
    }
  }
}
Amitaibu's avatar
Amitaibu committed
999

amitaibu's avatar
amitaibu committed
1000 1001 1002
/**
 * Implements hook_og_membership_delete().
 */
1003 1004
function og_og_membership_delete($og_membership) {
  if ($og_membership->entity_type != 'user')  {
1005 1006 1007
    return;
  }

1008
  // Remove possible records in the {og_users_roles} table.
1009
  db_delete('og_users_roles')
1010 1011 1012
    ->condition('uid', $og_membership->etid)
    ->condition('gid', $og_membership->gid)
    ->condition('group_type', $og_membership->group_type)
1013
    ->execute();
1014 1015 1016 1017

  if (module_exists('rules'))  {
    rules_invoke_event('og_user_delete', $og_membership, entity_metadata_wrapper('user', $og_membership->etid));
  }
amitaibu's avatar
amitaibu committed
1018 1019
}

1020
/**
1021
 * Implements hook_og_fields_info().
1022
 */
amitaibu's avatar
amitaibu committed
1023
function og_og_fields_info() {
amitaibu's avatar
amitaibu committed
1024
  $items[OG_GROUP_FIELD] = array(
1025 1026 1027
    'type' => array('group'),
    'description' => t('Determine if this should be a group.'),
    'field' => array(
amitaibu's avatar
amitaibu committed
1028
      'field_name' => OG_GROUP_FIELD,
1029 1030
      'type' => 'list_boolean',
      'cardinality' => 1,
1031
      'settings' => array(
1032
        'allowed_values' => array(0 => 'Not a group', 1 => 'Group'),
1033 1034
        'allowed_values_function' => '',
      ),
1035 1036
    ),
    'instance' => array(
1037 1038 1039 1040 1041
      'label' => t('Group'),
      'description' => t('Determine if this is an OG group.'),
      'display_label' => 1,
      'widget' => array(
        'module' => 'options',
1042 1043 1044
        'settings' => array(
          'og_hide' => TRUE,
        ),
1045 1046 1047
        'type' => 'options_onoff',
        'weight' => 0,
      ),
1048
      'default_value' => array(0 => array('value' => 1)),
1049
      'view modes' => array(
1050
        'full' => array(
amitaibu's avatar
amitaibu committed
1051
          'label' => t('Full'),
amitaibu's avatar
amitaibu committed
1052
          'type' => 'og_group_subscribe',
amitaibu's avatar
amitaibu committed
1053
          'custom settings' => FALSE,
1054 1055
        ),
        'teaser' => array(
amitaibu's avatar
amitaibu committed
1056
          'label' => t('Teaser'),
amitaibu's avatar
amitaibu committed
1057
          'type' => 'og_group_subscribe',
amitaibu's avatar
amitaibu committed
1058
          'custom settings' => FALSE,
1059 1060 1061 1062 1063
        ),
      ),
    ),
  );

amitaibu's avatar
amitaibu committed
1064
  $items[OG_DEFAULT_ACCESS_FIELD] = array(
1065 1066 1067
    'type' => array('group'),
    'description' => t('Determine if group should use default roles and permissions.'),
    'field' => array(
amitaibu's avatar
amitaibu committed
1068
      'field_name' => OG_DEFAULT_ACCESS_FIELD,
1069 1070
      'type' => 'list_boolean',
      'cardinality' => 1,
1071
      'settings' => array('allowed_values' => array(0 => 'Use default roles and permissions', 1 => 'Override default roles and permissions'), 'allowed_values_function' => ''),
1072 1073 1074
    ),
    'instance' => array(
      'label' => t('Group roles and permissions'),
1075 1076 1077 1078 1079
      'widget' => array(
        'module' => 'options',
        'settings' => array(),
        'type' => 'options_select',
      ),
Amitaibu's avatar
Amitaibu committed
1080 1081 1082
      'required' => TRUE,
      // Use default role and permissions as default value.
      'default_value' => array(0 => array('value' => 0)),
1083
      'view modes' => array(
1084
        'full' => array(
amitaibu's avatar
amitaibu committed
1085
          'label' => t('Full'),
1086
          'type' => 'list_default',
amitaibu's avatar
amitaibu committed
1087
          'custom settings' => FALSE,
1088 1089
        ),
        'teaser' => array(
amitaibu's avatar
amitaibu committed
1090
          'label' => t('Teaser'),
1091
          'type' => 'list_default',
amitaibu's avatar
amitaibu committed
1092
          'custom settings' => FALSE,
1093 1094 1095 1096 1097
        ),
      ),
    ),
  );

amitaibu's avatar
amitaibu committed
1098
  $items[OG_AUDIENCE_FIELD] = array(
1099
    'multiple' => TRUE,
1100 1101 1102
    'type' => array('group content'),
    'description' => t('Determine to which groups this group content is assigned to.'),
    'field' => array(
amitaibu's avatar
amitaibu committed
1103
      'field_name' => OG_AUDIENCE_FIELD,
1104
      'type' => 'entityreference',
1105
      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119
      'settings' => array(
        'handler' => 'og',
        'handler_submit' => 'Change handler',
        'handler_settings' => array(
          'behaviors' => array(
            'og_behavior' => array(
              'status' => TRUE,
            ),
          ),
          'target_bundles' => array(),
          'membership_type' => OG_MEMBERSHIP_TYPE_DEFAULT,
        ),
        'target_type' => 'node',
      ),
1120 1121 1122
    ),
    'instance' => array(
      'label' => t('Groups audience'),
1123
      'widget' => array(
1124 1125
        'type' => 'og_complex',
        'module' => 'og',
1126
        'settings' => array(),
1127 1128 1129