' . t("Read the README.txt file in the Organic groups module directory.", array('@url' => "/$path/README.txt")) . '

'; $output .= '

' . t("Information about Organic Groups can also be found on the module'sdocumentation page.", array('@og' => 'http://drupal.org/documentation/modules/og')) . '

'; return $output; } } /** * 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; } /** * Implements hook_entity_info(). */ function og_entity_info() { $items['og_membership_type'] = array( 'label' => t('OG membership type'), 'controller class' => 'EntityAPIControllerExportable', 'entity class' => 'OgMembershipType', 'base table' => 'og_membership_type', 'fieldable' => TRUE, 'entity keys' => array( 'id' => 'id', 'label' => 'description', 'name' => 'name', ), 'exportable' => TRUE, 'export' => array( 'default hook' => 'default_og_membership_type', ), 'bundle of' => 'og_membership', 'module' => 'og', 'metadata controller class' => 'EntityDefaultMetadataController', 'views controller class' => 'EntityDefaultViewsController', 'access callback' => 'og_membership_type_access', 'entity cache' => module_exists('entitycache'), ); if (class_exists('OgMembershipTypeUIController')) { $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', ), ); } $items['og_membership'] = array( 'label' => t('OG membership'), 'entity class' => 'OgMembership', 'controller class' => 'EntityAPIController', 'base table' => 'og_membership', 'fieldable' => TRUE, 'entity keys' => array( 'id' => 'id', // The message has no label. 'label' => FALSE, 'bundle' => 'type', ), 'label callback' => 'og_membership_label', 'bundles' => array(), 'bundle keys' => array( 'bundle' => 'name', ), 'module' => 'og', 'metadata controller class' => 'OgMembershipMetadataController', 'views controller class' => 'OgMembershipViewsController', 'access callback' => 'og_membership_access', 'entity cache' => module_exists('entitycache'), ); // Add bundle info but bypass entity_load() as we cannot use it here. 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'), ), ); } } return $items; } /** * Implements hook_entity_property_info(). */ function og_entity_property_info() { $info = array(); // 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', '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', // 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) { $params = array( '@label' => $label, '@label' => $label, '@state' => $state_label, ); $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); } } } } 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', '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); } } } return $info; } /** * Property getter callback for group members. * * @see og_entity_property_info() */ 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(); $query = new EntityFieldQuery(); $query ->entityCondition('entity_type', 'og_membership') ->propertyCondition('group_type', $type, '=') ->propertyCondition('gid', $id, '=') ->propertyCondition('entity_type', 'user', '='); if ($state) { $query->propertyCondition('state', $state, '='); } $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) { $ids = array_merge($ids, array_keys($values)); } } 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]; $field = field_info_field($field_name); $states = count($args) == 2 ? FALSE : array($args[2]); $result = og_get_entity_groups($type, $entity, $states, $field_name); $target_type = $field['settings']['target_type']; return !empty($result[$target_type]) ? array_keys($result[$target_type]) : array(); } /** * Getter callback to load the 'entity' or 'group' property from OG membership. * * We have to return the entity wrapped. */ 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); } } /** * Entity property info setter callback to set the "entity" property for groups * and memberships. * * As the property is of type entity, the value will be passed as a wrapped * entity. */ function og_entity_setter($object, $property_name, $wrapper) { 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; } } /** * Implements hook_default_og_membership_type(). */ 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; } /** * Implements hook_modules_uninstalled(). */ function og_modules_uninstalled($modules) { // Delete module's permissions. og_permissions_delete_by_module($modules); } /** * Implements hook_ctools_plugin_directory(). */ function og_ctools_plugin_directory($module, $plugin) { if ($module == 'ctools') { return 'plugins/' . $plugin; } elseif ($module == 'entityreference') { return "plugins/entityreference/$plugin"; } } /** * Implements hook_permission(). */ function og_permission() { return array( 'administer group' => array( 'title' => t('Administer Organic groups permissions'), 'description' => t('Administer all groups and permissions.'), ), ); } /** * Implements hook_og_permission(). */ function og_og_permission() { // Generate standard node permissions for all applicable node types. $perms = array(); $perms['update group'] = array( 'title' => t('Edit group'), 'description' => t('Edit the group. Note: This permission controls only node entity type groups.'), 'default role' => array(OG_ADMINISTRATOR_ROLE), ); $perms['administer group'] = array( 'title' => t('Administer group'), 'description' => t('Manage group members and content in the group.'), 'default role' => array(OG_ADMINISTRATOR_ROLE), 'restrict access' => TRUE, ); foreach (node_permissions_get_configured_types() as $type) { $perms = array_merge($perms, og_list_permissions($type)); } return $perms; } /** * Implements hook_og_default_roles(). */ function og_og_default_roles() { return array(OG_ADMINISTRATOR_ROLE); } /** * Implements hook_node_access(). */ function og_node_access($node, $op, $account) { $type = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type); 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; } if (user_access('administer group', $account)) { return NODE_ACCESS_ALLOW; } // 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() $required = FALSE; foreach (og_get_group_audience_fields('node', $type) as $field_name => $label) { $field = field_info_field($field_name); $instance = field_info_instance('node', $field_name, $type); // Set the "field mode" to default, before passing it to the // selection handler. $instance['field_mode'] = 'default'; if (entityreference_get_selection_handler($field, $instance)->countReferencableEntities()) { return NODE_ACCESS_ALLOW; } // Allow users to create content outside of groups, if none of the // audience fields is required. if ($instance['required']) { $required = TRUE; } } // 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; } elseif (in_array($op, array('update', 'delete'))) { $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; } else { $access = $access || // Any content. og_user_access_entity("$op any $type content", 'node', $node, $account) || // Own content. ($account->uid == $node->uid && og_user_access_entity("$op own $type content", 'node', $node, $account)); } if (!$access && $op == 'update' && og_is_group('node', $node)) { // 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. return variable_get('og_node_access_strict', TRUE) ? NODE_ACCESS_DENY : NODE_ACCESS_IGNORE; } return NODE_ACCESS_IGNORE; } /** * 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); $instance = field_info_instance($entity_type, $field_name, $bundle_name); 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']; // Check if we are editing the user entity. if ($entity_type == 'user') { if (!empty($instance['settings']['behaviors']['og_widget']['access_override'])) { return; } return user_access('administer group', $account); } } /** * Implements hook_views_api(). */ function og_views_api() { return array( 'api' => 3, 'path' => drupal_get_path('module', 'og') . '/includes/views', ); } /** * 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. */ function og_field_create_instance($instance) { if ($instance['field_name'] != OG_GROUP_FIELD) { return; } // Create default roles per entity-type per bundle. og_roles_override($instance['entity_type'], $instance['bundle'], 0); // Check if we need to add a group audience on the user's entity. // We add a different field, so each field can be set differently. $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; } } // 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; $og_field['instance']['label'] = t('Group membership'); // 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); } } /** * 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. */ 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; } // Get the per-bundle roles. $roles = og_roles($instance['entity_type'], $instance['bundle']); foreach ($roles as $rid => $name) { og_role_delete($rid); } } /** * Implements hook_field_attach_form(). */ function og_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { list(,, $bundle) = entity_extract_ids($entity_type, $entity); if (!isset($form['#entity'])) { $form['#entity'] = $entity; } 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)) { return; } $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; $entity_type = $form['#entity_type']; if (empty($form_state[$entity_type])) { // We are inside field settings page. return; } $account = user_load($user->uid); $bundle = $form['#bundle']; $entity = $form['#entity']; list($id) = entity_extract_ids($entity_type, $entity); $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. return; } 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.')); } /** * Validate handler; Make sure a group can be created. * * We check if the group manager has a matching group-audience field for the * OG membership to be created in. */ function og_form_group_manager_validate($form, &$form_state) { $entity_type = $form['#entity_type']; $bundle = $form['#bundle']; if (empty($form_state[$entity_type])) { // We are inside field settings page. return; } $entity = $form_state[$entity_type]; $langcode = isset($form_state['values']['language']) ? $form_state['values']['language'] : LANGUAGE_NONE; 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)))); } /** * Implements hook_entity_insert(). */ 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)); // 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); } } } if (!og_is_group_default_access($entity_type, $entity)) { // Override default roles. og_roles_override($entity_type, $bundle, $id); } } /** * Implements hook_entity_update(). */ function og_entity_update($entity, $entity_type) { if (!og_is_group($entity_type, $entity)) { return; } list($id, , $bundle) = entity_extract_ids($entity_type, $entity); if (!empty($entity->uid) && !og_is_member($entity_type, $id, 'user', $entity->uid)) { // Subscribe the group manager, in case the owner changed. og_group($entity_type, $id, array('entity' => $entity->uid)); // 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); } } } $original_entity = $entity->original; $property = OG_DEFAULT_ACCESS_FIELD; if (!empty($entity->{$property}) && $entity->{$property} != $original_entity->{$property}) { if (!og_is_group_default_access($entity_type, $entity)) { // Override default roles. og_roles_override($entity_type, $bundle, $id); } else { // Delete overridden roles. og_delete_user_roles_by_group($entity_type, $entity); } } } /** * 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); } /** * 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); foreach (og_get_group_audience_fields($entity_type, $bundle) as $field_name => $label) { $field = field_info_field($field_name); $gids = array(); if ($field['cardinality'] == 1) { if ($og_membership = $wrapper->{$field_name . '__og_membership'}->value()) { // Wrapper return an array. $gids = $og_membership[0]->gid; } } else { $target_type = $field['settings']['target_type']; $gids = og_get_entity_groups($entity_type, $entity, array(), $field_name); $gids = !empty($gids[$target_type]) ? array_values($gids[$target_type]) : array(); } if ($gids) { $wrapper->{$field_name}->set($gids); } } } /** * Implements hook_entity_delete(). */ function og_entity_delete($entity, $entity_type) { 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); og_membership_delete_by_group($entity_type, $entity); } 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; } } /** * Implements hook_og_membership_insert(). */ 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)); } } /** * Implements hook_og_membership_update(). */ 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)); } 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)); } } } /** * Implements hook_og_membership_delete(). */ function og_og_membership_delete($og_membership) { if ($og_membership->entity_type != 'user') { return; } // Remove possible records in the {og_users_roles} table. db_delete('og_users_roles') ->condition('uid', $og_membership->etid) ->condition('gid', $og_membership->gid) ->condition('group_type', $og_membership->group_type) ->execute(); if (module_exists('rules')) { rules_invoke_event('og_user_delete', $og_membership, entity_metadata_wrapper('user', $og_membership->etid)); } } /** * Implements hook_og_fields_info(). */ function og_og_fields_info() { $items[OG_GROUP_FIELD] = array( 'type' => array('group'), 'description' => t('Determine if this should be a group.'), 'field' => array( 'field_name' => OG_GROUP_FIELD, 'type' => 'list_boolean', 'cardinality' => 1, 'settings' => array( 'allowed_values' => array(0 => 'Not a group', 1 => 'Group'), 'allowed_values_function' => '', ), ), 'instance' => array( 'label' => t('Group'), 'description' => t('Determine if this is an OG group.'), 'display_label' => 1, 'widget' => array( 'module' => 'options', 'settings' => array( 'og_hide' => TRUE, ), 'type' => 'options_onoff', 'weight' => 0, ), 'default_value' => array(0 => array('value' => 1)), 'view modes' => array( 'full' => array( 'label' => t('Full'), 'type' => 'og_group_subscribe', 'custom settings' => FALSE, ), 'teaser' => array( 'label' => t('Teaser'), 'type' => 'og_group_subscribe', 'custom settings' => FALSE, ), ), ), ); $items[OG_DEFAULT_ACCESS_FIELD] = array( 'type' => array('group'), 'description' => t('Determine if group should use default roles and permissions.'), 'field' => array( 'field_name' => OG_DEFAULT_ACCESS_FIELD, 'type' => 'list_boolean', 'cardinality' => 1, 'settings' => array('allowed_values' => array(0 => 'Use default roles and permissions', 1 => 'Override default roles and permissions'), 'allowed_values_function' => ''), ), 'instance' => array( 'label' => t('Group roles and permissions'), 'widget' => array( 'module' => 'options', 'settings' => array(), 'type' => 'options_select', ), 'required' => TRUE, // Use default role and permissions as default value. 'default_value' => array(0 => array('value' => 0)), 'view modes' => array( 'full' => array( 'label' => t('Full'), 'type' => 'list_default', 'custom settings' => FALSE, ), 'teaser' => array( 'label' => t('Teaser'), 'type' => 'list_default', 'custom settings' => FALSE, ), ), ), ); $items[OG_AUDIENCE_FIELD] = array( 'multiple' => TRUE, 'type' => array('group content'), 'description' => t('Determine to which groups this group content is assigned to.'), 'field' => array( 'field_name' => OG_AUDIENCE_FIELD, 'type' => 'entityreference', 'cardinality' => FIELD_CARDINALITY_UNLIMITED, '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', ), ), 'instance' => array( 'label' => t('Groups audience'), 'widget' => array( 'type' => 'og_complex', 'module' => 'og', 'settings' => array(), ), 'settings' => array( 'behaviors' => array( 'og_widget' => array( 'status' => TRUE, 'default' => array( 'widget_type' => 'options_select', ), 'admin' => array( 'widget_type' => 'entityreference_autocomplete', ), ), ), ), 'view modes' => array( 'full' => array( 'label' => t('Full'), 'type' => 'og_list_default', 'custom settings' => FALSE, ), 'teaser' => array( 'label' => t('Teaser'), 'type' => 'og_list_default', 'custom settings' => FALSE, ), ), ), ); return $items; } /** * Creates a new membership type. * * If a message type already exists, an exception will be thrown. * * @return OgMembershipType * Returns a new OG membership type object. */ function og_membership_type_create($name, $values = array()) { global $language; // Make sure the message type doesn't already exist, to prevent duplicate key // error. if (og_membership_type_load($name)) { throw new OgException('Group membership type ' . check_plain($name) . ' already exists.'); } $values['name'] = $name; $values += array( 'language' => $language->language, ); $wrapper = entity_property_values_create_entity('og_membership_type', $values); return $wrapper->value(); } /** * OG membership type loader. * * @param $type_name * (optional) The name for this message type. If no type is given all existing * types are returned. * * @return MessageType * Returns a fully-loaded message type definition if a type name is passed. * Else an array containing all types is returned. */ function og_membership_type_load($name = NULL) { // Replace dashes with underscores so this can be used as menu argument // loader too. $types = entity_load_multiple_by_name('og_membership_type', isset($name) ? array(strtr($name, array('-' => '_'))) : FALSE); if (isset($name)) { return isset($types[$name]) ? $types[$name] : FALSE; } return $types; } /** * Inserts or updates an OG membership type entity into the database. * * @param $og_membership * The OG membership type entiyt to be saved. * * @return * Failure to write a record will return FALSE. Otherwise SAVED_NEW or * SAVED_UPDATED is returned depending on the operation performed. */ function og_membership_type_save($og_membership) { return entity_save('og_membership_type', $og_membership); } /** * Deletes an existing OG membership type. * * @param $og_membership * The OG membership type entity to be deleted. */ function og_membership_type_delete($og_membership) { return entity_delete('og_membership_type', $og_membership); } /** * Access callback for the OG membership type entity. */ function og_membership_type_access($op, $entity, $account = NULL, $entity_type = 'og_membership') { // No-end user needs access to this entity, so restrict it to admins. return user_access('administer group'); } /** * Reset static cache related to group membership. * * @deprecated * Use og_invalidate_cache() instead. */ function og_membership_invalidate_cache() { og_invalidate_cache(); } /** * Creates a new OG membership. * * If a group membership already exists, an exception will be thrown. * * @param $group_type * The entity type of the group. * @param $gid * The group ID. * @param $entity_type * The entity type of the group content. * @param $etid * The entity ID of the group content. * @param $field_name * The group audience field name. * @param $values * (optional) Array of fields values to be attached to the OG membership, that * will be processed using entity-metadata wrapper. * * @return OgMembership * Returns a new OG membership object. * * @see entity_property_values_create_entity() */ function og_membership_create($group_type, $gid, $entity_type, $etid, $field_name, $values = array()) { global $language; $values += array( 'group_type' => $group_type, 'gid' => $gid, 'entity_type' => $entity_type, 'etid' => $etid, 'state' => OG_STATE_ACTIVE, 'created' => time(), 'field_name' => $field_name, 'language' => $language->language, ); if (!og_is_group_audience_field($field_name)) { throw new OgException(format_string('%field-name is not a valid group-audience field.', array('%field-name' => $field_name))); } // Get the type from the field. $field = field_info_field($field_name); $values['type'] = $field['settings']['handler_settings']['membership_type']; $wrapper = entity_property_values_create_entity('og_membership', $values); return $wrapper->value(); } /** * OG membership loader. * * @param $name * (optional) The name for this group membership. If no type is given all existing * types are returned. * * @return OgMembership * Returns a fully-loaded group membership definition if a type name is passed. * Else an array containing all types is returned. */ function og_membership_load($id) { return entity_load_single('og_membership', $id); } /** * Load multiple OG membership entities based on certain conditions. * * @param $gids * An array of group membership IDs. * @param $conditions * An array of conditions to match against the {entity} table. * @param $reset * A boolean indicating that the internal cache should be reset. * * @return * An array of group entities, indexed by group ID. */ function og_membership_load_multiple($ids = array(), $conditions = array(), $reset = FALSE) { return entity_load('og_membership', $ids, $conditions, $reset); } /** * Get the group membership entity by user and group. * * @return * The OgMembership object if found, or FALSE. */ function og_get_membership($group_type, $gid, $entity_type, $etid) { $return = &drupal_static(__FUNCTION__, array()); $identifier = $group_type . ':' . $gid . ':' . $entity_type . ':' . $etid; if (!isset($return[$identifier])) { $return[$identifier] = FALSE; $query = new EntityFieldQuery(); $result = $query ->entityCondition('entity_type', 'og_membership') ->propertyCondition('gid', $gid, '=') ->propertyCondition('group_type', $group_type, '=') ->propertyCondition('etid', $etid, '=') ->propertyCondition('entity_type', $entity_type, '=') ->execute(); if (!empty($result['og_membership'])) { $key = key($result['og_membership']); $return[$identifier] = $key; } } if (!empty($return[$identifier])) { $og_membership = og_membership_load($return[$identifier]); return $og_membership; } return FALSE; } /** * Implements hook_entity_query_alter(). * * Add "og_membership" tag if there's a group audience field in the query. * * @see og_query_og_membership_alter(). */ function og_entity_query_alter(EntityFieldQuery $query) { foreach ($query->fieldConditions as $values) { if (og_is_group_audience_field($values['field']['field_name'])) { $query->addTag('og_membership'); return; } } } /** * Implements hook_query_TAG_alter(). * * Join the {og_membership} table and alter the query. */ function og_query_og_membership_alter(QueryAlterableInterface $query) { $tables = &$query->getTables(); $fields = &$query->getFields(); $conditions = &$query->conditions(); // Find the group-audience fields. $field_names = array(); foreach ($query->alterMetaData['entity_field_query']->fieldConditions as $values) { $field_name = $values['field']['field_name']; if (og_is_group_audience_field($field_name)) { $field_names[] = $field_name; } } $aliases = array(); $base_table = FALSE; $base_table_alias = ''; foreach ($tables as $alias => $values) { if (!$base_table_alias && empty($values['join type'])) { $base_table_alias = $alias; } if (strpos($alias, 'field_data') !== 0) { continue; } $field_name = substr($values['table'], 11); if (!in_array($field_name, $field_names)) { continue; } if (empty($values['join type'])) { // This is the base table, so remove it in favor of OG membership. $base_table = TRUE; } unset($tables[$alias]); $aliases[$alias] = $field_name; } foreach ($aliases as $alias => $field_name) { foreach ($tables as $key => $values) { $condition = str_replace("$alias.entity_type", 'ogm.entity_type', $values['condition']); $condition = str_replace("$alias.entity_id", 'ogm.etid', $condition); $tables[$key]['condition'] = $condition; } } $entity_type = $query->alterMetaData['entity_field_query']->entityConditions['entity_type']['value']; $entity_type = is_array($entity_type) ? $entity_type[0] : $entity_type; $entity_info = entity_get_info($entity_type); $id = $entity_info['entity keys']['id']; if ($base_table) { // If the table of the base entity does not exist (e.g. there is no // property condition), we need to add it, as we don't have the // revision ID and bundle in {og_membership} table. $base_table = $entity_info['base table']; if (strpos($base_table_alias, 'field_data') === 0) { // Check if the entity base table already exists. $base_table_alias = FALSE; foreach ($tables as $table) { if ($table['table'] == $base_table) { $base_table_alias = $table['alias']; break; } } if (!$base_table_alias) { $base_table_alias = $query->innerJoin($base_table, NULL, "$base_table.$id = ogm.etid"); } } // Point the revision ID and bundle to the base entity. $fields['revision_id']['table'] = $base_table; // If there is no revision table, use the bundle. if (!empty($entity_info['entity keys']['revision'])) { // Entity doesn't support revisions. $fields['revision_id']['field'] = $entity_info['entity keys']['revision']; } elseif (!empty($entity_info['entity keys']['bundle'])) { $fields['revision_id']['field'] = $entity_info['entity keys']['bundle']; } else { // Entity doesn't have bundles (e.g. user). $fields['revision_id']['field'] = $id; } $fields['bundle']['table'] = $base_table; $fields['bundle']['field'] = !empty($entity_info['entity keys']['bundle']) ? $entity_info['entity keys']['bundle'] : $id; $fields['entity_type']['table'] = 'ogm'; $fields['entity_type']['field'] = 'entity_type'; $fields['entity_id']['table'] = 'ogm'; $fields['entity_id']['field'] = 'etid'; // Populate the alias key, as it might be empty on COUNT queries. foreach (array_keys($fields) as $key) { if (empty($fields[$key]['alias'])) { $fields[$key]['alias'] = $key; } } $ogm = array( 'join type' => NULL, 'table' => 'og_membership', 'alias' => 'ogm', 'condition' => '', 'arguments' => array(), ); $tables = array_merge(array('ogm' => $ogm), $tables); } else { // If the original EntityFieldQuery has an entity type entityCondition, // restrict the join by this. Otherwise, we would bring in the IDs of // entities of other types if they happen to match on the base table entity // ID. $query_base_entity_type = $query->alterMetaData['entity_field_query']->entityConditions['entity_type']['value']; if (empty($query_base_entity_type)) { // It's possible to have no entity type specified: join without it. $query->join('og_membership', 'ogm', "ogm.etid = $base_table_alias.entity_id"); } else { if (is_array($query_base_entity_type)) { // It's also possible for the entity type to be multiple. $query->join('og_membership', 'ogm', "ogm.etid = $base_table_alias.entity_id AND ogm.entity_type IN (:entity_type)", array( ':entity_type' => $query_base_entity_type, )); } else { $query->join('og_membership', 'ogm', "ogm.etid = $base_table_alias.entity_id AND ogm.entity_type = :entity_type", array( ':entity_type' => $query_base_entity_type, )); } } } _og_query_og_membership_alter_conditions($conditions, $aliases, $base_table_alias, $entity_info); } /** * Recursively replace the fields to their aliases in the query's conditions. * * See og_query_og_membership_alter(). */ function _og_query_og_membership_alter_conditions(&$conditions, $aliases, $base_table_alias, $entity_info) { foreach ($conditions as $delta => $values) { if (!is_array($values)) { continue; } // Handle conditions in a sub-query. if (is_object($values['value'])) { _og_query_og_membership_alter_conditions($values['value']->conditions(), $aliases, $base_table_alias, $entity_info); } // Handle sub-conditions. if (is_object($values['field'])) { _og_query_og_membership_alter_conditions($values['field']->conditions(), $aliases, $base_table_alias, $entity_info); continue; } if (strpos($values['field'], 'field_data_') !== 0) { continue; } // Explode spaces on the fiels, for handling only the first part in values // such as "foo.nid = bar.nid". $field_parts = explode(' ', $values['field'], 2); list($table, $column) = explode('.', $field_parts[0]); if (empty($aliases[$table])) { continue; } $table = 'ogm'; // Replace entity_id or any other primary id (e.g. nid for the node // entity). $id_columns = array('entity_id', $entity_info['entity keys']['id']); if (in_array($column, $id_columns)) { $column = 'etid'; } if ($column == 'deleted') { unset($conditions[$delta]); continue; } elseif (strpos($column, 'target_id')) { $column = 'gid'; } elseif ($column == 'bundle') { // Add the bundle of the base entity type. $table = $base_table_alias; $column = $entity_info['entity keys']['bundle']; } $conditions[$delta]['field'] = "$table.$column"; // Add the second part if it exists. if (!empty($field_parts[1])) { $conditions[$delta]['field'] .= ' ' . $field_parts[1]; } } } /** * Inserts or updates an OG membership entity into the database. * * @param $og_membership * The OG membership entity to be inserted. * * @return * Failure to write a record will return FALSE. Otherwise SAVED_NEW or * SAVED_UPDATED is returned depending on the operation performed. */ function og_membership_save($og_membership) { return entity_save('og_membership', $og_membership); } /** * Delete an existing OG membership. * * @param $id * The OG membership entity ID to be deleted. */ function og_membership_delete($id) { return og_membership_delete_multiple(array($id)); } /** * Delete multiple existing OG memberships. * * We can't use entity_delete_multiple(), as we need to make sure the field * cache is invalidated. * * @param $ids * Array with OG membership entity IDs to be deleted. */ function og_membership_delete_multiple($ids = array()) { entity_delete_multiple('og_membership', $ids); og_invalidate_cache(); } /** * Implements hook_cron_queue_info(). */ function og_cron_queue_info() { $items['og_membership_orphans'] = array( 'title' => t('OG orphans'), 'worker callback' => 'og_membership_orphans_worker', 'time' => 60, ); return $items; } /** * Queue worker; Process a queue item. * * Delete memberships, and if needed all related group-content. */ function og_membership_orphans_worker($data) { $group_type = $data['group_type']; $gid = $data['gid']; $query = new EntityFieldQuery(); $result = $query ->entityCondition('entity_type', 'og_membership') ->propertyCondition('group_type', $group_type, '=') ->propertyCondition('gid', $gid, '=') ->propertyOrderBy('id') ->range(0, 50) ->execute(); if (empty($result['og_membership'])) { return; } $ids = array_keys($result['og_membership']); if ($data['orphans']['move']) { _og_orphans_move($ids, $data['orphans']['move']['group_type'], $data['orphans']['move']['gid']); $queue = DrupalQueue::get('og_membership_orphans'); return $queue->createItem($data); } elseif ($data['orphans']['delete']) { _og_orphans_delete($ids); // Create a new item. $queue = DrupalQueue::get('og_membership_orphans'); return $queue->createItem($data); } } /** * Helper function to delete orphan group-content. * * @param $ids * Array of OG membership IDs. * * @see og_membership_delete_by_group_worker() */ function _og_orphans_delete($ids) { // Get all the group-content that is now orphan. $orphans = array(); $og_memberships = og_membership_load_multiple($ids); foreach ($og_memberships as $og_membership) { $entity_type = $og_membership->entity_type; $id = $og_membership->etid; // Don't delete users. if ($entity_type == 'user') { continue; } $entity_groups = og_get_entity_groups($entity_type, $id); // Orphan node can be relate to only one type of entity group. if (count($entity_groups) == 1) { $gids = reset($entity_groups); // Orphan node can be relate to only one node. if (count($gids) > 1) { continue; } } $orphans[$entity_type][] = $id; } if ($orphans) { foreach ($orphans as $entity_type => $orphan_ids) { entity_delete_multiple($entity_type, $orphan_ids); } } // Delete the OG memberships. og_membership_delete_multiple($ids); } /** * Helper function to move orphan group-content to another group. * * @param $ids * Array of OG membership IDs. * * @see og_membership_delete_by_group_worker() */ function _og_orphans_move($ids, $group_type, $gid) { if (!og_is_group($group_type, $gid)) { $params = array( '@group-type' => $group_type, '@gid' => $gid, ); throw new OgException(format_string('Cannot move orphan group-content to @group-type - @gid, as it is not a valid group.', $params)); } $og_memberships = og_membership_load_multiple($ids); foreach ($og_memberships as $og_membership) { $entity_type = $og_membership->entity_type; $id = $og_membership->etid; if (count(og_get_entity_groups($entity_type, $id)) > 1) { continue; } $og_membership->group_type = $group_type; $og_membership->gid = $gid; $og_membership->save(); } } /** * Register memberships for deletion. * * if the property "skip_og_membership_delete_by_group" exists on the * entity, this function will return early, and allow other implementing * modules to deal with the deletion logic. * * @param $entity_type * The group type. * @param $entity * The group entity object. */ function og_membership_delete_by_group($entity_type, $entity) { if (!empty($entity->skip_og_membership_delete_by_group)) { return; } list($gid) = entity_extract_ids($entity_type, $entity); $query = new EntityFieldQuery(); $result = $query ->entityCondition('entity_type', 'og_membership') ->propertyCondition('group_type', $entity_type, '=') ->propertyCondition('gid', $gid, '=') ->execute(); if (empty($result['og_membership'])) { return; } if (variable_get('og_use_queue', FALSE)) { $queue = DrupalQueue::get('og_membership_orphans'); // Add item to the queue. $data = array( 'group_type' => $entity_type, 'gid' => $gid, // Allow implementing modules to determine the disposition (e.g. delete // orphan group content). 'orphans' => array( 'delete' => isset($entity->og_orphans['delete']) ? $entity->og_orphans['delete'] : variable_get('og_orphans_delete', FALSE), 'move' => isset($entity->og_orphans['move']) ? $entity->og_orphans['move'] : array(), ), ); // Exit now, as the task will be processed via queue. return $queue->createItem($data); } // No scalable solution was chosen, so just delete OG memberships. og_membership_delete_multiple(array_keys($result['og_membership'])); } /** * Label callback; Return the label of OG membership entity. */ function og_membership_label($og_membership) { $wrapper = entity_metadata_wrapper('og_membership', $og_membership); $params = array( '@entity' => $wrapper->entity->label(), '@group' => $wrapper->group->label(), ); return t('@entity in group @group', $params); } /** * Access callback for the group membership entity. */ function og_membership_access($op, $entity, $account = NULL, $entity_type = 'og_membership') { // No-end user needs access to this entity, so restrict it to admins. return user_access('administer group'); } /** * Return TRUE if the entity is acting as a group. * * @param $entity_type * The entity type. * @param $entity * The entity object, or the entity ID. * * @return * TRUE or FALSE if the entity is a group. */ function og_is_group($entity_type, $entity) { if (is_numeric($entity)) { $entity = entity_load_single($entity_type, $entity); } list(,, $bundle) = entity_extract_ids($entity_type, $entity); if (!field_info_instance($entity_type, OG_GROUP_FIELD, $bundle)) { return variable_get("og_is_group__{$entity_type}__{$bundle}", FALSE); } $items = field_get_items($entity_type, $entity, OG_GROUP_FIELD); return !empty($items[0]['value']); } /** * Invalidate cache. * * @param $gids * Array with group IDs that their cache should be invalidated. */ function og_invalidate_cache($gids = array()) { // Reset static cache. $caches = array( 'og_user_access', 'og_user_access_alter', 'og_role_permissions', 'og_get_user_roles', 'og_get_permissions', 'og_get_group_audience_fields', 'og_get_entity_groups', 'og_get_membership', 'og_get_field_og_membership_properties', 'og_get_user_roles', ); foreach ($caches as $cache) { drupal_static_reset($cache); } // Let other OG modules know we invalidate cache. module_invoke_all('og_invalidate_cache', $gids); } /** * Return all existing groups of an entity type. */ function og_get_all_group($group_type = 'node') { if (!field_info_field(OG_GROUP_FIELD)) { return array(); } $query = new EntityFieldQuery(); $return = $query ->entityCondition('entity_type', $group_type) ->fieldCondition(OG_GROUP_FIELD, 'value', 1, '=') ->execute(); return !empty($return[$group_type]) ? array_keys($return[$group_type]) : array(); } /** * Get the first best matching group-audience field. * * @param $entity_type * The entity type. * @param $entity * The entity object. * @param $group_type * The group type. * @param $group_bundle * The group bundle. * @param $skip_access * TRUE, if current user access to the field, should be skipped. * Defaults to FALSE. */ function og_get_best_group_audience_field($entity_type, $entity, $group_type, $group_bundle, $skip_access = FALSE) { list(,, $bundle) = entity_extract_ids($entity_type, $entity); $field_names = og_get_group_audience_fields($entity_type, $bundle); if (!$field_names) { return; } foreach ($field_names as $field_name => $label) { $field = field_info_field($field_name); $settings = $field['settings']; if ($settings['target_type'] != $group_type) { // Group type doesn't match. continue; } if (!empty($settings['handler_settings']['target_bundles']) && !in_array($group_bundle, $settings['handler_settings']['target_bundles'])) { // Bundles don't match. continue; } if (!og_check_field_cardinality($entity_type, $entity, $field_name)) { // Field reached maximum. continue; } if (!$skip_access && !field_access('view', $field, $entity_type, $entity)) { // User can't access field. continue; } return $field_name; } } /** * Return TRUE if a field can be used and has not reached maximum values. * * @param $entity_type * The entity type. * @param $entity * The entity object or entity ID. * @param $field_name * The group audience field name. */ function og_check_field_cardinality($entity_type, $entity, $field_name) { $field = field_info_field($field_name); if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) { return TRUE; } $wrapper = entity_metadata_wrapper($entity_type, $entity); return count($wrapper->{$field_name . '__og_membership'}->value(array('identifier' => TRUE))) < $field['cardinality']; } /** * Set an association (e.g. subscribe) an entity to a group. * * @param $group_type * The entity type of the group. * @param $gid * The group entity or ID. * @param $values * Array with the information to pass along, until it is processed in the * field handlers. * - "entity_type": (optional) The entity type (e.g. "node" or "user"). * Defaults to 'user' * - "entity": (optional) The entity object or entity Id to set the * association. Defaults to the current user if the $entity_type property is * set to 'user'. * - "field_name": The name of the field, the membership should be registered * in. If no value given, a first field with the correct membership type * will be used. If no field found, an execpetion will be thrown. * @param $save_created * (optional) If the OG membership is new, it determines whether the * membership will be saved. Defaults to TRUE. * * @return * The OG membership entity. */ function og_group($group_type, $gid, $values = array(), $save_created = TRUE) { global $user; // Set default values. $values += array( 'entity_type' => 'user', 'entity' => FALSE, 'field_name' => FALSE, 'state' => OG_STATE_ACTIVE, ); $entity_type = $values['entity_type']; $entity = $values['entity']; $field_name = $values['field_name']; $state = $values['state']; if ($entity_type == 'user' && empty($entity)) { // We don't pass the object, as we want entity_metadata_wrapper() to reload // the user object. $entity = $user->uid; } $wrapper = entity_metadata_wrapper($entity_type, $entity); // If entity was an ID, get the object. $entity = $wrapper->value(); $bundle = $wrapper->getBundle(); $id = $wrapper->getIdentifier(); if (is_object($gid)) { $group = $gid; } else { $group = entity_load_single($group_type, $gid); } // the group ID might be the entity, so re-popualte it. list($gid,, $group_bundle) = entity_extract_ids($group_type, $group); // Get membership if exists. $og_membership = og_get_membership($group_type, $gid, $entity_type, $id); if (!$og_membership && empty($field_name)) { $params = array( '%entity-type' => $entity_type, '%bundle' => $bundle, '%group-type' => $group_type, '%group-bundle' => $group_bundle, ); // Allow getting fields not accessible by the user. $field_name = og_get_best_group_audience_field($entity_type, $entity, $group_type, $group_bundle, TRUE); if (!$field_name) { throw new OgException(format_string('There are no OG fields in entity %entity-type and bundle %bundle referencing %group-type - %group-bundle.', $params)); } } if ($og_membership) { if (empty($og_membership->is_new) && $og_membership->field_name == $field_name && $og_membership->state == $state) { // Entity is already associated with group. return $og_membership; } elseif (!empty($field_name) && $og_membership->field_name != $field_name) { // Ungroup the current association, as it needs to change field. og_ungroup($group_type, $gid, $entity_type, $id); $og_membership = FALSE; } elseif ($og_membership->state != $state){ // Change the state. $og_membership->state = $state; } else { // Nothing changed. return $og_membership; } } if (!$og_membership) { // Unset the values, so we don't try to process them. unset($values['entity_type'], $values['entity'], $values['field_name']); // Create a new OG membership. $og_membership = og_membership_create($group_type, $gid, $entity_type, $id, $field_name, $values); } if (empty($og_membership->is_new) || $save_created) { // Pass the entity object along to OgMembership::save() so we don't have // to reload it. $og_membership->entity = $entity; // Save the membership for update, or if the OG membership is new when // "save-created" is TRUE. $og_membership->save(); } return $og_membership; } /** * Delete an association (e.g. unsubscribe) of an entity to a group. * * @param $group_type * The entity type (e.g. "node"). * @param $gid * The group entity object or ID, to ungroup. * @param $entity_type * (optional) The entity type (e.g. "node" or "user"). * @param $etid * (optional) The entity object or ID, to ungroup. * * @return * The entity with the fields updated. */ function og_ungroup($group_type, $gid, $entity_type = 'user', $etid = NULL) { if (is_object($gid)) { list($gid) = entity_extract_ids($group_type, $gid); } if ($entity_type == 'user' && empty($etid)) { global $user; $etid = $user->uid; } elseif (is_object($etid)) { list($etid) = entity_extract_ids($entity_type, $etid); } if ($og_membership = og_get_membership($group_type, $gid, $entity_type, $etid)) { $og_membership->delete(); } } /** * Determine whether a user has a given privilege. * * All permission checks in OG should go through this function. This * way, we guarantee consistent behavior, and ensure that the superuser * and group administrators can perform all actions. * * @param $group_type * The entity type of the group. * @param $gid * The entity ID of the group. * @param $string * The permission, such as "administer group", being checked for. * @param $account * (optional) The account to check. Defaults to the current user. * @param $skip_alter * (optional) If TRUE then user access will not be sent to other modules * using drupal_alter(). This can be used by modules implementing * hook_og_user_access_alter() that still want to use og_user_access(), but * without causing a recursion. Defaults to FALSE. * @param $ignore_admin * (optional) When TRUE the specific permission is checked, ignoring the * "administer group" permission if the user has it. When FALSE, a user * with "administer group" will be granted all permissions. * Defaults to FALSE. * * @return * TRUE or FALSE if the current user has the requested permission. * NULL, if the given group isn't a valid group. */ function og_user_access($group_type, $gid, $string, $account = NULL, $skip_alter = FALSE, $ignore_admin = FALSE) { global $user; $perm = &drupal_static(__FUNCTION__, array()); // Mark the group ID and permissions that invoked an alter. $perm_alter = &drupal_static(__FUNCTION__ . '_alter', array()); if (!og_is_group($group_type, $gid)) { // Not a group. return NULL; } if (empty($account)) { $account = clone $user; } // User #1 has all privileges. if ($account->uid == 1) { return TRUE; } // Administer group permission. if (user_access('administer group', $account) && !$ignore_admin) { return TRUE; } // Group manager has all privileges (if variable is TRUE). if (!empty($account->uid) && variable_get('og_group_manager_full_access', TRUE)) { $group = entity_load_single($group_type, $gid); if (!empty($group->uid) && $group->uid == $account->uid) { return TRUE; } } $identifier = $group_type . ':' . $gid; // To reduce the number of SQL queries, we cache the user's permissions // in a static variable. if (!isset($perm[$identifier][$account->uid])) { $perms = array(); if ($roles = og_get_user_roles($group_type, $gid, $account->uid)) { // Member might not have roles if they are blocked. // A pending member is treated as a non-member. $role_permissions = og_role_permissions($roles); foreach ($role_permissions as $one_role) { $perms += $one_role; } } $perm[$identifier][$account->uid] = $perms; } if (!$skip_alter && empty($perm_alter[$identifier][$account->uid][$string])) { // Let modules alter the permissions. since $perm is static we create // a clone of it. $group = !empty($group) ? $group : entity_load_single($group_type, $gid); $temp_perm = $perm[$identifier][$account->uid]; $context = array( 'string' => $string, 'group_type' => $group_type, 'group' => $group, 'account' => $account, ); drupal_alter('og_user_access', $temp_perm, $context); // Re-assing the altered permissions. $perm[$identifier][$account->uid] = $temp_perm; // Make sure alter isn't called for the same permissions. $perm_alter[$identifier][$account->uid][$string] = TRUE; } return !empty($perm[$identifier][$account->uid][$string]) || (!empty($perm[$identifier][$account->uid]['administer group']) && !$ignore_admin); } /** * Check if a user has access to a permission on a certain entity context. * * @param $perm * The organic groups permission. * @param $entity_type * The entity type. * @param $entity * The entity object, or the entity ID. * @param $account * (optional) The user object. If empty the current user will be used. * @param $skip_alter * (optional) If TRUE then user access will not be sent to other modules * using drupal_alter(). This can be used by modules implementing * hook_og_user_access_alter() that still want to use og_user_access(), but * without causing a recursion. Defaults to FALSE. * @param $ignore_admin * (optional) When TRUE the specific permission is checked, ignoring the * "administer group" permission if the user has it. When FALSE, a user * with "administer group" will be granted all permissions. * Defaults to FALSE. * * @return * Returns TRUE if the user has access to the permission, otherwise FALSE, or * if the entity is not in OG context, function will return NULL. This allows * a distinction between FALSE - no access, and NULL - no access as no OG * context found. */ function og_user_access_entity($perm, $entity_type, $entity, $account = NULL, $skip_alter = FALSE, $ignore_admin = FALSE) { if (empty($account)) { global $user; $account = clone $user; } // Set the default for the case there is not a group or a group content. $result = NULL; if (empty($entity)) { // $entity might be NULL, so return early. // @see field_access(). return $result; } elseif (is_numeric($entity)) { $entity = entity_load_single($entity_type, $entity); } list($id, $vid, $bundle_name) = entity_extract_ids($entity_type, $entity); if (empty($id)) { // Entity isn't saved yet. return $result; } $is_group = og_is_group($entity_type, $entity); $is_group_content = og_is_group_content_type($entity_type, $bundle_name); if ($is_group) { if (og_user_access($entity_type, $id, $perm, $account)) { return TRUE; } else { // An entity can be a group and group content in the same time. The group // didn't return TRUE, but the user still might have access to the // permission in group content context. $result = FALSE; } } if ($is_group_content && $groups = og_get_entity_groups($entity_type, $entity)) { foreach ($groups as $group_type => $gids) { foreach ($gids as $gid) { if (og_user_access($group_type, $gid, $perm, $account, $skip_alter, $ignore_admin)) { return TRUE; } } } return FALSE; } // Either the user didn't have permission, or the entity might be a // disabled group or an orphaned group content. return $result; } /** * Get the groups an entity is associated with. * * @param $entity_type * The entity type. Defaults to 'user' * @param $entity * (optional) The entity object or entity ID. If empty, and $entity_type is * "user", the current user will be used. * @param $states * (optional) Array with the state to return. Defaults to active. * @param $field_name * (optional) The field name associated with the group. * * @return * An array with the group's entity type as the key, and array - keyed by * the OG membership ID and the group ID as the value. If nothing found, * then an empty array. */ function og_get_entity_groups($entity_type = 'user', $entity = NULL, $states = array(OG_STATE_ACTIVE), $field_name = NULL) { $cache = &drupal_static(__FUNCTION__, array()); if ($entity_type == 'user' && empty($entity)) { global $user; $entity = clone $user; } if (is_object($entity)) { // Get the entity ID. list($id) = entity_extract_ids($entity_type, $entity); } else { $id = $entity; } // Get a string identifier of the states, so we can retrieve it from cache. if ($states) { sort($states); $state_identifier = implode(':', $states); } else { $state_identifier = FALSE; } $identifier = array( $entity_type, $id, $state_identifier, $field_name, ); $identifier = implode(':', $identifier); if (isset($cache[$identifier])) { // Return cached values. return $cache[$identifier]; } $cache[$identifier] = array(); $query = db_select('og_membership', 'ogm') ->fields('ogm', array('id', 'gid', 'group_type')) ->condition('entity_type', $entity_type) ->condition('etid', $id); if ($states) { $query->condition('state', $states, 'IN'); } if ($field_name) { $query->condition('field_name', $field_name); } $result = $query ->execute() ->fetchAll(); foreach ($result as $row) { $cache[$identifier][$row->group_type][$row->id] = $row->gid; } return $cache[$identifier]; } /** * Return TRUE if field is a group audience type. * * @param $field_name * The field name. */ function og_is_group_audience_field($field_name) { $field = field_info_field($field_name); return $field['type'] == 'entityreference' && ($field['settings']['handler'] == 'og' || strpos($field['settings']['handler'], 'og_') === 0); } /** * Get the name of the group-audience type field. * * @param $entity_type * The entity type. * @param $bundle_name * The bundle name to be checked. * @param $group_type * Filter list to only include fields referencing a specific group type. * @param $group_bundle * Filter list to only include fields referencing a specific group bundle. * Fields that do not specify any bundle restrictions at all are also * included. * * @return * Array keyed with the field name and the field label as the value. */ function og_get_group_audience_fields($entity_type = 'user', $bundle_name = 'user', $group_type = NULL, $group_bundle = NULL) { $return = &drupal_static(__FUNCTION__, array()); $identifier = $entity_type . ':' . $bundle_name . ':' . $group_type . ':' . $group_bundle; if (isset($return[$identifier])) { return $return[$identifier]; } $return[$identifier] = array(); foreach (field_info_instances($entity_type, $bundle_name) as $field_name => $instance) { if (!og_is_group_audience_field($field_name)) { continue; } $field_info = field_info_field($instance['field_name']); if (isset($group_type) && $field_info['settings']['target_type'] != $group_type) { continue; } if ($group_bundle && !empty($field_info['settings']['handler_settings']['target_bundles']) && !in_array($group_bundle, $field_info['settings']['handler_settings']['target_bundles'])) { continue; } $return[$identifier][$field_name] = $instance['label']; } return $return[$identifier]; } /** * Return the group type (i.e. "group" or "group_content") of an entity. * * @param $entity_type * The entity type. * @param $bundle_name * The bundle name to be checked. * @param $type * The group usage type. Must be "group" or "group content". * * @return * The group type or an "omitted" if node type doesn't participate in * Group. */ function og_get_group_type($entity_type, $bundle_name, $type = 'group') { if ($type == 'group') { return (bool)field_info_instance($entity_type, OG_GROUP_FIELD, $bundle_name); } elseif ($type == 'group content') { return (bool)og_get_group_audience_fields($entity_type, $bundle_name); } } /** * Return TRUE if the entity type is a "group" type. * * This is a wrapper function around og_get_group_type(). * * @param $node_type * The node type to be checked. */ function og_is_group_type($entity_type, $bundle_name) { return og_get_group_type($entity_type, $bundle_name); } /** * Return TRUE if the entity type is a "group content" type. * * This is a wrapper function around og_get_group_type(). * * @param $entity_type * The entity type to be checked. */ function og_is_group_content_type($entity_type, $bundle_name) { return og_get_group_type($entity_type, $bundle_name, 'group content'); } /** * Return all entity types that have bundles that are a group type. * * @return * Array keyed with the entity type machine name and the entity human readable * name as the value, or an empty array if no entities are defined as group. */ function og_get_all_group_entity() { $return = array(); foreach (entity_get_info() as $entity_type => $entity_value) { foreach ($entity_value['bundles'] as $bundle => $bundle_value) { if (og_is_group_type($entity_type, $bundle)) { $return[$entity_type] = check_plain($entity_value['label']); // At least one bundle of the entity can be a group, so break. break; } } } return $return; } /** * Return all bundles that are a group type. * * @return * An associative array whose keys are entity types, and whose values are * arrays of bundles for that entity type. The array of bundles is keyed by * bundle machine name, and the values are bundle labels. */ function og_get_all_group_bundle() { $return = array(); foreach (entity_get_info() as $entity_type => $entity_value) { foreach ($entity_value['bundles'] as $bundle => $bundle_value) { if (og_is_group_type($entity_type, $bundle)) { $return[$entity_type][$bundle] = check_plain($bundle_value['label']); } } } return $return; } /** * Return all the entities that are a group content. * * @return * Array keyed with the entity type machine name and the entity human readable * name as the value, or an empty array if no entities are defined as group * content. */ function og_get_all_group_content_entity() { $return = array(); foreach (entity_get_info() as $entity_type => $entity_value) { foreach ($entity_value['bundles'] as $bundle => $bundle_value) { if (og_is_group_content_type($entity_type, $bundle)) { $return[$entity_type] = check_plain($entity_value['label']); // At least one bundle of the entity can be a group, so break. break; } } } return $return; } /** * Return all the entities that are a group content. * * @return * Array keyed with the entity type machine name and the entity human readable * name as the value, or an empty array if no entities are defined as group * content. */ function og_get_all_group_content_bundle() { $return = array(); foreach (entity_get_info() as $entity_type => $entity_value) { foreach ($entity_value['bundles'] as $bundle => $bundle_value) { if (og_is_group_content_type($entity_type, $bundle)) { $return[$entity_type][$bundle] = check_plain($bundle_value['label']); } } } return $return; } /** * Return TRUE if entity belongs to a group. * * @param $group_type * The entity type of the group. * @param $gid * The group ID. * @param $entity_type * The entity type. * @param $entity * The entity object. If empty the current user will be used. * @param $states * (optional) Array with the state to return. If empty groups of all state will * return. * * @return * TRUE if the entity (e.g. the user) belongs to a group and is not pending or * blocked. */ function og_is_member($group_type, $gid, $entity_type = 'user', $entity = NULL, $states = array(OG_STATE_ACTIVE)) { $groups = og_get_entity_groups($entity_type, $entity, $states); return !empty($groups[$group_type]) && in_array($gid, $groups[$group_type]); } /** * Check if group should use default roles and permissions. * * @param $group_type * The entity type of the group. * @param $gid * The group ID or the group entity. * * @return * TRUE if group should use default roles and permissions. */ function og_is_group_default_access($group_type, $gid) { $wrapper = entity_metadata_wrapper($group_type, $gid); $bundle = $wrapper->getBundle(); if (!field_info_instance($group_type, OG_DEFAULT_ACCESS_FIELD, $bundle)) { return variable_get("og_is_group_default_access__{$group_type}__{$bundle}", TRUE); } if (empty($wrapper->{OG_DEFAULT_ACCESS_FIELD})) { return TRUE; } return !$wrapper->{OG_DEFAULT_ACCESS_FIELD}->value(); } /** * Determine the permissions for one or more roles. * * @param $roles * An array whose keys are the role IDs of interest. * * @return * An array indexed by role ID. Each value is an array whose keys are the * permission strings for the given role ID. */ function og_role_permissions($roles = array()) { $cache = &drupal_static(__FUNCTION__, array()); $role_permissions = $fetch = array(); if ($roles) { foreach ($roles as $rid => $name) { if (isset($cache[$rid])) { $role_permissions[$rid] = $cache[$rid]; } else { // Add this rid to the list of those needing to be fetched. $fetch[] = $rid; // Prepare in case no permissions are returned. $cache[$rid] = array(); } } if ($fetch) { // Get from the database permissions that were not in the static variable. // Only role IDs with at least one permission assigned will return rows. $result = db_query("SELECT rid, permission FROM {og_role_permission} WHERE rid IN (:fetch)", array(':fetch' => $fetch)); foreach ($result as $row) { $cache[$row->rid][$row->permission] = TRUE; } foreach ($fetch as $rid) { // For every rid, we know we at least assigned an empty array. $role_permissions[$rid] = $cache[$rid]; } } } return $role_permissions; } /** * Retrieve an array of roles matching specified conditions. * * @param $group_type * The group type. * @param $bundle * The bundle type. * @param $gid * The group ID. * @param $force_group * (optional) If TRUE then the roles of the group will be retrieved by the * group ID, even if the group is set to have default roles and permissions. * The group might be set to "Default access" but infact there are inactive * group roles. Thus, we are forcing the function to return the overriden * roles. see og_delete_user_roles_by_group(). * @param $include_all * (optional) If TRUE then the anonymous and authenticated default roles will * be included. * * @return * An associative array with the role id as the key and the role name as * value. The anonymous and authenticated default roles are on the top of the * array. */ function og_roles($group_type, $bundle, $gid = 0, $force_group = FALSE, $include_all = TRUE) { if ($gid && !$bundle) { $wrapper = entity_metadata_wrapper($group_type, $gid); $bundle = $wrapper->getBundle(); } // Check if overriden access exists. if ($gid && !$force_group) { $query_gid = og_is_group_default_access($group_type, $gid) ? 0 : $gid; } else { $query_gid = $gid; } $query = db_select('og_role', 'ogr') ->fields('ogr', array('rid', 'name')) ->condition('group_type', $group_type, '=') ->condition('group_bundle', $bundle, '=') ->condition('gid', $query_gid, '=') ->orderBy('rid', 'ASC'); if (!$include_all) { $query->condition('name', array(OG_ANONYMOUS_ROLE, OG_AUTHENTICATED_ROLE), 'NOT IN'); } $rids = $query ->execute() ->fetchAllkeyed(); return $rids; } /** * Get array of default roles, keyed by their declaring module. * * @param $include * (optional) If TRUE also anonymous and authenticated roles will be returned. * Defaults to TRUE. * * @return * Array of default roles, grouped by module name. */ function og_get_default_roles($include = TRUE) { $roles = array(); foreach (module_implements('og_default_roles') as $module) { $roles = array_merge($roles, module_invoke($module, 'og_default_roles')); } // Allow other modules to alter the defult roles, excpet of the anonymous and // authenticated. drupal_alter('og_default_roles', $roles); if ($include) { array_unshift($roles, OG_AUTHENTICATED_ROLE); array_unshift($roles, OG_ANONYMOUS_ROLE); } return $roles; } /** * Get all roles of a user in a certain group. * * @param $group_type * The entity type of the group. * @param $gid * The group ID. * @param $uid * (optional) Integer specifying the user ID. By default an ID of current * logged in user will be used. * @param $include * (optional) If TRUE also anonymous or authenticated role ID will be * returned. Defaults to TRUE. * @param $check_active * (optional) If TRUE, and the user is pending, only anonymous role will be * returned. If blocked, no role will be returned. * * @return * Array with the role IDs of the user as the key, and the role name as * the value. */ function og_get_user_roles($group_type, $gid, $uid = NULL, $include = TRUE, $check_active = TRUE) { $roles = &drupal_static(__FUNCTION__, array()); if (empty($uid)) { global $user; $uid = $user->uid; } $account = user_load($uid); $identifier = implode(':', array($group_type, $gid, $uid, $include)); if (isset($roles[$identifier])) { return $roles[$identifier]; } $is_blocked = og_is_member($group_type, $gid, 'user', $account, array(OG_STATE_BLOCKED)); if ($check_active && $is_blocked) { $roles[$identifier] = array(); return $roles[$identifier]; } $is_member = og_is_member($group_type, $gid, 'user', $account); $rids = array(); $group = entity_load_single($group_type, $gid); // Get the bundle of the group. list(,, $bundle) = entity_extract_ids($group_type, $group); // Check if roles are overriden for the group. $query_gid = og_is_group_default_access($group_type, $gid) ? 0 : $gid; if (!$check_active || $is_member) { $query = db_select('og_users_roles', 'ogur'); $query->innerJoin('og_role', 'ogr', 'ogur.rid = ogr.rid'); $rids = $query ->fields('ogur', array('rid')) ->fields('ogr', array('name')) ->condition('ogr.group_type', $group_type, '=') ->condition('ogr.group_bundle', $bundle, '=') ->condition('ogr.gid', $query_gid, '=') ->condition('ogur.uid', $uid, '=') ->condition('ogur.gid', $gid, '=') ->orderBy('rid') ->execute() ->fetchAllkeyed(); } if ($include && !$is_blocked) { $role_name = $is_member ? OG_AUTHENTICATED_ROLE : OG_ANONYMOUS_ROLE; $rids = db_select('og_role', 'ogr') ->fields('ogr', array('rid', 'name')) ->condition('group_type', $group_type, '=') ->condition('group_bundle', $bundle, '=') ->condition('gid', $query_gid, '=') ->condition('name', $role_name, '=') ->execute() ->fetchAllkeyed() + $rids; } $roles[$identifier] = $rids; return $rids; } /** * Create a stub OG role object. * * @param $name * A name of the role. * @param $group_type * (optional) The entity type of the group. * @param $gid * (optional) The group ID. * @param $group_bundle * (optional) The bundle of the group. * * @return * A stub OG role object. */ function og_role_create($name, $group_type = '', $gid = 0, $group_bundle = '') { $role = new stdClass; $role->name = $name; $role->gid = $gid; $role->group_type = $group_type; $role->group_bundle = $group_bundle; return $role; } /** * Fetch a user role from database. * * @param $rid * An integer with the role ID. * * @return * A fully-loaded role object if a role with the given ID exists, * FALSE otherwise. */ function og_role_load($rid) { return db_select('og_role', 'r') ->fields('r') ->condition('rid', $rid) ->execute() ->fetchObject(); } /** * Save a user role to the database. * * @param $role * A role object to modify or add. If $role->rid is not specified, a new * role will be created. * * @return * Status constant indicating if role was created or updated. * Failure to write the user role record will return FALSE. Otherwise. * SAVED_NEW or SAVED_UPDATED is returned depending on the operation * performed. */ function og_role_save($role) { if ($role->name) { // Prevent leading and trailing spaces in role names. $role->name = trim($role->name); } if (!empty($role->rid) && $role->name) { $status = drupal_write_record('og_role', $role, 'rid'); module_invoke_all('og_role_update', $role); } else { $status = drupal_write_record('og_role', $role); module_invoke_all('og_role_insert', $role); } og_invalidate_cache(); return $status; } /** * Delete a user role from database. * * @param $rid * An integer with the role ID. */ function og_role_delete($rid) { $role = og_role_load($rid); db_delete('og_role') ->condition('rid', $rid) ->execute(); db_delete('og_role_permission') ->condition('rid', $rid) ->execute(); // Update the users who have this role set. db_delete('og_users_roles') ->condition('rid', $rid) ->execute(); module_invoke_all('og_role_delete', $role); og_invalidate_cache(); } /** * Delete all roles belonging to a group. * * This will also maintain user roles when revertting an ovverriden group. * For example, if in the overridden group users were assigned to the role * "administrator", upon reverting back to default roles and * permissions, OG will search for existing roles with that name, and re-assign * the correct role ID, and the users that had "administrator" will still have * it. * * @param $group_type * The group type. * @param $gid * The group ID. */ function og_delete_user_roles_by_group($group_type, $group) { // Check if group has overriden roles defined. list($gid, $vid,$bundle) = entity_extract_ids($group_type, $group); $global_roles = array_flip(og_roles($group_type, $bundle)); if ($roles = og_roles($group_type, $bundle, $gid, TRUE)) { foreach ($roles as $rid => $name) { if (variable_get('og_maintain_overridden_roles', TRUE) && !empty($global_roles[$name])) { // Role name exists in the global roles, update the role ID to the // global one. db_update('og_users_roles') ->fields(array('rid' => $global_roles[$name])) ->condition('rid', $rid) ->condition('group_type', $group_type) ->condition('gid', $gid) ->execute(); } og_role_delete($rid); } } } /** * Get the role names of role IDs. * * @param $rids * Array with role IDs. * @return * Array keyed by the role ID, and the role name as the value. */ function og_get_user_roles_name($rids = array()) { if ($rids) { $query = db_query("SELECT rid, name FROM {og_role} WHERE rid IN (:rids)", array(':rids' => $rids)); } else { $query = db_query("SELECT rid, name FROM {og_role}"); } return $query->fetchAllKeyed(); } /** * Delete all permissions defined by a module. * * @see og_modules_uninstalled() * * @param $modules * Array with the module names. */ function og_permissions_delete_by_module($modules = array()) { db_delete('og_role_permission') ->condition('module', $modules, 'IN') ->execute(); } /** * Create new roles, based on the default roles and permissions. * * @param $group_type * The group type. * @param $bundle * The bundle type. * @param $gid * The group ID. * * @return * The newly created roles keyed by role ID and role name as the value. Or * FALSE if no roles were created. */ function og_roles_override($group_type, $bundle, $gid) { // Check if roles aren't already overridden. We can't use // og_is_group_default_access() as the field is already set, so we // check to see if there are new roles in the database by setting // "force group" parameter to TRUE. if (og_roles($group_type, $bundle, $gid, TRUE)) { return; } $rids = array(); if ($gid) { // Copy roles from a specific group $og_roles = og_roles($group_type, $bundle); $perms = og_role_permissions($og_roles); } else { // Copy the global default roles $og_roles = og_get_default_roles(); $perms = og_get_default_permissions(); } foreach ($og_roles as $rid => $name) { $role = og_role_create($name, $group_type, $gid, $bundle); og_role_save($role); $rids[$role->rid] = $role->name; og_role_change_permissions($role->rid, $perms[$rid]); // Remap the default roles, to the newely created ones. db_update('og_users_roles') ->fields(array('rid' => $role->rid)) ->condition('rid', $rid) ->condition('group_type', $group_type) ->condition('gid', $gid) ->execute(); } return $rids; } /** * Grant a group role to a user. * * @param $group_type * The entity type of the group. * @param $gid * The group ID. * @param $uid * The user ID. * @param $rid * The role ID. */ function og_role_grant($group_type, $gid, $uid, $rid) { // Make sure the role is valid. $group = entity_load_single($group_type, $gid); list(,, $bundle) = entity_extract_ids($group_type, $group); $og_roles = og_roles($group_type, $bundle, $gid, FALSE, FALSE); if (empty($og_roles[$rid])) { // Role isn't valid. return; } // Get the existing user roles. $user_roles = og_get_user_roles($group_type, $gid, $uid, TRUE, FALSE); if (empty($user_roles[$rid])) { $role = new stdClass(); $role->uid = $uid; $role->rid = $rid; $role->group_type = $group_type; $role->gid = $gid; drupal_write_record('og_users_roles', $role); og_invalidate_cache(); module_invoke_all('og_role_grant', $group_type, $gid, $uid, $rid); if (module_exists('rules')) { rules_invoke_event('og_role_grant', og_get_membership($group_type, $gid, 'user', $uid), entity_metadata_wrapper('user', $uid), $rid); } } } /** * Revoke a group role from a user. * * @param $group_type * The entity type of the group. * @param $gid * The group ID. * @param $uid * The user ID. * @param $rid * The role ID. */ function og_role_revoke($group_type, $gid, $uid, $rid) { $og_roles = og_get_user_roles($group_type, $gid, $uid); if (!empty($og_roles[$rid])) { db_delete('og_users_roles') ->condition('uid', $uid) ->condition('rid', $rid) ->condition('group_type', $group_type) ->condition('gid', $gid) ->execute(); og_invalidate_cache(); module_invoke_all('og_role_revoke', $group_type, $gid, $uid, $rid); if (module_exists('rules')) { rules_invoke_event('og_role_revoke', og_get_membership($group_type, $gid, 'user', $uid), entity_metadata_wrapper('user', $uid), $rid); } } } /** * Change permissions for a user role. * * This function may be used to grant and revoke multiple permissions at once. * For example, when a form exposes checkboxes to configure permissions for a * role, the submitted values may be directly passed on in a form submit * handler. * * @param $rid * The ID of a group user role to alter. * @param $permissions * An array of permissions, where the key holds the permission name and the * value is an integer or boolean that determines whether to grant or revoke * the permission: * @code * array( * 'edit group' => 0, * 'administer group' => 1, * ) * @endcode * Existing permissions are not changed, unless specified in $permissions. * * @see og_role_grant_permissions() * @see og_role_revoke_permissions() */ function og_role_change_permissions($rid, array $permissions = array()) { // Grant new permissions for the role. $grant = array_filter($permissions); if (!empty($grant)) { og_role_grant_permissions($rid, array_keys($grant)); } // Revoke permissions for the role. $revoke = array_diff_assoc($permissions, $grant); if (!empty($revoke)) { og_role_revoke_permissions($rid, array_keys($revoke)); } if (!empty($grant) || !empty($revoke)) { // Allow modules to be notified on permission changes. $role = og_role_load($rid); module_invoke_all('og_role_change_permissions', $role, $grant, $revoke); } } /** * Grant permissions to a user role. * * @param $rid * The ID of a user role to alter. * @param $permissions * A list of permission names to grant. * * @see user_role_change_permissions() * @see user_role_revoke_permissions() */ function og_role_grant_permissions($rid, array $permissions = array()) { $modules = array(); foreach (og_get_permissions() as $name => $value) { $modules[$name] = $value['module']; } // Grant new permissions for the role. foreach ($permissions as $name) { // Prevent WSOD, if the permission name is wrong, and we can't find its // module. if (!empty($modules[$name])) { db_merge('og_role_permission') ->key(array( 'rid' => $rid, 'permission' => $name, 'module' => $modules[$name], )) ->execute(); } } og_invalidate_cache(); } /** * Revoke permissions from a user role. * * @param $rid * The ID of a user role to alter. * @param $permissions * A list of permission names to revoke. * * @see user_role_change_permissions() * @see user_role_grant_permissions() */ function og_role_revoke_permissions($rid, array $permissions = array()) { // Revoke permissions for the role. db_delete('og_role_permission') ->condition('rid', $rid) ->condition('permission', $permissions, 'IN') ->execute(); og_invalidate_cache(); } /** * Get all permissions defined by implementing modules. * * @return * Array keyed with the permissions name and the value of the permissions. * TODO: Write the values. */ function og_get_permissions() { $perms = &drupal_static(__FUNCTION__, array()); if (!empty($perms)) { return $perms; } foreach (module_implements('og_permission') as $module) { if ($permissions = module_invoke($module, 'og_permission')) { foreach ($permissions as $key => $perm) { $permissions[$key] += array( // Initialize the roles key, if other modules haven't set it // explicetly. This means the permissions can apply to anonymous and // authenticated members as-well. 'roles' => array(OG_ANONYMOUS_ROLE, OG_AUTHENTICATED_ROLE), 'default role' => array(), 'module' => $module, ); } $perms = array_merge($perms, $permissions); } } // Allow other modules to alter the permissions. drupal_alter('og_permission', $perms); return $perms; } /** * Get default permissions. * * @return * Array keyed with the anonymous, authenticated and administror and the * permissions that should be enabled by default. */ function og_get_default_permissions() { $roles = og_get_default_roles(); $default_perms = og_get_permissions(); $perms = array(); foreach ($roles as $rid => $role_name) { $perms[$rid] = array(); // For each default role, iterate default permissions and mark the // permissions that set the role as default. foreach ($default_perms as $perm_name => $perm) { if (in_array($role_name, $perm['default role'])) { $perms[$rid][$perm_name] = TRUE; } } } return $perms; } /** * Get all the modules fields that can be assigned to fieldable entities. * * @param $field_name * The field name that was registered for the definition. * * @return * An array with the field and instance definitions, or FALSE if not * found. */ function og_fields_info($field_name = NULL) { $return = &drupal_static(__FUNCTION__, array()); if (empty($return)) { foreach (module_implements('og_fields_info') as $module) { if ($fields = module_invoke($module, 'og_fields_info')) { foreach ($fields as $key => $field) { // Add default values. $field += array( 'entity type' => array(), 'multiple' => FALSE, 'description' => '', ); // Add the module information. $return[$key] = array_merge($field, array('module' => $module)); } } } // Allow other modules to alter the field info. drupal_alter('og_fields_info', $return); } if (!empty($field_name)) { return !empty($return[$field_name]) ? $return[$field_name] : FALSE; } return $return; } /** * Set breadcrumbs according to a given group. * * @param $entity_type * The entity type. * @param $etid * The entity ID. * @param $path * (optional) The path to append to the breadcrumb. */ function og_set_breadcrumb($entity_type, $etid, $path = array()) { $entity = entity_load_single($entity_type, $etid); $label = entity_label($entity_type, $entity); $uri = entity_uri($entity_type, $entity); drupal_set_breadcrumb(array_merge(array(l(t('Home'), '')), array(l($label, $uri['path'])), $path)); } /** * Create an organic groups field in a bundle. * * @param $field_name * The field name * @param $entity_type * The entity type * @param $bundle * The bundle name. * @param $og_field * (optional) Array with field definitions, to allow easier overriding by the * caller. If empty, function will get the field info by calling * og_fields_info() with the field name. */ function og_create_field($field_name, $entity_type, $bundle, $og_field = array()) { if (empty($og_field)) { $og_field = og_fields_info($field_name); } $field = field_info_field($field_name); // Allow overriding the field name. $og_field['field']['field_name'] = $field_name; if (empty($field)) { $field = field_create_field($og_field['field']); } $instance = field_info_instance($entity_type, $field_name, $bundle); if (empty($instance)) { $instance = $og_field['instance']; $instance += array( 'field_name' => $field_name, 'bundle' => $bundle, 'entity_type' => $entity_type, ); field_create_instance($instance); // Clear the entity property info cache, as OG fields might add different // entity property info. og_invalidate_cache(); entity_property_info_cache_clear(); } } /** * Return the states a group can be in. */ function og_group_states() { return array( OG_STATE_ACTIVE => t('Active'), OG_STATE_PENDING => t('Pending'), ); } /** * Return the states a group content can be in. */ function og_group_content_states() { return array( OG_STATE_ACTIVE => t('Active'), OG_STATE_PENDING => t('Pending'), OG_STATE_BLOCKED => t('Blocked'), ); } /** * Return a list of fieldable entities. * * @return * Array keyed with the entity machine name and the saniztized human name as * the value. */ function og_get_fieldable_entity_list() { $return = array(); foreach (entity_get_info() as $name => $info) { if (!empty($info['fieldable'])) { $return[$name] = check_plain($info['label']); } } return $return; } /** * Helper function to generate standard node permission list for a given type. * * @param $type * The machine-readable name of the node type. * * @return array * An array of permission names and descriptions. */ function og_list_permissions($type) { $info = node_type_get_type($type); $type = check_plain($info->type); $perms = array(); // Check type is of group content. if (og_is_group_content_type('node', $type)) { // Build standard list of node permissions for this type. $perms += array( "create $type content" => array( 'title' => t('Create %type_name content', array('%type_name' => $info->name)), ), "update own $type content" => array( 'title' => t('Edit own %type_name content', array('%type_name' => $info->name)), ), "update any $type content" => array( 'title' => t('Edit any %type_name content', array('%type_name' => $info->name)), ), "delete own $type content" => array( 'title' => t('Delete own %type_name content', array('%type_name' => $info->name)), ), "delete any $type content" => array( 'title' => t('Delete any %type_name content', array('%type_name' => $info->name)), ), ); if (!module_exists('entityreference_prepopulate')) { // We allow the create permission only on members, as otherwise we would // have to iterate over every single group to decide if the user has // permissions for it. $perms["create $type content"]['roles'] = array(OG_AUTHENTICATED_ROLE); } // Add default permissions. foreach ($perms as $key => $value) { $perms[$key]['default role'] = array(OG_AUTHENTICATED_ROLE); } } return $perms; } /** * Return a form element with crafted links to create nodes for a group. * * @param $group_type * The entity type of the group. * @param $gid * The group ID. * @param $field_name * The group audience field name. * @param $destination * (optional) The destiantion after a node is created. Defaults to the * destination passed in the URL if exists, otherwise back to the current * page. FALSE to not append any destination to node create links. * @param $types * (optional) An array of type names. Restrict the created links to the given * types. */ function og_node_create_links($group_type, $gid, $field_name, $destination = NULL, $types = NULL) { if (!og_is_group($group_type, $gid)) { return; } $types = isset($types) ? $types : array_keys(node_type_get_types()); foreach ($types as $type_name) { if (!og_is_group_content_type('node', $type_name) || !og_user_access($group_type, $gid, "create $type_name content")) { continue; } $instance = field_info_instance('node', $field_name, $type_name); if (empty($instance['settings']['behaviors']['prepopulate']['status'])) { // Instance doesn't allow prepopulating. continue; } $names[$type_name] = node_type_get_name($type_name); } if (empty($names)) { return; } // Sort names. asort($names); // Build links. $options = array( 'query' => array($field_name => $gid), ); if ($destination) { $options['query']['destination'] = $destination; } elseif ($destination !== FALSE) { $options['query'] += drupal_get_destination(); } $items = array(); foreach ($names as $type => $name) { // theme_item_list's 'data' items isn't a render element, so use l(). // http://drupal.org/node/891112 $items[] = array('data' => l($name, 'node/add/' . str_replace('_', '-', $type), $options)); } $element = array(); $element['og_node_create_links'] = array( '#theme' => 'item_list', '#items' => $items, ); return $element; } /** * Get the group IDs of all the groups a user is an approved member of. * * @param $account * (optional) The user object to fetch group memberships for. Defaults to the * acting user. * @param $group_type * (optional) The entity type of the groups to fetch. By default all group * types will be fetched. * * @return * An array with the group IDs or an empty array. */ function og_get_groups_by_user($account = NULL, $group_type = NULL) { if (empty($account)) { global $user; $account = $user; } if (!og_get_group_audience_fields()) { // User entity doesn't have group audience fields. return; } $gids = array(); // Get all active OG membership that belong to the user. $wrapper = entity_metadata_wrapper('user', $account->uid); $og_memberships = $wrapper->{'og_membership__' . OG_STATE_ACTIVE}->value(); if (!$og_memberships) { return; } foreach ($og_memberships as $og_membership) { if (!empty($og_membership)) { $gids[$og_membership->group_type][$og_membership->gid] = $og_membership->gid; } } if (empty($group_type)) { return $gids; } elseif (!empty($gids[$group_type])) { return $gids[$group_type]; } } /** * Implements hook_action_info(). * * @see views_bulk_operations_action_info() */ function og_action_info() { $actions = array(); $files = og_operations_load_action_includes(); foreach ($files as $filename) { $action_info_fn = 'og_'. str_replace('.', '_', basename($filename, '.inc')).'_info'; $action_info = call_user_func($action_info_fn); if (is_array($action_info)) { $actions += $action_info; } } return $actions; } /** * Loads the VBO actions placed in their own include files. * * @return * An array of containing filenames of the included actions. * * @see views_bulk_operations_load_action_includes() */ function og_operations_load_action_includes() { static $loaded = FALSE; $path = drupal_get_path('module', 'og') . '/includes/actions/'; $files = array( 'user_roles.action.inc', 'set_state.action.inc', 'membership_delete.action.inc', ); if (!$loaded) { foreach ($files as $file) { include_once $path . $file; } $loaded = TRUE; } return $files; } /** * Implements hook_features_api(). */ function og_features_api() { return array( 'og_features_role' => array( 'name' => t('OG Role'), 'feature source' => TRUE, 'default_hook' => 'og_features_default_roles', 'default_file' => FEATURES_DEFAULTS_INCLUDED, 'file' => drupal_get_path('module', 'og') . '/includes/og_features_role.features.inc', ), 'og_features_permission' => array( 'name' => t('OG Permissions'), 'feature_source' => TRUE, 'default_hook' => 'og_features_default_permissions', 'default_file' => FEATURES_DEFAULTS_INCLUDED, 'file' => drupal_get_path('module', 'og') . '/includes/og_features_permission.features.inc', ), ); } /** * Implements hook_features_pipe_alter(). * * Prevent OG related fields from being piped in features, when a content * type that has them is selected. * * This if compatible with Features 1.x and 2.x */ function og_features_pipe_alter(&$pipe, $data, $export) { if (!variable_get('og_features_ignore_og_fields', FALSE)) { return; } if (empty($pipe['field']) && empty($pipe['field_base']) && empty($pipe['field_instance'])) { // The exported item is not a field. return; } if (!empty($pipe['field_instance'])) { $key = 'field_instance'; $explode = TRUE; } elseif (!empty($pipe['field_base'])) { $key = 'field_base'; $explode = FALSE; } else { $key = 'field'; $explode = TRUE; } foreach ($pipe[$key] as $delta => $value) { if ($explode) { // Get the field name from the [entity-type]-[bundle]-[field-name]. $args = explode('-', $value); $field_name = $args[2]; } else { $field_name = $value; } if (og_fields_info($field_name) || og_is_group_audience_field($field_name)) { unset($pipe[$key][$delta]); } } } /** * Implements hook_migrate_api(). */ function og_migrate_api() { $migrations = array(); if (db_table_exists('d6_og')) { $migrations['OgMigrateAddFields'] = array('class_name' => 'OgMigrateAddFields'); $migrations['OgMigrateContent'] = array('class_name' => 'OgMigrateContent'); $migrations['OgMigrateUser'] = array('class_name' => 'OgMigrateUser'); foreach (node_type_get_names() as $bundle => $value) { $machine_name = 'OgMigrateGroup' . ucfirst($bundle); $migrations[$machine_name] = array( 'class_name' => 'OgMigrateGroup', 'bundle' => $bundle, ); } if (db_table_exists('d6_og_users_roles')) { // OG user roles (OGUR) related migrations. $migrations['OgMigrateOgurRoles'] = array('class_name' => 'OgMigrateOgurRoles'); $migrations['OgMigrateOgur'] = array('class_name' => 'OgMigrateOgur'); } } elseif (db_field_exists('og_membership', 'group_type') && db_table_exists('og') && !db_table_exists('d6_og')) { $migrations['OgMigrateMembership'] = array('class_name' => 'OgMigrateMembership'); $migrations['OgMigrateRoles'] = array('class_name' => 'OgMigrateRoles'); $migrations['OgMigrateUserRoles'] = array('class_name' => 'OgMigrateUserRoles'); } $api = array( 'api' => 2, 'migrations' => $migrations, ); return $api; } /** * Implements hook_flush_caches(). */ function og_flush_caches() { $bins = array( 'cache_entity_og_membership', 'cache_entity_og_membership_type', ); return $bins; }