Skip to content

Commit

Permalink
Merge pull request #215 from WordPress/event-capabilities
Browse files Browse the repository at this point in the history
Event capabilities
  • Loading branch information
psrpinto authored Apr 22, 2024
2 parents b09fcf9 + 22f933b commit 730c34a
Show file tree
Hide file tree
Showing 12 changed files with 419 additions and 119 deletions.
1 change: 1 addition & 0 deletions autoload.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
188 changes: 188 additions & 0 deletions includes/event/event-capabilities.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<?php

namespace Wporg\TranslationEvents\Event;

use GP;
use WP_User;
use Wporg\TranslationEvents\Attendee\Attendee;
use Wporg\TranslationEvents\Attendee\Attendee_Repository;
use Wporg\TranslationEvents\Stats\Stats_Calculator;

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';

/**
* All the capabilities that concern an Event.
*/
private const CAPS = array(
self::CREATE,
self::VIEW,
self::EDIT,
self::DELETE,
);

private Event_Repository_Interface $event_repository;
private Attendee_Repository $attendee_repository;
private Stats_Calculator $stats_calculator;

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;
}

/**
* 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,
);
}
}
54 changes: 17 additions & 37 deletions includes/event/event-form-handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) ) {
Expand All @@ -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'] ) ) );
Expand Down
5 changes: 5 additions & 0 deletions includes/routes/event/create.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
13 changes: 1 addition & 12 deletions includes/routes/event/details.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}

Expand All @@ -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() );
}
}
37 changes: 8 additions & 29 deletions includes/routes/event/edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,19 @@

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;

/**
* Displays the event edit page.
*/
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 {
Expand All @@ -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';
Expand All @@ -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() );
}
Expand Down
Loading

0 comments on commit 730c34a

Please sign in to comment.