From 1b1ff99492ca2df44e7ab3a712e8611437f4759d Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Wed, 17 Apr 2024 12:00:58 +0100 Subject: [PATCH 01/26] Add Event_Capabilities class --- autoload.php | 1 + includes/event/event-capabilities.php | 46 +++++++++++++++++++++++++++ tests/event/event-capabilities.php | 21 ++++++++++++ wporg-gp-translation-events.php | 6 ++++ 4 files changed, 74 insertions(+) create mode 100644 includes/event/event-capabilities.php create mode 100644 tests/event/event-capabilities.php 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..27b455ef --- /dev/null +++ b/includes/event/event-capabilities.php @@ -0,0 +1,46 @@ +has_create( $user ); + } + + return false; + } + + private function has_create( WP_User $user ): bool { + // TODO. + return true; + } + + 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/tests/event/event-capabilities.php b/tests/event/event-capabilities.php new file mode 100644 index 00000000..3896ced3 --- /dev/null +++ b/tests/event/event-capabilities.php @@ -0,0 +1,21 @@ +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 ); + $this->capilities = new Event_Capabilities(); + } +} diff --git a/wporg-gp-translation-events.php b/wporg-gp-translation-events.php index 398071cc..f6bb4b07 100644 --- a/wporg-gp-translation-events.php +++ b/wporg-gp-translation-events.php @@ -26,6 +26,7 @@ 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; @@ -34,6 +35,8 @@ 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 +78,9 @@ public function __construct() { if ( is_admin() ) { Upgrade::upgrade_if_needed(); } + + $this->event_capabilities = new Event_Capabilities(); + $this->event_capabilities->register_hooks(); } public function gp_init() { From 813c5e4d39137a750f6f41febae5f72ca4c95cb4 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Wed, 17 Apr 2024 15:45:27 +0100 Subject: [PATCH 02/26] Implement create capability --- includes/event/event-capabilities.php | 8 ++++++-- includes/event/event-form-handler.php | 2 +- includes/routes/event/create.php | 5 +++++ phpcs.xml | 3 +++ tests/event/event-capabilities.php | 19 +++++++++++++++++-- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php index 27b455ef..4a6928ce 100644 --- a/includes/event/event-capabilities.php +++ b/includes/event/event-capabilities.php @@ -2,6 +2,7 @@ namespace Wporg\TranslationEvents\Event; +use GP; use WP_User; class Event_Capabilities { @@ -21,8 +22,11 @@ private function has_cap( string $cap, array $args, WP_User $user ): bool { } private function has_create( WP_User $user ): bool { - // TODO. - return true; + return $this->has_gp_crud( $user ); + } + + private function has_gp_crud( WP_User $user ): bool { + return apply_filters( 'gp_translation_events_can_crud_event', GP::$permission->user_can( $user, 'admin' ) ); } public function register_hooks(): void { diff --git a/includes/event/event-form-handler.php b/includes/event/event-form-handler.php index 3592d333..6d899c0a 100644 --- a/includes/event/event-form-handler.php +++ b/includes/event/event-form-handler.php @@ -37,7 +37,7 @@ public function handle( array $form_data ): void { * @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 ) ) { + if ( 'create_event' === $action && ( ! current_user_can( 'create_translation_event' ) ) ) { wp_send_json_error( esc_html__( 'The user does not have permission to create an event.', 'gp-translation-events' ), 403 ); } if ( 'edit_event' === $action ) { 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/phpcs.xml b/phpcs.xml index 83bfe44b..7af8b427 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -12,6 +12,9 @@ ./wporg-gp-translation-events.php + + + diff --git a/tests/event/event-capabilities.php b/tests/event/event-capabilities.php index 3896ced3..1cb90f9b 100644 --- a/tests/event/event-capabilities.php +++ b/tests/event/event-capabilities.php @@ -4,7 +4,6 @@ use GP_UnitTestCase; use Wporg\TranslationEvents\Attendee\Attendee_Repository; -use Wporg\TranslationEvents\Event\Event_Capabilities; use Wporg\TranslationEvents\Event\Event_Repository; use Wporg\TranslationEvents\Tests\Event_Factory; use Wporg\TranslationEvents\Tests\Stats_Factory; @@ -16,6 +15,22 @@ public function setUp(): void { $this->stats_factory = new Stats_Factory(); $this->attendee_repository = new Attendee_Repository(); $this->event_repository = new Event_Repository( $this->attendee_repository ); - $this->capilities = new Event_Capabilities(); + } + + 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' ) ); } } From 02697e9b8ea3d26c8ec1aa1fb9b1199134e4f4cd Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 15:10:22 +0100 Subject: [PATCH 03/26] Add docblocks --- includes/event/event-capabilities.php | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php index 4a6928ce..dc5c9870 100644 --- a/includes/event/event-capabilities.php +++ b/includes/event/event-capabilities.php @@ -8,10 +8,21 @@ class Event_Capabilities { private const CREATE = 'create_translation_event'; + /** + * All the capabilities that concern an Event. + */ private const CAPS = array( self::CREATE, ); + /** + * 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: @@ -21,11 +32,23 @@ private function has_cap( string $cap, array $args, WP_User $user ): bool { 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->has_gp_crud( $user ); + return $this->is_gp_admin( $user ); } - private function has_gp_crud( WP_User $user ): bool { + /** + * 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' ) ); } From 20fd493ef0e17ce28e71d6d9cfaddc196f3bb685 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 15:15:24 +0100 Subject: [PATCH 04/26] Pass event repository to Event_Capabilities --- includes/event/event-capabilities.php | 6 ++++++ wporg-gp-translation-events.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php index dc5c9870..fc51b688 100644 --- a/includes/event/event-capabilities.php +++ b/includes/event/event-capabilities.php @@ -15,6 +15,12 @@ class Event_Capabilities { self::CREATE, ); + private Event_Repository_Interface $event_repository; + + public function __construct( Event_Repository_Interface $event_repository ) { + $this->event_repository = $event_repository; + } + /** * This function is automatically called whenever user_can() is called for one the capabilities in self::CAPS. * diff --git a/wporg-gp-translation-events.php b/wporg-gp-translation-events.php index f6bb4b07..5a82dfaf 100644 --- a/wporg-gp-translation-events.php +++ b/wporg-gp-translation-events.php @@ -79,7 +79,7 @@ public function __construct() { Upgrade::upgrade_if_needed(); } - $this->event_capabilities = new Event_Capabilities(); + $this->event_capabilities = new Event_Capabilities( self::get_event_repository() ); $this->event_capabilities->register_hooks(); } From f050551dfa547eb0d6980ae11c99cf898a9f8833 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 15:22:13 +0100 Subject: [PATCH 05/26] Add edit_translation_event capability --- includes/event/event-capabilities.php | 23 +++++++++++++++++++++++ phpcs.xml | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php index fc51b688..917d2593 100644 --- a/includes/event/event-capabilities.php +++ b/includes/event/event-capabilities.php @@ -7,12 +7,14 @@ class Event_Capabilities { private const CREATE = 'create_translation_event'; + private const EDIT = 'edit_translation_event'; /** * All the capabilities that concern an Event. */ private const CAPS = array( self::CREATE, + self::EDIT, ); private Event_Repository_Interface $event_repository; @@ -33,6 +35,15 @@ private function has_cap( string $cap, array $args, WP_User $user ): bool { switch ( $cap ) { case self::CREATE: return $this->has_create( $user ); + case self::EDIT: + if ( ! isset( $args[2] ) || ! is_int( $args[2] ) ) { + return false; + } + $event = $this->event_repository->get_event( $args[2] ); + if ( ! $event ) { + return false; + } + return $this->has_edit( $user, $event ); } return false; @@ -48,6 +59,18 @@ private function has_create( WP_User $user ): bool { return $this->is_gp_admin( $user ); } + /** + * 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 { + // TODO. + return false; + } + /** * Evaluate whether a user is a GlotPress admin. * diff --git a/phpcs.xml b/phpcs.xml index 7af8b427..397d4995 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -13,7 +13,7 @@ - + From 14ca1bc522ef3935f6bc7610b9876141fec93f1a Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 15:29:43 +0100 Subject: [PATCH 06/26] Add tests for edit capability --- tests/event/event-capabilities.php | 52 ++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/event/event-capabilities.php b/tests/event/event-capabilities.php index 1cb90f9b..4f60432c 100644 --- a/tests/event/event-capabilities.php +++ b/tests/event/event-capabilities.php @@ -3,6 +3,7 @@ namespace Wporg\Tests\Event; use GP_UnitTestCase; +use Wporg\TranslationEvents\Attendee\Attendee; use Wporg\TranslationEvents\Attendee\Attendee_Repository; use Wporg\TranslationEvents\Event\Event_Repository; use Wporg\TranslationEvents\Tests\Event_Factory; @@ -33,4 +34,55 @@ public function test_can_create_if_crud_permission() { $this->assertTrue( current_user_can( 'create_translation_event' ) ); } + + 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_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 ) ); + } } From 32287aba3a1373807e5e467539c21eac6a8becca Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 15:33:18 +0100 Subject: [PATCH 07/26] Pass dependencies to Event_Capabilities --- includes/event/event-capabilities.php | 14 ++++++++++++-- wporg-gp-translation-events.php | 7 ++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php index 917d2593..904b698b 100644 --- a/includes/event/event-capabilities.php +++ b/includes/event/event-capabilities.php @@ -4,6 +4,8 @@ use GP; use WP_User; +use Wporg\TranslationEvents\Attendee\Attendee_Repository; +use Wporg\TranslationEvents\Stats\Stats_Calculator; class Event_Capabilities { private const CREATE = 'create_translation_event'; @@ -18,9 +20,17 @@ class Event_Capabilities { ); private Event_Repository_Interface $event_repository; + private Attendee_Repository $attendee_repository; + private Stats_Calculator $stats_calculator; - public function __construct( Event_Repository_Interface $event_repository ) { - $this->event_repository = $event_repository; + public function __construct( + Event_Repository_Interface $event_repository, + Attendee_Repository $attendee_repository, + Stats_Calculator $stats_calculator + ) { + $this->event_repository = $event_repository; + $this->attendee_repository = $attendee_repository; + $this->stats_calculator = $stats_calculator; } /** diff --git a/wporg-gp-translation-events.php b/wporg-gp-translation-events.php index 5a82dfaf..9a10a338 100644 --- a/wporg-gp-translation-events.php +++ b/wporg-gp-translation-events.php @@ -30,6 +30,7 @@ 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 { @@ -79,7 +80,11 @@ public function __construct() { Upgrade::upgrade_if_needed(); } - $this->event_capabilities = new Event_Capabilities( self::get_event_repository() ); + $this->event_capabilities = new Event_Capabilities( + self::get_event_repository(), + self::get_attendee_repository(), + new Stats_Calculator() + ); $this->event_capabilities->register_hooks(); } From dbc66e4e164494d092ea142c27f59599f78b1633 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 15:42:16 +0100 Subject: [PATCH 08/26] Implement edit event capability check --- includes/event/event-capabilities.php | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php index 904b698b..0289c3b2 100644 --- a/includes/event/event-capabilities.php +++ b/includes/event/event-capabilities.php @@ -4,6 +4,7 @@ use GP; use WP_User; +use Wporg\TranslationEvents\Attendee\Attendee; use Wporg\TranslationEvents\Attendee\Attendee_Repository; use Wporg\TranslationEvents\Stats\Stats_Calculator; @@ -77,7 +78,27 @@ private function has_create( WP_User $user ): bool { * @return bool */ private function has_edit( WP_User $user, Event $event ): bool { - // TODO. + 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; + } + return false; } From 196745c114d3619676f8e1e81e523beb3bfbd969 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 15:55:51 +0100 Subject: [PATCH 09/26] Implement delete capability --- includes/event/event-capabilities.php | 31 +++++++++++++++++++++++++++ phpcs.xml | 2 +- tests/event/event-capabilities.php | 25 +++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php index 0289c3b2..bdb50e81 100644 --- a/includes/event/event-capabilities.php +++ b/includes/event/event-capabilities.php @@ -11,6 +11,7 @@ class Event_Capabilities { private const CREATE = 'create_translation_event'; private const EDIT = 'edit_translation_event'; + private const DELETE = 'delete_translation_event'; /** * All the capabilities that concern an Event. @@ -18,6 +19,7 @@ class Event_Capabilities { private const CAPS = array( self::CREATE, self::EDIT, + self::DELETE, ); private Event_Repository_Interface $event_repository; @@ -55,6 +57,15 @@ private function has_cap( string $cap, array $args, WP_User $user ): bool { return false; } return $this->has_edit( $user, $event ); + case self::DELETE: + if ( ! isset( $args[2] ) || ! is_int( $args[2] ) ) { + return false; + } + $event = $this->event_repository->get_event( $args[2] ); + if ( ! $event ) { + return false; + } + return $this->has_delete( $user, $event ); } return false; @@ -102,6 +113,26 @@ private function has_edit( WP_User $user, Event $event ): bool { 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, 'manage_options' ) ) { + return true; + } + + return false; + } + /** * Evaluate whether a user is a GlotPress admin. * diff --git a/phpcs.xml b/phpcs.xml index 397d4995..b76a7278 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -13,7 +13,7 @@ - + diff --git a/tests/event/event-capabilities.php b/tests/event/event-capabilities.php index 4f60432c..aac67a24 100644 --- a/tests/event/event-capabilities.php +++ b/tests/event/event-capabilities.php @@ -85,4 +85,29 @@ public function test_cannot_edit_event_with_stats() { $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_manage_options_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_manage_options_capability() { + $this->set_admin_user_as_current(); + + $event_id = $this->event_factory->create_active(); + + $this->assertFalse( current_user_can( 'delete_translation_event', $event_id ) ); + } } From ccc278f37bc966872092b01914e5159f3b366ad8 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 15:57:52 +0100 Subject: [PATCH 10/26] Return 404 if event is not found --- includes/routes/event/edit.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/includes/routes/event/edit.php b/includes/routes/event/edit.php index b1d0b149..05e42545 100644 --- a/includes/routes/event/edit.php +++ b/includes/routes/event/edit.php @@ -28,10 +28,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() ) ) { + $event = $this->event_repository->get_event( $event_id ); + if ( ! $event ) { + $this->die_with_404(); + } + + $attendee = $this->attendee_repository->get_attendee( $event->id(), get_current_user_id() ); + if ( ! ( ( $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 ); } if ( 'trash' === $event->status() ) { From 5f652d8ac18b0d7073128b27878b2291c66dc5da Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 16:03:01 +0100 Subject: [PATCH 11/26] Use capabilities to check if user can view the edit page --- includes/routes/event/edit.php | 33 ++++----------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/includes/routes/event/edit.php b/includes/routes/event/edit.php index 05e42545..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 { @@ -34,12 +29,8 @@ public function handle( int $event_id ): void { $this->die_with_404(); } - $attendee = $this->attendee_repository->get_attendee( $event->id(), get_current_user_id() ); - if ( ! ( ( $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 ); - } - 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'; @@ -57,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() ); } From a785a4b2b594909a126278798e88d170f3521f52 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 16:07:02 +0100 Subject: [PATCH 12/26] GlotPress admins can always edit --- includes/event/event-capabilities.php | 4 ++++ tests/event/event-capabilities.php | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php index bdb50e81..d39e6ad9 100644 --- a/includes/event/event-capabilities.php +++ b/includes/event/event-capabilities.php @@ -110,6 +110,10 @@ private function has_edit( WP_User $user, Event $event ): bool { return true; } + if ( $this->is_gp_admin( $user ) ) { + return true; + } + return false; } diff --git a/tests/event/event-capabilities.php b/tests/event/event-capabilities.php index aac67a24..7a484acf 100644 --- a/tests/event/event-capabilities.php +++ b/tests/event/event-capabilities.php @@ -67,6 +67,16 @@ public function test_host_can_edit() { $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(); From d990113b3fd5fec9c73d893d45392c275a27815e Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 16:11:25 +0100 Subject: [PATCH 13/26] Check for delete_post instead of manage_options We were checking for manage_options to know whether the user can view the edit page, and for delete_post when handling form submission. We will now standardise on checking for delete_post in all places. --- includes/event/event-capabilities.php | 2 +- tests/event/event-capabilities.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php index d39e6ad9..766f42ae 100644 --- a/includes/event/event-capabilities.php +++ b/includes/event/event-capabilities.php @@ -130,7 +130,7 @@ private function has_delete( WP_User $user, Event $event ): bool { return false; } - if ( user_can( $user->ID, 'manage_options' ) ) { + if ( user_can( $user->ID, 'delete_post', $event->id() ) ) { return true; } diff --git a/tests/event/event-capabilities.php b/tests/event/event-capabilities.php index 7a484acf..b8c225de 100644 --- a/tests/event/event-capabilities.php +++ b/tests/event/event-capabilities.php @@ -105,7 +105,7 @@ public function test_cannot_delete_if_cannot_edit() { $this->assertFalse( user_can( $non_author_user_id, 'delete_translation_event', $event_id ) ); } - public function test_cannot_delete_without_manage_options_capability() { + public function test_cannot_delete_without_delete_post_capability() { $this->set_normal_user_as_current(); $event_id = $this->event_factory->create_active(); @@ -113,7 +113,7 @@ public function test_cannot_delete_without_manage_options_capability() { $this->assertFalse( current_user_can( 'delete_translation_event', $event_id ) ); } - public function test_can_delete_with_manage_options_capability() { + public function test_can_delete_with_delete_post_capability() { $this->set_admin_user_as_current(); $event_id = $this->event_factory->create_active(); From 3260aead82151a5ba2539a9afa8c17ee7cbb40b1 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 16:22:46 +0100 Subject: [PATCH 14/26] Use current_user_can() to check for permissions --- includes/event/event-form-handler.php | 41 ++++++--------------------- wporg-gp-translation-events.php | 2 +- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/includes/event/event-form-handler.php b/includes/event/event-form-handler.php index 6d899c0a..ae503c82 100644 --- a/includes/event/event-form-handler.php +++ b/includes/event/event-form-handler.php @@ -5,19 +5,14 @@ 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 { @@ -31,35 +26,17 @@ public function handle( array $form_data ): void { 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 && ( ! current_user_can( 'create_translation_event' ) ) ) { - wp_send_json_error( esc_html__( 'The user does not have permission to create an event.', 'gp-translation-events' ), 403 ); + 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' ) ) ) { + 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' ) ) ) { + wp_send_json_error( esc_html__( 'You do not have permissions to delete this event.', 'gp-translation-events' ), 403 ); } + 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 ) ) { diff --git a/wporg-gp-translation-events.php b/wporg-gp-translation-events.php index 9a10a338..bbed37b8 100644 --- a/wporg-gp-translation-events.php +++ b/wporg-gp-translation-events.php @@ -186,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 ); From 0a551eaed7c14eabd92e0985241c8577330facf7 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 16:24:03 +0100 Subject: [PATCH 15/26] Move variables to where they are first needed --- includes/event/event-form-handler.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/includes/event/event-form-handler.php b/includes/event/event-form-handler.php index ae503c82..b5856147 100644 --- a/includes/event/event-form-handler.php +++ b/includes/event/event-form-handler.php @@ -19,10 +19,8 @@ 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 ); } @@ -37,6 +35,8 @@ public function handle( array $form_data ): void { 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 ) ) { @@ -47,6 +47,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'] ) ) ); From 5c1583fe4988d09303219246e32a7550f8b377b7 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 16:51:42 +0100 Subject: [PATCH 16/26] Implement view capability --- includes/event/event-capabilities.php | 26 ++++++++++++++++++++++++++ phpcs.xml | 2 +- tests/event/event-capabilities.php | 24 ++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php index 766f42ae..aa6236c8 100644 --- a/includes/event/event-capabilities.php +++ b/includes/event/event-capabilities.php @@ -10,6 +10,7 @@ class Event_Capabilities { private const CREATE = 'create_translation_event'; + private const VIEW = 'view_translation_event'; private const EDIT = 'edit_translation_event'; private const DELETE = 'delete_translation_event'; @@ -18,6 +19,7 @@ class Event_Capabilities { */ private const CAPS = array( self::CREATE, + self::VIEW, self::EDIT, self::DELETE, ); @@ -48,6 +50,15 @@ 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: + if ( ! isset( $args[2] ) || ! is_int( $args[2] ) ) { + return false; + } + $event = $this->event_repository->get_event( $args[2] ); + if ( ! $event ) { + return false; + } + return $this->has_view( $user, $event ); case self::EDIT: if ( ! isset( $args[2] ) || ! is_int( $args[2] ) ) { return false; @@ -81,6 +92,21 @@ 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. * diff --git a/phpcs.xml b/phpcs.xml index b76a7278..46f575db 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -13,7 +13,7 @@ - + diff --git a/tests/event/event-capabilities.php b/tests/event/event-capabilities.php index b8c225de..48b8f6e4 100644 --- a/tests/event/event-capabilities.php +++ b/tests/event/event-capabilities.php @@ -35,6 +35,30 @@ public function test_can_create_if_crud_permission() { $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_author_can_edit() { $this->set_normal_user_as_current(); From 0d4239942301d1401b452c3444e8a1dcef33bb39 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 16:56:54 +0100 Subject: [PATCH 17/26] Use current_user_can() to check for permissions --- includes/routes/event/details.php | 8 +------- templates/events-header.php | 9 +-------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/includes/routes/event/details.php b/includes/routes/event/details.php index d4415226..23bbc590 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 ); } diff --git a/templates/events-header.php b/templates/events-header.php index 479f513e..5a2dac70 100644 --- a/templates/events-header.php +++ b/templates/events-header.php @@ -2,7 +2,6 @@ namespace Wporg\TranslationEvents; -use GP; use Wporg\TranslationEvents\Attendee\Attendee; use Wporg\TranslationEvents\Event\Event; @@ -23,13 +22,7 @@
  • My Events
  • current_user_can( 'admin' ) ); - if ( $can_crud_event ) : + if ( current_user_can( 'create_translation_event' ) ) : ?>
  • Create Event
  • From fab7ad1b0fb966c5aa01a131c9879081efb43f35 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 16:58:27 +0100 Subject: [PATCH 18/26] Use edit capability to show edit button --- includes/routes/event/details.php | 5 ----- templates/events-header.php | 4 +--- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/includes/routes/event/details.php b/includes/routes/event/details.php index 23bbc590..6b69bc62 100644 --- a/includes/routes/event/details.php +++ b/includes/routes/event/details.php @@ -62,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/templates/events-header.php b/templates/events-header.php index 5a2dac70..ebc547e7 100644 --- a/templates/events-header.php +++ b/templates/events-header.php @@ -8,7 +8,6 @@ /** @var Attendee $attendee */ /** @var Event $event */ /** @var string $event_page_title */ -/** @var bool $is_editable_event */ ?>
    @@ -54,8 +53,7 @@ - is_host() ) || current_user_can( 'edit_post', $event->id() ) ) && $is_editable_event; ?> - + id() ) ) : ?>

    From 5ce4d4b97b903227b6644003176557b9f516148d Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 17:01:53 +0100 Subject: [PATCH 19/26] Check for edit_translation_event when managing hosts --- includes/routes/user/host-event.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/includes/routes/user/host-event.php b/includes/routes/user/host-event.php index 0977275c..253de5bc 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' ) ) { + $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 ); From a867505f6511b7cabf7ac90e1136cb5c1fe17a01 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 17:12:12 +0100 Subject: [PATCH 20/26] Use edit capability to check for permissions --- templates/event.php | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/templates/event.php b/templates/event.php index b51f9762..ad2e8a89 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' ) ) : + $_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() ) ) ) : ?> +

    Date: Thu, 18 Apr 2024 17:13:13 +0100 Subject: [PATCH 21/26] Remove unneeded checks We already checke it above, and the start of the attendees section. --- templates/event.php | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/templates/event.php b/templates/event.php index ad2e8a89..c487022f 100644 --- a/templates/event.php +++ b/templates/event.php @@ -96,19 +96,15 @@ 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; ?> From 5a63d8c4c8501ed1d84ebf35e797d707f69a6c4c Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 17:23:21 +0100 Subject: [PATCH 22/26] Simplify logic --- includes/event/event-capabilities.php | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php index aa6236c8..be4875d9 100644 --- a/includes/event/event-capabilities.php +++ b/includes/event/event-capabilities.php @@ -51,23 +51,7 @@ private function has_cap( string $cap, array $args, WP_User $user ): bool { case self::CREATE: return $this->has_create( $user ); case self::VIEW: - if ( ! isset( $args[2] ) || ! is_int( $args[2] ) ) { - return false; - } - $event = $this->event_repository->get_event( $args[2] ); - if ( ! $event ) { - return false; - } - return $this->has_view( $user, $event ); case self::EDIT: - if ( ! isset( $args[2] ) || ! is_int( $args[2] ) ) { - return false; - } - $event = $this->event_repository->get_event( $args[2] ); - if ( ! $event ) { - return false; - } - return $this->has_edit( $user, $event ); case self::DELETE: if ( ! isset( $args[2] ) || ! is_int( $args[2] ) ) { return false; @@ -76,7 +60,15 @@ private function has_cap( string $cap, array $args, WP_User $user ): bool { if ( ! $event ) { return false; } - return $this->has_delete( $user, $event ); + + if ( self::VIEW === $cap ) { + return $this->has_view( $user, $event ); + } elseif ( self::EDIT === $cap ) { + return $this->has_edit( $user, $event ); + } elseif ( self::DELETE === $cap ) { + return $this->has_delete( $user, $event ); + } + break; } return false; From 91c8136b083bd47ef6402a703503013a7cae6478 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 17:25:14 +0100 Subject: [PATCH 23/26] Declare properties --- tests/event/event-capabilities.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/event/event-capabilities.php b/tests/event/event-capabilities.php index 48b8f6e4..cc8a39c9 100644 --- a/tests/event/event-capabilities.php +++ b/tests/event/event-capabilities.php @@ -10,6 +10,11 @@ use Wporg\TranslationEvents\Tests\Stats_Factory; class Event_Capabilities_Test extends GP_UnitTestCase { + private Event_Factory $event_factory; + private Stats_Factory $stats_factory; + private Attendee_Repository $attendee_repository; + private Event_Repository $event_repository; + public function setUp(): void { parent::setUp(); $this->event_factory = new Event_Factory(); From a9fced42e6393312510a025651274a0d5d99c31b Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Thu, 18 Apr 2024 17:43:21 +0100 Subject: [PATCH 24/26] Correctly call current_user_can() --- includes/event/event-form-handler.php | 6 ++++-- includes/routes/user/host-event.php | 2 +- templates/event.php | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/includes/event/event-form-handler.php b/includes/event/event-form-handler.php index b5856147..d05733e2 100644 --- a/includes/event/event-form-handler.php +++ b/includes/event/event-form-handler.php @@ -25,13 +25,15 @@ public function handle( array $form_data ): void { wp_send_json_error( esc_html__( 'Invalid form name.', '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 && ( ! current_user_can( 'edit_translation_event' ) ) ) { + 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 && ( ! current_user_can( 'delete_translation_event' ) ) ) { + 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 ); } diff --git a/includes/routes/user/host-event.php b/includes/routes/user/host-event.php index 253de5bc..0d670d00 100644 --- a/includes/routes/user/host-event.php +++ b/includes/routes/user/host-event.php @@ -39,7 +39,7 @@ public function handle( int $event_id, int $user_id ): void { $this->die_with_error( esc_html__( "Only logged-in users can manage the event's hosts.", 'gp-translation-events' ), 403 ); } - if ( ! current_user_can( 'edit_translation_event' ) ) { + 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 ); } diff --git a/templates/event.php b/templates/event.php index c487022f..05c290e4 100644 --- a/templates/event.php +++ b/templates/event.php @@ -59,7 +59,7 @@ id() ) ) : $_attendee = $attendee_repo->get_attendee( $event_id, $contributor->ID ); if ( $_attendee instanceof Attendee ) : echo '
    ID" ) ) . '">'; @@ -79,7 +79,7 @@

    - + id() ) ) : ?>

    Date: Fri, 19 Apr 2024 11:57:42 +0100 Subject: [PATCH 25/26] Remove unneeded else conditions --- includes/event/event-capabilities.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php index be4875d9..d27c3393 100644 --- a/includes/event/event-capabilities.php +++ b/includes/event/event-capabilities.php @@ -63,9 +63,11 @@ private function has_cap( string $cap, array $args, WP_User $user ): bool { if ( self::VIEW === $cap ) { return $this->has_view( $user, $event ); - } elseif ( self::EDIT === $cap ) { + } + if ( self::EDIT === $cap ) { return $this->has_edit( $user, $event ); - } elseif ( self::DELETE === $cap ) { + } + if ( self::DELETE === $cap ) { return $this->has_delete( $user, $event ); } break; From 22f933b4565ba0a73216b2b5a632ee31a818f818 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Fri, 19 Apr 2024 14:48:57 +0100 Subject: [PATCH 26/26] Allow passing event id as string --- includes/event/event-capabilities.php | 4 ++-- tests/event/event-capabilities.php | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/includes/event/event-capabilities.php b/includes/event/event-capabilities.php index d27c3393..fbe38953 100644 --- a/includes/event/event-capabilities.php +++ b/includes/event/event-capabilities.php @@ -53,10 +53,10 @@ private function has_cap( string $cap, array $args, WP_User $user ): bool { case self::VIEW: case self::EDIT: case self::DELETE: - if ( ! isset( $args[2] ) || ! is_int( $args[2] ) ) { + if ( ! isset( $args[2] ) || ! is_numeric( $args[2] ) ) { return false; } - $event = $this->event_repository->get_event( $args[2] ); + $event = $this->event_repository->get_event( intval( $args[2] ) ); if ( ! $event ) { return false; } diff --git a/tests/event/event-capabilities.php b/tests/event/event-capabilities.php index cc8a39c9..176323fe 100644 --- a/tests/event/event-capabilities.php +++ b/tests/event/event-capabilities.php @@ -64,6 +64,14 @@ public function test_gp_admin_can_view_non_published_events() { $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();