diff --git a/modules/grequest/config/install/field.storage.group_content.grequest_status.yml b/modules/grequest/config/install/field.storage.group_content.grequest_status.yml new file mode 100644 index 0000000..496fd0d --- /dev/null +++ b/modules/grequest/config/install/field.storage.group_content.grequest_status.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - group + - grequest +id: group_content.grequest_status +field_name: grequest_status +entity_type: group_content +type: integer +settings: + unsigned: false + size: normal +module: core +locked: true +cardinality: 1 +translatable: false +indexes: { } +persist_with_no_fields: true +custom_storage: false diff --git a/modules/grequest/config/install/field.storage.group_content.grequest_updated_by.yml b/modules/grequest/config/install/field.storage.group_content.grequest_updated_by.yml new file mode 100644 index 0000000..23222eb --- /dev/null +++ b/modules/grequest/config/install/field.storage.group_content.grequest_updated_by.yml @@ -0,0 +1,20 @@ +langcode: en +status: TRUE +dependencies: + enforced: + module: + - group + - grequest +id: group_content.grequest_updated_by +field_name: grequest_updated_by +entity_type: group_content +type: entity_reference +settings: + target_type: user +module: core +locked: true +cardinality: 1 +translatable: false +indexes: { } +persist_with_no_fields: true +custom_storage: false diff --git a/modules/grequest/config/optional/views.view.group_pending_members.yml b/modules/grequest/config/optional/views.view.group_pending_members.yml new file mode 100644 index 0000000..f0d615a --- /dev/null +++ b/modules/grequest/config/optional/views.view.group_pending_members.yml @@ -0,0 +1,660 @@ +langcode: en +status: true +dependencies: + module: + - user + enforced: + module: + - group + - grequest +id: group_pending_members +label: 'Group pending members' +module: views +description: '' +tag: '' +base_table: group_content_field_data +base_field: id +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: group_permission + options: + group_permission: 'administer members' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + label: label + info: + label: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: '-1' + empty_table: false + row: + type: fields + fields: + name: + id: name + table: users_field_data + field: name + relationship: gc__user + group_type: group + admin_label: '' + label: Name + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: user_name + settings: + link_to_entity: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: user + entity_field: name + plugin_id: field + created: + id: created + table: group_content_field_data + field: created + relationship: none + group_type: group + admin_label: '' + label: 'Requested on' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp + settings: + date_format: short + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: group_content + entity_field: created + plugin_id: field + gid: + id: gid + table: group_content_field_data + field: gid + relationship: none + group_type: group + admin_label: '' + label: 'Parent group' + exclude: true + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_entity_id + settings: { } + group_column: target_id + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: group_content + entity_field: gid + plugin_id: field + id: + id: id + table: group_content_field_data + field: id + relationship: none + group_type: group + admin_label: '' + label: ID + exclude: true + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: group_content + entity_field: id + plugin_id: field + nothing: + id: nothing + table: views + field: nothing + relationship: none + group_type: group + admin_label: '' + label: 'Approve membership' + exclude: true + alter: + alter_text: true + text: 'Approve membership' + make_link: false + path: '/group/{{ raw_arguments.gid }}/content/{{ id }}/approve-membership' + absolute: true + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: 'Approve membership' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: false + plugin_id: custom + nothing_1: + id: nothing_1 + table: views + field: nothing + relationship: none + group_type: group + admin_label: '' + label: 'Reject Membership' + exclude: true + alter: + alter_text: true + text: 'Reject Membership' + make_link: false + path: '/group/{{ raw_arguments.gid }}/content/{{ id }}/reject-membership' + absolute: true + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: 'Reject Membership' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: false + plugin_id: custom + dropbutton: + id: dropbutton + table: views + field: dropbutton + relationship: none + group_type: group + admin_label: '' + label: Operations + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + fields: + nothing: nothing + nothing_1: nothing_1 + name: '0' + created: '0' + gid: '0' + id: '0' + destination: true + plugin_id: dropbutton + filters: + grequest_status_value: + id: grequest_status_value + table: group_content__grequest_status + field: grequest_status_value + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: + min: '' + max: '' + value: '0' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + placeholder: '' + min_placeholder: '' + max_placeholder: '' + operator_limit_selection: false + operator_list: { } + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: numeric + sorts: { } + title: 'Group pending members' + header: { } + footer: { } + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: false + content: 'No membership requests available.' + plugin_id: text_custom + relationships: + gc__user: + id: gc__user + table: group_content_field_data + field: gc__user + relationship: none + group_type: group + admin_label: 'Group content User' + required: true + group_content_plugins: + group_membership_request: group_membership_request + group_membership: '0' + entity_type: group_content + plugin_id: group_content_to_entity + arguments: + gid: + id: gid + table: group_content_field_data + field: gid + relationship: none + group_type: group + admin_label: '' + default_action: 'access denied' + exception: + value: all + title_enable: false + title: All + title_enable: true + title: '{{ arguments.gid|placeholder }} pending members' + default_argument_type: fixed + default_argument_options: + argument: '3' + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: false + validate: + type: none + fail: 'not found' + validate_options: { } + break_phrase: false + not: false + entity_type: group_content + entity_field: gid + plugin_id: group_id + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - route.group + - url + - url.query_args + - user.group_permissions + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: group/%group/members-pending + menu: + type: tab + title: 'Membership requests' + description: '' + expanded: false + parent: '' + weight: 21 + context: '0' + menu_name: main + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - route.group + - url + - url.query_args + - user.group_permissions + tags: { } diff --git a/modules/grequest/grequest.info.yml b/modules/grequest/grequest.info.yml new file mode 100644 index 0000000..0c8f7ad --- /dev/null +++ b/modules/grequest/grequest.info.yml @@ -0,0 +1,9 @@ +name: 'Group Membership Request' +type: module +description: 'Allows users to request access to Groups' +package: Group +core: 8.x +core_version_requirement: ^8 || ^9 +php: 7.1 +dependencies: + - group:group diff --git a/modules/grequest/grequest.module b/modules/grequest/grequest.module new file mode 100644 index 0000000..f3c544b --- /dev/null +++ b/modules/grequest/grequest.module @@ -0,0 +1,34 @@ +setFormClass('group-approve-membership', 'Drupal\grequest\Entity\Form\GroupMembershipApproveForm') + ->setLinkTemplate('group-approve-membership', '/group/{group}/content/{group_content}/approve-membership') + ->setFormClass('group-reject-membership', 'Drupal\grequest\Entity\Form\GroupMembershipRejectForm') + ->setLinkTemplate('group-reject-membership', '/group/{group}/content/{group_content}/reject-membership') + ->setFormClass('group-request-membership', 'Drupal\grequest\Entity\Form\GroupMembershipRequestForm'); + + $entity_types['group']->setLinkTemplate('group-request-membership', '/group/{group}/request-membership'); +} + +/** + * Implements hook_menu_local_actions_alter(). + */ +function grequest_menu_local_tasks_alter(&$data, $route_name) { + $route_matcher = \Drupal::service('current_route_match'); + $group = $route_matcher->getParameter('group'); + + if ($group instanceof GroupInterface && !$group->getGroupType()->hasContentPlugin('group_membership_request')) { + unset($data['tabs'][0]['views_view:view.group_pending_members.page_1']); + } +} diff --git a/modules/grequest/grequest.routing.yml b/modules/grequest/grequest.routing.yml new file mode 100644 index 0000000..1ed0662 --- /dev/null +++ b/modules/grequest/grequest.routing.yml @@ -0,0 +1,40 @@ +entity.group.group_request_membership: + path: '/group/{group}/request-membership' + defaults: + _controller: '\Drupal\grequest\Controller\GroupMembershipRequestController::requestMembership' + _title_callback: '\Drupal\grequest\Controller\GroupMembershipRequestController::requestMembershipTitle' + requirements: + _group_permission: 'request group membership' + _group_member: 'FALSE' + options: + parameters: + group: + type: 'entity:group' + +entity.group_content.group_approve_membership: + path: '/group/{group}/content/{group_content}/approve-membership' + defaults: + _controller: '\Drupal\grequest\Controller\GroupMembershipRequestController::approveMembership' + _title_callback: '\Drupal\grequest\Controller\GroupMembershipRequestController::approveMembershipTitle' + requirements: + _group_permission: 'administer members' + options: + parameters: + group: + type: 'entity:group' + group_content: + type: 'entity:group_content' + +entity.group_content.group_reject_membership: + path: '/group/{group}/content/{group_content}/reject-membership' + defaults: + _controller: '\Drupal\grequest\Controller\GroupMembershipRequestController::rejectMembership' + _title_callback: '\Drupal\grequest\Controller\GroupMembershipRequestController::rejectMembershipTitle' + requirements: + _group_permission: 'administer members' + options: + parameters: + group: + type: 'entity:group' + group_content: + type: 'entity:group_content' diff --git a/modules/grequest/grequest.views.inc b/modules/grequest/grequest.views.inc new file mode 100644 index 0000000..d0c0a09 --- /dev/null +++ b/modules/grequest/grequest.views.inc @@ -0,0 +1,19 @@ + t('Request Membership'), + 'help' => t('Provides an Request Membership link to the Group.'), + 'field' => [ + 'id' => 'group_request_membership', + ], + ]; +} diff --git a/modules/grequest/src/Controller/GroupMembershipRequestController.php b/modules/grequest/src/Controller/GroupMembershipRequestController.php new file mode 100644 index 0000000..dece110 --- /dev/null +++ b/modules/grequest/src/Controller/GroupMembershipRequestController.php @@ -0,0 +1,110 @@ + $group + ->getGroupType() + ->getContentPlugin('group_membership_request') + ->getContentTypeConfigId(), + 'gid' => $group->id(), + 'entity_id' => $this->currentUser()->id(), + 'grequest_status' => GroupMembershipRequest::REQUEST_PENDING, + ]); + + return $this->entityFormBuilder()->getForm($group_content, 'group-request-membership'); + } + + /** + * The _title_callback for the request membership form route. + * + * @param \Drupal\group\Entity\GroupInterface $group + * The group to request membership of. + * + * @return string + * The page title. + */ + public function requestMembershipTitle(GroupInterface $group) { + return $this->t('Request membership group %label', ['%label' => $group->label()]); + } + + /** + * Provides the form for approval a group membership. + * + * @param \Drupal\group\Entity\GroupInterface $group + * The group where we approve membership. + * + * @return array + * A group approval membership form. + */ + public function approveMembership(GroupInterface $group, GroupContentInterface $group_content) { + return $this->entityFormBuilder()->getForm($group_content, 'group-approve-membership'); + } + + /** + * The _title_callback for the approval requested membership form route. + * + * @param \Drupal\group\Entity\GroupInterface $group + * The group containing the requested membership. + * + * @return string + * The page title. + */ + public function approveMembershipTitle(GroupInterface $group) { + return $this->t('Approve membership request for group %label', ['%label' => $group->label()]); + } + + /** + * Provides the form for rejection a group membership. + * + * @param \Drupal\group\Entity\GroupInterface $group + * The group where we reject membership. + * + * @return array + * A group rejection membership form. + */ + public function rejectMembership(GroupInterface $group, GroupContentInterface $group_content) { + return $this->entityFormBuilder()->getForm($group_content, 'group-reject-membership'); + } + + /** + * The _title_callback for the rejection requested membership form route. + * + * @param \Drupal\group\Entity\GroupInterface $group + * The group containing the requested membership. + * + * @return string + * The page title. + */ + public function rejectMembershipTitle(GroupInterface $group) { + return $this->t('Reject membership request for group %label', ['%label' => $group->label()]); + } + +} diff --git a/modules/grequest/src/Entity/Form/GroupMembershipApproveForm.php b/modules/grequest/src/Entity/Form/GroupMembershipApproveForm.php new file mode 100644 index 0000000..66161ae --- /dev/null +++ b/modules/grequest/src/Entity/Form/GroupMembershipApproveForm.php @@ -0,0 +1,83 @@ +getEntity()->getContentPlugin(); + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->t('Are you sure you want to Approve this request?'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return ''; + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return Url::fromUserInput(\Drupal::destination()->get()); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Approve'); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $group_content = $this->getEntity(); + + $group_content + ->set('grequest_status', GroupMembershipRequest::REQUEST_APPROVED) + // Who created request will become an 'approver' for Membership request. + ->set('grequest_updated_by', $this->currentUser()->id()); + $result = $group_content->save(); + + if ($result) { + $this->messenger()->addStatus($this->t('Membership Request approved')); + + // Adding user to a group. + $group_content->getGroup()->addMember($group_content->getEntity()); + } + else { + $this->messenger()->addError($this->t('Error updating Request')); + } + + \Drupal::logger('group_content')->notice('@type: approved %title.', [ + '@type' => $group_content->bundle(), + '%title' => $group_content->label(), + ]); + + $form_state->setRedirectUrl($this->getCancelUrl()); + } + +} diff --git a/modules/grequest/src/Entity/Form/GroupMembershipRejectForm.php b/modules/grequest/src/Entity/Form/GroupMembershipRejectForm.php new file mode 100644 index 0000000..60eb1b1 --- /dev/null +++ b/modules/grequest/src/Entity/Form/GroupMembershipRejectForm.php @@ -0,0 +1,82 @@ +getEntity()->getContentPlugin(); + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->t('Are you sure you want to Reject this request?'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return ''; + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return Url::fromUserInput(\Drupal::destination()->get()); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Reject'); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $group_content = $this->getEntity(); + + $group_content + ->set('grequest_status', GroupMembershipRequest::REQUEST_REJECTED) + ->set('grequest_updated_by', $this->currentUser()->id()); + $result = $group_content->save(); + + if ($result) { + $this->messenger()->addStatus($this->t('Membership Request rejected')); + + // Adding user to a group. + $group_content->getGroup()->addMember($group_content->getEntity()); + } + else { + $this->messenger()->addError($this->t('Error updating Request')); + } + + \Drupal::logger('group_content')->notice('@type: rejected %title.', [ + '@type' => $group_content->bundle(), + '%title' => $group_content->label(), + ]); + + $form_state->setRedirectUrl($this->getCancelUrl()); + } + +} diff --git a/modules/grequest/src/Entity/Form/GroupMembershipRequestForm.php b/modules/grequest/src/Entity/Form/GroupMembershipRequestForm.php new file mode 100644 index 0000000..66193dd --- /dev/null +++ b/modules/grequest/src/Entity/Form/GroupMembershipRequestForm.php @@ -0,0 +1,107 @@ +get()); + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + $actions = parent::actions($form, $form_state); + $actions['submit']['#value'] = $this->t('Request group membership'); + $actions['cancel'] = [ + '#type' => 'submit', + '#value' => $this->t('Cancel'), + '#submit' => ['::cancelSubmit'], + ]; + + return $actions; + } + + /** + * Form cancel handler. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function cancelSubmit(array &$form, FormStateInterface $form_state) { + $form_state->setRedirectUrl($this->getCancelUrl()); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + // Hide entity_id field, it will be prefilled. + $form['entity_id']['#access'] = FALSE; + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + /** @var \Drupal\group\Entity\GroupContentInterface $group_content */ + $group_content = parent::validateForm($form, $form_state); + $group = $group_content->getGroup(); + $plugin = $group_content->getContentPlugin(); + $entity_cardinality = $plugin->getEntityCardinality(); + + // Get the current instances of this content entity in the group. + $entity_instances = $group->getContentByEntityId($plugin->getPluginId(), $group_content->getEntity()->id()); + $entity_count = count($entity_instances); + + // If the current group content entity has an ID, exclude that one. + if ($group_content_id = $group_content->id()) { + foreach ($entity_instances as $instance) { + if ($instance->id() == $group_content_id) { + $entity_count--; + break; + } + } + } + + // Raise a violation if the content has reached the cardinality limit. + if ($entity_count >= $entity_cardinality) { + $form_state->setErrorByName('', $this->t("You have already submitted a request. It is waiting for Group Administrator's approval")); + } + + return $group_content; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $return = parent::save($form, $form_state); + + $this->messenger()->addMessage($this->t("Your request is waiting for Group Administrator's approval")); + + $form_state->setRedirectUrl($this->getCancelUrl()); + return $return; + } + +} diff --git a/modules/grequest/src/Plugin/GroupContentEnabler/GroupMembershipRequest.php b/modules/grequest/src/Plugin/GroupContentEnabler/GroupMembershipRequest.php new file mode 100644 index 0000000..0cf2421 --- /dev/null +++ b/modules/grequest/src/Plugin/GroupContentEnabler/GroupMembershipRequest.php @@ -0,0 +1,188 @@ +getMember($account) && $group->hasPermission('request group membership', $account)) { + $operations['group-request-membership'] = [ + 'title' => $this->t('Request group membership'), + 'url' => $group->toUrl('group-request-membership'), + 'weight' => 99, + ]; + } + + return $operations; + } + + /** + * {@inheritdoc} + */ + public function createAccess(GroupInterface $group, AccountInterface $account) { + return GroupAccessResult::allowedIfHasGroupPermission($group, $account, 'administer members'); + } + + /** + * {@inheritdoc} + */ + protected function viewAccess(GroupContentInterface $group_content, AccountInterface $account) { + return GroupAccessResult::allowedIfHasGroupPermission($group_content->getGroup(), $account, 'administer members'); + } + + /** + * {@inheritdoc} + */ + protected function updateAccess(GroupContentInterface $group_content, AccountInterface $account) { + return GroupAccessResult::allowedIfHasGroupPermission($group_content->getGroup(), $account, 'administer members'); + } + + /** + * {@inheritdoc} + */ + protected function deleteAccess(GroupContentInterface $group_content, AccountInterface $account) { + return GroupAccessResult::allowedIfHasGroupPermission($group_content->getGroup(), $account, 'administer members'); + } + + /** + * {@inheritdoc} + */ + public function getEntityReferenceSettings() { + $settings = parent::getEntityReferenceSettings(); + $settings['handler_settings']['include_anonymous'] = FALSE; + return $settings; + } + + /** + * {@inheritdoc} + */ + public function postInstall() { + if (!\Drupal::isConfigSyncing()) { + $group_content_type_id = $this->getContentTypeConfigId(); + + // Add Status field. + FieldConfig::create([ + 'field_storage' => FieldStorageConfig::loadByName('group_content', 'grequest_status'), + 'bundle' => $group_content_type_id, + 'label' => $this->t('Request status'), + 'required' => TRUE, + 'default_value' => self::REQUEST_PENDING, + ])->save(); + + // Add "Updated by" field, to save reference to + // user who approved/denied request. + FieldConfig::create([ + 'field_storage' => FieldStorageConfig::loadByName('group_content', 'grequest_updated_by'), + 'bundle' => $group_content_type_id, + 'label' => $this->t('Approved/Rejected by'), + 'settings' => [ + 'handler' => 'default', + 'target_bundles' => NULL, + ], + ])->save(); + + // Build the 'default' display ID for both the entity form and view mode. + $default_display_id = "group_content.$group_content_type_id.default"; + // Build or retrieve the 'default' view mode. + if (!$view_display = EntityViewDisplay::load($default_display_id)) { + $view_display = EntityViewDisplay::create([ + 'targetEntityType' => 'group_content', + 'bundle' => $group_content_type_id, + 'mode' => 'default', + 'status' => TRUE, + ]); + } + + // Assign display settings for the 'default' view mode. + $view_display + ->setComponent('grequest_status', [ + 'type' => 'number_integer', + ]) + ->setComponent('grequest_updated_by', [ + 'label' => 'above', + 'type' => 'entity_reference_label', + 'settings' => [ + 'link' => 1, + ], + ]) + ->save(); + } + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + $config = parent::defaultConfiguration(); + $config['entity_cardinality'] = 1; + return $config; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + + // Disable the entity cardinality field as the functionality of this module + // relies on a cardinality of 1. We don't just hide it, though, to keep a UI + // that's consistent with other content enabler plugins. + $info = $this->t("This field has been disabled by the plugin to guarantee the functionality that's expected of it."); + $form['entity_cardinality']['#disabled'] = TRUE; + $form['entity_cardinality']['#description'] .= '
' . $info . ''; + + return $form; + } + +} diff --git a/modules/grequest/src/Plugin/GroupMembershipRequestPermissionProvider.php b/modules/grequest/src/Plugin/GroupMembershipRequestPermissionProvider.php new file mode 100644 index 0000000..ff10194 --- /dev/null +++ b/modules/grequest/src/Plugin/GroupMembershipRequestPermissionProvider.php @@ -0,0 +1,56 @@ + 'Request group membership', + 'allowed for' => ['outsider'], + ]; + + return $permissions; + } + +} diff --git a/modules/grequest/src/Plugin/views/field/RequestMembership.php b/modules/grequest/src/Plugin/views/field/RequestMembership.php new file mode 100644 index 0000000..c259831 --- /dev/null +++ b/modules/grequest/src/Plugin/views/field/RequestMembership.php @@ -0,0 +1,70 @@ +currentDisplay = $view->current_display; + } + + /** + * {@inheritdoc} + */ + public function usesGroupBy() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function query() { + // Do nothing -- to override the parent query. + } + + /** + * {@inheritdoc} + * + * @return \Drupal\Component\Render\MarkupInterface|\Drupal\Core\GeneratedUrl|\Drupal\views\Render\ViewsRenderPipelineMarkup|string + * @throws \Drupal\Core\Entity\EntityMalformedException + */ + public function render(ResultRow $values) { + /** @var \Drupal\Core\Entity\EntityInterface $entity */ + $group = $values->_entity; + $build = NULL; + if ($group->getGroupType()->hasContentPlugin('group_membership_request') && empty($group->getMember(\Drupal::currentUser()))) { + $build = $group->toLink($this->t('Request Membership'), 'group-request-membership')->toString(); + } + + return $build; + } + +}