diff --git a/autoload.php b/autoload.php index 1c6fca80..e0b31a7d 100644 --- a/autoload.php +++ b/autoload.php @@ -18,6 +18,7 @@ require_once __DIR__ . '/includes/event/event-repository.php'; require_once __DIR__ . '/includes/event/event-repository-cached.php'; require_once __DIR__ . '/includes/event/event-form-handler.php'; +require_once __DIR__ . '/includes/event/event-capabilities.php'; require_once __DIR__ . '/includes/stats/stats-calculator.php'; require_once __DIR__ . '/includes/stats/stats-importer.php'; require_once __DIR__ . '/includes/stats/stats-listener.php'; diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php new file mode 100644 index 00000000..fbe38953 --- /dev/null +++ b/includes/event/event-capabilities.php @@ -0,0 +1,188 @@ +event_repository = $event_repository; + $this->attendee_repository = $attendee_repository; + $this->stats_calculator = $stats_calculator; + } + + /** + * This function is automatically called whenever user_can() is called for one the capabilities in self::CAPS. + * + * @param string $cap Requested capability. + * @param array $args Arguments that accompany the requested capability check. + * @param WP_User $user User for which we're evaluating the capability. + * @return bool + */ + private function has_cap( string $cap, array $args, WP_User $user ): bool { + switch ( $cap ) { + case self::CREATE: + return $this->has_create( $user ); + case self::VIEW: + case self::EDIT: + case self::DELETE: + if ( ! isset( $args[2] ) || ! is_numeric( $args[2] ) ) { + return false; + } + $event = $this->event_repository->get_event( intval( $args[2] ) ); + if ( ! $event ) { + return false; + } + + if ( self::VIEW === $cap ) { + return $this->has_view( $user, $event ); + } + if ( self::EDIT === $cap ) { + return $this->has_edit( $user, $event ); + } + if ( self::DELETE === $cap ) { + return $this->has_delete( $user, $event ); + } + break; + } + + return false; + } + + /** + * Evaluate whether a user can create events. + * + * @param WP_User $user User for which we're evaluating the capability. + * @return bool + */ + private function has_create( WP_User $user ): bool { + return $this->is_gp_admin( $user ); + } + + /** + * Evaluate whether a user can view a specific event. + * + * @param WP_User $user User for which we're evaluating the capability. + * @param Event $event Event for which we're evaluating the capability. + * @return bool + */ + private function has_view( WP_User $user, Event $event ): bool { + if ( $this->is_gp_admin( $user ) ) { + return true; + } + + return 'publish' === $event->status(); + } + + /** + * Evaluate whether a user can edit a specific event. + * + * @param WP_User $user User for which we're evaluating the capability. + * @param Event $event Event for which we're evaluating the capability. + * @return bool + */ + private function has_edit( WP_User $user, Event $event ): bool { + if ( $event->end()->is_in_the_past() ) { + return false; + } + + if ( $this->stats_calculator->event_has_stats( $event->id() ) ) { + return false; + } + + if ( $event->author_id() === $user->ID ) { + return true; + } + + if ( user_can( $user->ID, 'edit_post', $event->id() ) ) { + return true; + } + + $attendee = $this->attendee_repository->get_attendee( $event->id(), $user->ID ); + if ( ( $attendee instanceof Attendee ) && $attendee->is_host() ) { + return true; + } + + if ( $this->is_gp_admin( $user ) ) { + return true; + } + + return false; + } + + /** + * Evaluate whether a user can delete a specific event. + * + * @param WP_User $user User for which we're evaluating the capability. + * @param Event $event Event for which we're evaluating the capability. + * @return bool + */ + private function has_delete( WP_User $user, Event $event ): bool { + // Must be able to edit in order to delete. + if ( ! $this->has_edit( $user, $event ) ) { + return false; + } + + if ( user_can( $user->ID, 'delete_post', $event->id() ) ) { + return true; + } + + return false; + } + + /** + * Evaluate whether a user is a GlotPress admin. + * + * @param WP_User $user User for which we're evaluating the capability. + * @return bool + */ + private function is_gp_admin( WP_User $user ): bool { + return apply_filters( 'gp_translation_events_can_crud_event', GP::$permission->user_can( $user, 'admin' ) ); + } + + public function register_hooks(): void { + add_action( + 'user_has_cap', + function ( $allcaps, $caps, $args, $user ) { + foreach ( $caps as $cap ) { + if ( ! in_array( $cap, self::CAPS, true ) ) { + continue; + } + if ( $this->has_cap( $cap, $args, $user ) ) { + $allcaps[ $cap ] = true; + } + } + return $allcaps; + }, + 10, + 4, + ); + } +} diff --git a/includes/event/event-form-handler.php b/includes/event/event-form-handler.php index 3592d333..d05733e2 100644 --- a/includes/event/event-form-handler.php +++ b/includes/event/event-form-handler.php @@ -5,61 +5,40 @@ use DateTime; use DateTimeZone; use Exception; -use GP; use WP_Error; -use Wporg\TranslationEvents\Attendee\Attendee; -use Wporg\TranslationEvents\Attendee\Attendee_Repository; use Wporg\TranslationEvents\Stats\Stats_Calculator; class Event_Form_Handler { private Event_Repository_Interface $event_repository; - private Attendee_Repository $attendee_repository; - public function __construct( Event_Repository_Interface $event_repository, Attendee_Repository $attendee_repository ) { - $this->event_repository = $event_repository; - $this->attendee_repository = $attendee_repository; + public function __construct( Event_Repository_Interface $event_repository ) { + $this->event_repository = $event_repository; } public function handle( array $form_data ): void { if ( ! is_user_logged_in() ) { wp_send_json_error( esc_html__( 'The user must be logged in.', 'gp-translation-events' ), 403 ); } - $action = isset( $form_data['form_name'] ) ? sanitize_text_field( wp_unslash( $form_data['form_name'] ) ) : ''; - $response_message = ''; - $is_nonce_valid = false; - $nonce_name = '_event_nonce'; + + $action = isset( $form_data['form_name'] ) ? sanitize_text_field( wp_unslash( $form_data['form_name'] ) ) : ''; if ( ! in_array( $action, array( 'create_event', 'edit_event', 'delete_event' ), true ) ) { wp_send_json_error( esc_html__( 'Invalid form name.', 'gp-translation-events' ), 403 ); } - /** - * Filter the ability to create, edit, or delete an event. - * - * @param bool $can_crud_event Whether the user can create, edit, or delete an event. - */ - $can_crud_event = apply_filters( 'gp_translation_events_can_crud_event', GP::$permission->current_user_can( 'admin' ) ); - if ( 'create_event' === $action && ( ! $can_crud_event ) ) { - wp_send_json_error( esc_html__( 'The user does not have permission to create an event.', 'gp-translation-events' ), 403 ); + + $event_id = isset( $form_data['event_id'] ) ? sanitize_text_field( wp_unslash( $form_data['event_id'] ) ) : 0; + + if ( 'create_event' === $action && ( ! current_user_can( 'create_translation_event' ) ) ) { + wp_send_json_error( esc_html__( 'You do not have permissions to create events.', 'gp-translation-events' ), 403 ); } - if ( 'edit_event' === $action ) { - $event_id = isset( $form_data['event_id'] ) ? sanitize_text_field( wp_unslash( $form_data['event_id'] ) ) : ''; - $event = $this->event_repository->get_event( $event_id ); - $attendee = $this->attendee_repository->get_attendee( $event->id(), get_current_user_id() ); - if ( ! ( $can_crud_event || ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'edit_post', $event_id ) || $event->author_id() === get_current_user_id() ) ) { - wp_send_json_error( esc_html__( 'The user does not have permission to edit or delete the event.', 'gp-translation-events' ), 403 ); - } + if ( 'edit_event' === $action && ( ! current_user_can( 'edit_translation_event', $event_id ) ) ) { + wp_send_json_error( esc_html__( 'You do not have permissions to edit this event.', 'gp-translation-events' ), 403 ); } - if ( 'delete_event' === $action ) { - $event_id = isset( $form_data['event_id'] ) ? sanitize_text_field( wp_unslash( $form_data['event_id'] ) ) : ''; - $event = $this->event_repository->get_event( $event_id ); - $attendee = $this->attendee_repository->get_attendee( $event->id(), get_current_user_id() ); - $stats_calculator = new Stats_Calculator(); - if ( $stats_calculator->event_has_stats( $event->id() ) ) { - wp_send_json_error( esc_html__( 'The event has stats so it cannot be deleted.', 'gp-translation-events' ), 422 ); - } - if ( ! ( $can_crud_event || ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'delete_post', $event_id ) || get_current_user_id() === $event->author_id() ) ) { - wp_send_json_error( esc_html__( 'You do not have permission to delete this event.', 'gp-translation-events' ), 403 ); - } + if ( 'delete_event' === $action && ( ! current_user_can( 'delete_translation_event', $event_id ) ) ) { + wp_send_json_error( esc_html__( 'You do not have permissions to delete this event.', 'gp-translation-events' ), 403 ); } + + $is_nonce_valid = false; + $nonce_name = '_event_nonce'; if ( isset( $form_data[ $nonce_name ] ) ) { $nonce_value = sanitize_text_field( wp_unslash( $form_data[ $nonce_name ] ) ); if ( wp_verify_nonce( $nonce_value, $nonce_name ) ) { @@ -70,6 +49,7 @@ public function handle( array $form_data ): void { wp_send_json_error( esc_html__( 'Nonce verification failed.', 'gp-translation-events' ), 403 ); } + $response_message = ''; if ( 'delete_event' === $action ) { // Delete event. $event_id = intval( sanitize_text_field( wp_unslash( $form_data['event_id'] ) ) ); diff --git a/includes/routes/event/create.php b/includes/routes/event/create.php index 3bd04fa7..540f7e78 100644 --- a/includes/routes/event/create.php +++ b/includes/routes/event/create.php @@ -16,6 +16,11 @@ public function handle(): void { wp_safe_redirect( wp_login_url( home_url( $wp->request ) ) ); exit; } + + if ( ! current_user_can( 'create_translation_event' ) ) { + $this->die_with_error( 'You do not have permission to create events.' ); + } + $event_page_title = 'Create Event'; $event_form_name = 'create_event'; $css_show_url = 'hide-event-url'; diff --git a/includes/routes/event/details.php b/includes/routes/event/details.php index d4415226..6b69bc62 100644 --- a/includes/routes/event/details.php +++ b/includes/routes/event/details.php @@ -35,13 +35,7 @@ public function handle( string $event_slug ): void { $this->die_with_404(); } - /** - * Filter the ability to create, edit, or delete an event. - * - * @param bool $can_crud_event Whether the user can create, edit, or delete an event. - */ - $can_crud_event = apply_filters( 'gp_translation_events_can_crud_event', GP::$permission->current_user_can( 'admin' ) ); - if ( 'publish' !== $event->status() && ! $can_crud_event ) { + if ( ! current_user_can( 'view_translation_event', $event->id() ) ) { $this->die_with_error( esc_html__( 'You are not authorized to view this page.', 'gp-translation-events' ), 403 ); } @@ -68,11 +62,6 @@ public function handle( string $event_slug ): void { $this->die_with_error( esc_html__( 'Failed to calculate event stats', 'gp-translation-events' ) ); } - $is_editable_event = true; - if ( $event_end->is_in_the_past() || $stats_calculator->event_has_stats( $event->id() ) ) { - $is_editable_event = false; - } - $this->tmpl( 'event', get_defined_vars() ); } } diff --git a/includes/routes/event/edit.php b/includes/routes/event/edit.php index b1d0b149..7bc87927 100644 --- a/includes/routes/event/edit.php +++ b/includes/routes/event/edit.php @@ -2,11 +2,8 @@ namespace Wporg\TranslationEvents\Routes\Event; -use Wporg\TranslationEvents\Attendee\Attendee; -use Wporg\TranslationEvents\Attendee\Attendee_Repository; use Wporg\TranslationEvents\Event\Event_Repository_Interface; use Wporg\TranslationEvents\Routes\Route; -use Wporg\TranslationEvents\Stats\Stats_Calculator; use Wporg\TranslationEvents\Translation_Events; /** @@ -14,12 +11,10 @@ */ class Edit_Route extends Route { private Event_Repository_Interface $event_repository; - private Attendee_Repository $attendee_repository; public function __construct() { parent::__construct(); - $this->event_repository = Translation_Events::get_event_repository(); - $this->attendee_repository = Translation_Events::get_attendee_repository(); + $this->event_repository = Translation_Events::get_event_repository(); } public function handle( int $event_id ): void { @@ -28,14 +23,14 @@ public function handle( int $event_id ): void { wp_safe_redirect( wp_login_url( home_url( $wp->request ) ) ); exit; } - $event = $this->event_repository->get_event( $event_id ); - $attendee = $this->attendee_repository->get_attendee( $event->id(), get_current_user_id() ); - if ( ! $event || ! ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'edit_post', $event->id() ) || $event->author_id() === get_current_user_id() ) ) { - $this->die_with_error( esc_html__( 'Event does not exist, or you do not have permission to edit it.', 'gp-translation-events' ), 403 ); + $event = $this->event_repository->get_event( $event_id ); + if ( ! $event ) { + $this->die_with_404(); } - if ( 'trash' === $event->status() ) { - $this->die_with_error( esc_html__( 'You cannot edit a trashed event', 'gp-translation-events' ), 403 ); + + if ( ! current_user_can( 'edit_translation_event', $event->id() ) ) { + $this->die_with_error( esc_html__( 'You do not have permission to edit this event.', 'gp-translation-events' ), 403 ); } include ABSPATH . 'wp-admin/includes/post.php'; @@ -53,23 +48,7 @@ public function handle( int $event_id ): void { $event_end = $event->end(); $create_delete_button = false; $visibility_delete_button = 'inline-flex'; - - if ( $event->end()->is_in_the_past() ) { - $this->die_with_error( esc_html__( 'You cannot edit a past event.', 'gp-translation-events' ), 403 ); - } - - $stats_calculator = new Stats_Calculator(); - - if ( $stats_calculator->event_has_stats( $event->id() ) ) { - $this->die_with_error( esc_html__( 'You cannot edit an event with translations.', 'gp-translation-events' ), 403 ); - } - - if ( ! $stats_calculator->event_has_stats( $event->id() ) ) { - $current_user = wp_get_current_user(); - if ( ( $current_user->ID === $event->author_id() || ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) ) && ! $event->end()->is_in_the_past() ) { - $create_delete_button = true; - } - } + $create_delete_button = current_user_can( 'delete_translation_event', $event->id() ); $this->tmpl( 'events-form', get_defined_vars() ); } diff --git a/includes/routes/user/host-event.php b/includes/routes/user/host-event.php index 0977275c..0d670d00 100644 --- a/includes/routes/user/host-event.php +++ b/includes/routes/user/host-event.php @@ -36,12 +36,11 @@ public function __construct() { public function handle( int $event_id, int $user_id ): void { $current_user = wp_get_current_user(); if ( ! $current_user->exists() ) { - $this->die_with_error( esc_html__( "Only logged-in users can manage event's hosts.", 'gp-translation-events' ), 403 ); + $this->die_with_error( esc_html__( "Only logged-in users can manage the event's hosts.", 'gp-translation-events' ), 403 ); } - $current_user_attendee = $this->attendee_repository->get_attendee( $event_id, $current_user->ID ); - if ( ! current_user_can( 'manage_options' ) && ! $current_user_attendee->is_host() ) { - $this->die_with_error( esc_html__( "This user does not have permissions to manage event's hosts.", 'gp-translation-events' ), 403 ); + if ( ! current_user_can( 'edit_translation_event', $event_id ) ) { + $this->die_with_error( esc_html__( "You do not have permissions to manage the event's hosts.", 'gp-translation-events' ), 403 ); } $event = $this->event_repository->get_event( $event_id ); diff --git a/phpcs.xml b/phpcs.xml index 83bfe44b..46f575db 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -12,6 +12,9 @@ ./wporg-gp-translation-events.php + + + diff --git a/templates/event.php b/templates/event.php index b51f9762..05c290e4 100644 --- a/templates/event.php +++ b/templates/event.php @@ -59,20 +59,18 @@ end()->is_in_the_past() ) : - if ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) || $user->ID === $event->author_id() ) : - $_attendee = $attendee_repo->get_attendee( $event_id, $contributor->ID ); - if ( $_attendee instanceof Attendee ) : - echo '
ID" ) ) . '">'; - if ( $_attendee->is_host() ) : - echo ''; - else : - echo ''; - endif; - echo '
'; + if ( current_user_can( 'edit_translation_event', $event->id() ) ) : + $_attendee = $attendee_repo->get_attendee( $event_id, $contributor->ID ); + if ( $_attendee instanceof Attendee ) : + echo '
ID" ) ) . '">'; + if ( $_attendee->is_host() ) : + echo ''; else : - echo '' . esc_html__( 'Not attending', 'gp-translation-events' ) . ''; + echo ''; endif; + echo '
'; + else : + echo '' . esc_html__( 'Not attending', 'gp-translation-events' ) . ''; endif; endif; ?> @@ -81,7 +79,7 @@ - end()->is_in_the_past() || ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) || $user->ID === $event->author_id() ) ) ) : ?> + id() ) ) : ?>

"> end()->is_in_the_past() ) : - if ( ( $attendee instanceof Attendee && $attendee->is_host() ) || current_user_can( 'manage_options' ) || $user->ID === $event->author_id() ) : - $_attendee = $attendee_repo->get_attendee( $event_id, $_user->ID ); - if ( $_attendee instanceof Attendee ) : - echo '
ID" ) ) . '">'; - if ( $_attendee->is_host() ) : - echo ''; - else : - echo ''; - endif; - echo '
'; - endif; + $_attendee = $attendee_repo->get_attendee( $event_id, $_user->ID ); + if ( $_attendee instanceof Attendee ) : + echo '
ID" ) ) . '">'; + if ( $_attendee->is_host() ) : + echo ''; + else : + echo ''; endif; + echo '
'; endif; ?> diff --git a/templates/events-header.php b/templates/events-header.php index 479f513e..ebc547e7 100644 --- a/templates/events-header.php +++ b/templates/events-header.php @@ -2,14 +2,12 @@ namespace Wporg\TranslationEvents; -use GP; use Wporg\TranslationEvents\Attendee\Attendee; use Wporg\TranslationEvents\Event\Event; /** @var Attendee $attendee */ /** @var Event $event */ /** @var string $event_page_title */ -/** @var bool $is_editable_event */ ?>
@@ -23,13 +21,7 @@
  • My Events
  • current_user_can( 'admin' ) ); - if ( $can_crud_event ) : + if ( current_user_can( 'create_translation_event' ) ) : ?>
  • Create Event
  • @@ -61,8 +53,7 @@ - is_host() ) || current_user_can( 'edit_post', $event->id() ) ) && $is_editable_event; ?> - + id() ) ) : ?>

    diff --git a/tests/event/event-capabilities.php b/tests/event/event-capabilities.php new file mode 100644 index 00000000..176323fe --- /dev/null +++ b/tests/event/event-capabilities.php @@ -0,0 +1,160 @@ +event_factory = new Event_Factory(); + $this->stats_factory = new Stats_Factory(); + $this->attendee_repository = new Attendee_Repository(); + $this->event_repository = new Event_Repository( $this->attendee_repository ); + } + + public function test_cannot_create_if_no_crud_permission() { + $this->set_normal_user_as_current(); + + add_filter( 'gp_translation_events_can_crud_event', '__return_false' ); + + $this->assertFalse( current_user_can( 'create_translation_event' ) ); + } + + public function test_can_create_if_crud_permission() { + $this->set_normal_user_as_current(); + get_current_user_id(); + + add_filter( 'gp_translation_events_can_crud_event', '__return_true' ); + + $this->assertTrue( current_user_can( 'create_translation_event' ) ); + } + + public function test_cannot_view_non_published_events() { + $this->set_normal_user_as_current(); + + $event_id = $this->event_factory->create_active(); + $event = $this->event_repository->get_event( $event_id ); + $event->set_status( 'draft' ); + $this->event_repository->update_event( $event ); + + $this->assertFalse( current_user_can( 'view_translation_event', $event_id ) ); + } + + public function test_gp_admin_can_view_non_published_events() { + $this->set_normal_user_as_current(); + + $event_id = $this->event_factory->create_active(); + $event = $this->event_repository->get_event( $event_id ); + $event->set_status( 'draft' ); + $this->event_repository->update_event( $event ); + + add_filter( 'gp_translation_events_can_crud_event', '__return_true' ); + + $this->assertTrue( current_user_can( 'view_translation_event', $event_id ) ); + } + + public function test_event_id_as_string() { + $this->set_normal_user_as_current(); + + $event_id = $this->event_factory->create_active(); + + $this->assertTrue( current_user_can( 'edit_translation_event', (string) $event_id ) ); + } + + public function test_author_can_edit() { + $this->set_normal_user_as_current(); + + $event_id = $this->event_factory->create_active(); + + $this->assertTrue( current_user_can( 'edit_translation_event', $event_id ) ); + } + + public function test_non_author_cannot_edit() { + $this->set_normal_user_as_current(); + $non_author_user_id = get_current_user_id(); + $this->set_normal_user_as_current(); // This user is the author. + + $event_id = $this->event_factory->create_active(); + + $this->assertFalse( user_can( $non_author_user_id, 'edit_translation_event', $event_id ) ); + } + + public function test_host_can_edit() { + $this->set_normal_user_as_current(); + $non_author_user_id = get_current_user_id(); + $this->set_normal_user_as_current(); // This user is the author. + + $event_id = $this->event_factory->create_active(); + + $attendee = new Attendee( $event_id, $non_author_user_id ); + $attendee->mark_as_host(); + $this->attendee_repository->insert_attendee( $attendee ); + + $this->assertTrue( user_can( $non_author_user_id, 'edit_translation_event', $event_id ) ); + } + + public function test_gp_admin_can_edit() { + $this->set_normal_user_as_current(); + $non_author_user_id = get_current_user_id(); + $this->set_normal_user_as_current(); // This user is the author. + + $event_id = $this->event_factory->create_active(); + add_filter( 'gp_translation_events_can_crud_event', '__return_true' ); + + $this->assertTrue( user_can( $non_author_user_id, 'edit_translation_event', $event_id ) ); + } + + public function test_cannot_edit_past_event() { + $this->set_normal_user_as_current(); + + $event_id = $this->event_factory->create_inactive_past(); + + $this->assertFalse( current_user_can( 'edit_translation_event', $event_id ) ); + } + + public function test_cannot_edit_event_with_stats() { + $this->set_normal_user_as_current(); + $author_user_id = get_current_user_id(); + + $event_id = $this->event_factory->create_active(); + $this->stats_factory->create( $event_id, $author_user_id, 1, 'create' ); + + $this->assertFalse( current_user_can( 'edit_translation_event', $event_id ) ); + } + + public function test_cannot_delete_if_cannot_edit() { + $this->set_normal_user_as_current(); + $non_author_user_id = get_current_user_id(); + $this->set_normal_user_as_current(); // This user is the author. + + $event_id = $this->event_factory->create_active(); + $this->assertFalse( user_can( $non_author_user_id, 'delete_translation_event', $event_id ) ); + } + + public function test_cannot_delete_without_delete_post_capability() { + $this->set_normal_user_as_current(); + + $event_id = $this->event_factory->create_active(); + + $this->assertFalse( current_user_can( 'delete_translation_event', $event_id ) ); + } + + public function test_can_delete_with_delete_post_capability() { + $this->set_admin_user_as_current(); + + $event_id = $this->event_factory->create_active(); + + $this->assertFalse( current_user_can( 'delete_translation_event', $event_id ) ); + } +} diff --git a/wporg-gp-translation-events.php b/wporg-gp-translation-events.php index 398071cc..bbed37b8 100644 --- a/wporg-gp-translation-events.php +++ b/wporg-gp-translation-events.php @@ -26,14 +26,18 @@ use WP_Query; use Wporg\TranslationEvents\Attendee\Attendee; use Wporg\TranslationEvents\Attendee\Attendee_Repository; +use Wporg\TranslationEvents\Event\Event_Capabilities; use Wporg\TranslationEvents\Event\Event_Form_Handler; use Wporg\TranslationEvents\Event\Event_Repository_Cached; use Wporg\TranslationEvents\Event\Event_Repository_Interface; +use Wporg\TranslationEvents\Stats\Stats_Calculator; use Wporg\TranslationEvents\Stats\Stats_Listener; class Translation_Events { public const CPT = 'translation_event'; + private Event_Capabilities $event_capabilities; + public static function get_instance(): Translation_Events { static $instance = null; if ( null === $instance ) { @@ -75,6 +79,13 @@ public function __construct() { if ( is_admin() ) { Upgrade::upgrade_if_needed(); } + + $this->event_capabilities = new Event_Capabilities( + self::get_event_repository(), + self::get_attendee_repository(), + new Stats_Calculator() + ); + $this->event_capabilities->register_hooks(); } public function gp_init() { @@ -175,7 +186,7 @@ public function save_event_meta_boxes( int $post_id ) { * Handle the event form submission for the creation, editing, and deletion of events. This function is called via AJAX. */ public function submit_event_ajax() { - $form_handler = new Event_Form_Handler( self::get_event_repository(), self::get_attendee_repository() ); + $form_handler = new Event_Form_Handler( self::get_event_repository() ); // Nonce verification is done by the form handler. // phpcs:ignore WordPress.Security.NonceVerification.Missing $form_handler->handle( $_POST );