Skip to content

Commit a761832

Browse files
committed
Merge branch 'release/T24.freybug.1' of github.com:the-events-calendar/event-tickets
2 parents 2ef1a6e + 57cab6f commit a761832

File tree

12 files changed

+1091
-9
lines changed

12 files changed

+1091
-9
lines changed

changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
### [5.17.0.1] 2024-11-21
4+
5+
* Tweak - Introduced filter `tec_tickets_rest_api_archive_results` that gives the ability to filter out the tickets being provided to the REST API archive.
6+
* Security - Prevent Tickets from showing through REST API to unauthorized requests. [SVUL-9]
7+
38
### [5.17.0] 2024-11-19
49

510
* Version - Event Tickets 5.17.0 is only compatible with Event Tickets Plus 6.1.1 or higher.

event-tickets.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Plugin Name: Event Tickets
44
* Plugin URI: https://evnt.is/1acb
55
* Description: Event Tickets allows you to sell basic tickets and collect RSVPs from any post, page, or event.
6-
* Version: 5.17.0
6+
* Version: 5.17.0.1
77
* Requires at least: 6.3
88
* Requires PHP: 7.4
99
* Author: The Events Calendar

readme.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
Contributors: theeventscalendar, brianjessee, camwynsp, redscar, tribalmike, rafsuntaskin, aguseo, bordoni, borkweb, GeoffBel, jentheo, leahkoerper, lucatume, neillmcshea, vicskf, zbtirrell, juanfra
44
Tags: tickets, event registration, RSVP, ticket sales, attendee management
5-
Stable tag: 5.17.0
5+
Stable tag: 5.17.0.1
66
Requires at least: 6.3
7-
Tested up to: 6.7
7+
Tested up to: 6.7.1
88
Requires PHP: 7.4
99
License: GPLv2 or later
1010
License URI: http://www.gnu.org/licenses/gpl-2.0.html
@@ -203,6 +203,11 @@ Check out our extensive [knowledgebase](https://evnt.is/18wm) for articles on us
203203

204204
== Changelog ==
205205

206+
= [5.17.0.1] 2024-11-21 =
207+
208+
* Tweak - Introduced filter `tec_tickets_rest_api_archive_results` that gives the ability to filter out the tickets being provided to the REST API archive.
209+
* Security - Prevent Tickets from showing through REST API to unauthorized requests. [SVUL-9]
210+
206211
= [5.17.0] 2024-11-19 =
207212

208213
* Version - Event Tickets 5.17.0 is only compatible with Event Tickets Plus 6.1.1 or higher.

src/Tribe/Main.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Tribe__Tickets__Main {
1515
/**
1616
* Current version of this plugin.
1717
*/
18-
const VERSION = '5.17.0';
18+
const VERSION = '5.17.0.1';
1919

2020
/**
2121
* Used to store the version history.

src/Tribe/REST/V1/Endpoints/Ticket_Archive.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,16 @@ public function get( WP_REST_Request $request ) {
219219
$data['rest_url'] = add_query_arg( $query_args, $main->get_url( '/tickets/' ) );
220220
$data['total'] = $found;
221221
$data['total_pages'] = $total_pages;
222-
$data['tickets'] = $tickets;
222+
223+
/**
224+
* Filters the tickets returned by the REST API.
225+
*
226+
* @since 5.17.0.1
227+
*
228+
* @param array $tickets The tickets returned by the REST API.
229+
* @param WP_REST_Request $request The request object.
230+
*/
231+
$data['tickets'] = apply_filters( 'tec_tickets_rest_api_archive_results', $tickets, $request );
223232

224233
$headers = array(
225234
'X-ET-TOTAL' => $data['total'],

src/Tribe/REST/V1/Main.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,60 @@ public function hook() {
4545

4646
add_filter( 'tribe_rest_event_data', [ $this, 'rest_event_data_add_attendance' ], 10, 2 );
4747
add_filter( 'tribe_rest_events_archive_data', [ $this, 'rest_events_archive_add_attendance' ], 10, 2 );
48+
49+
add_filter( 'tec_tickets_rest_api_archive_results', [ $this, 'filter_out_tickets_on_unauthorized' ], 10, 2 );
50+
add_filter( 'tribe_rest_single_ticket_data', [ $this, 'filter_out_single_ticket_data_on_unauthorized' ], 10, 2 );
51+
}
52+
53+
/**
54+
* Filters out single ticket data that unauthorized users should not see.
55+
*
56+
* @since 5.17.0.1
57+
*
58+
* @param array $ticket_data
59+
* @param WP_REST_Request $request
60+
*
61+
* @return array
62+
*/
63+
public function filter_out_single_ticket_data_on_unauthorized( array $ticket_data, WP_REST_Request $request ): array {
64+
if ( $this->request_has_manage_access() ) {
65+
return $ticket_data;
66+
}
67+
68+
$ticket_validator = tribe( 'tickets.rest-v1.validator' );
69+
70+
if ( $ticket_validator->should_see_ticket( $ticket_data['post_id'] ?? 0, $request ) ) {
71+
return $ticket_data;
72+
}
73+
74+
return $ticket_validator->remove_ticket_data( $ticket_data );
75+
}
76+
/**
77+
* Filters out tickets that unauthorized users should not see.
78+
*
79+
* @since 5.17.0.1
80+
*
81+
* @param array $tickets The tickets to filter.
82+
* @param WP_REST_Request $request The request object.
83+
*
84+
* @return array The filtered tickets.
85+
*/
86+
public function filter_out_tickets_on_unauthorized( array $tickets, WP_REST_Request $request ) : array {
87+
if ( $this->request_has_manage_access() ) {
88+
return $tickets;
89+
}
90+
91+
$ticket_validator = tribe( 'tickets.rest-v1.validator' );
92+
93+
foreach ( $tickets as $offset => $ticket ) {
94+
if ( $ticket_validator->should_see_ticket( $ticket['post_id'] ?? 0, $request ) ) {
95+
continue;
96+
}
97+
98+
$tickets[ $offset ] = $ticket_validator->remove_ticket_data( $ticket );
99+
}
100+
101+
return $tickets;
48102
}
49103

50104
/**

src/Tribe/REST/V1/Validator/Base.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,82 @@ class Tribe__Tickets__REST__V1__Validator__Base
55
extends Tribe__Tickets__Validator__Base
66
implements Tribe__Tickets__REST__V1__Validator__Interface {
77

8+
/**
9+
* Remove all the ticket data from ticket in rest response by authorized user.
10+
*
11+
* @since 5.17.0.1
12+
*
13+
* @param array $ticket The ticket data.
14+
*
15+
* @return array The ticket data with password protected fields removed.
16+
*/
17+
public function remove_ticket_data( array $ticket ): array {
18+
foreach ( $ticket as $key => $val ) {
19+
if ( is_array( $val ) || is_object( $val ) ) {
20+
$ticket[ $key ] = $this->remove_ticket_data( (array) $val );
21+
continue;
22+
}
23+
24+
if ( is_numeric( $val ) ) {
25+
$ticket[ $key ] = 0;
26+
continue;
27+
}
28+
29+
if ( is_bool( $val ) ) {
30+
$ticket[ $key ] = null;
31+
continue;
32+
}
33+
34+
$ticket[ $key ] = __( 'No Access', 'event-tickets' );;
35+
}
36+
37+
return $ticket;
38+
}
39+
40+
/**
41+
* Check if the ticket should be seen by the current request.
42+
*
43+
* @since TBD
44+
*
45+
* @param int $parent_id The parent's ID.
46+
* @param WP_REST_Request $request The request object.
47+
*
48+
* @return bool Whether the ticket should be seen by the current user.
49+
*/
50+
public function should_see_ticket( int $parent_id, WP_REST_Request $request ): bool {
51+
if ( empty( $parent_id ) ) {
52+
$parent_id = 0;
53+
}
54+
55+
$parent = get_post( $parent_id );
56+
57+
if ( ! ( $parent instanceof WP_Post && $parent->ID ) ) {
58+
// Possibly parent does not exist anymore. Unauthorized should see nothing.
59+
return false;
60+
}
61+
62+
if ( ! 'publish' === $parent->post_status ) {
63+
// Unauthorized users should not see tickets from not published events.
64+
return false;
65+
}
66+
67+
try {
68+
$tec_validator = tribe( 'tec.rest-v1.validator' );
69+
70+
if ( ! method_exists( $tec_validator, 'can_access_password_content' ) ) {
71+
// The validator is available but outdated. Better to hide data than assume its good.
72+
throw new Exception( 'Method not found' );
73+
}
74+
75+
if ( post_password_required( $parent ) && ! $tec_validator->can_access_password_content( $parent, $request ) ) {
76+
// Unauthorized users should not see tickets from password protected events.
77+
return false;
78+
}
79+
} catch ( Exception $e ) {
80+
// If the validator is not available, we can't check the password. Fail silently hiding data.
81+
return false;
82+
}
83+
84+
return true;
85+
}
886
}

tests/integration/Tribe/Attendee_Registration/Template_Test.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22

3-
use Tribe__Tickets__Attendee_Registration__Template;
4-
use Tribe__Tickets__Attendee_Registration__Main;
3+
use Tribe__Tickets__Attendee_Registration__Template as Template;
4+
use Tribe__Tickets__Attendee_Registration__Main as Main;
55
use Tribe\Tests\Traits\With_Uopz;
66

77
class Template_Test extends \Codeception\TestCase\WPTestCase {
@@ -11,7 +11,7 @@ class Template_Test extends \Codeception\TestCase\WPTestCase {
1111
* @before
1212
*/
1313
public function setup_singletons() {
14-
tribe()->singleton( 'tickets.attendee_registration.template', new Tribe__Tickets__Attendee_Registration__Template() );
14+
tribe()->singleton( 'tickets.attendee_registration.template', new Template() );
1515
tribe()->singleton( 'tickets.attendee_registration', new Tribe__Tickets__Attendee_Registration__Main() );
1616
}
1717

@@ -24,7 +24,7 @@ public function it_should_return_true_if_on_custom_ar_page_with_shortcode( $wp_q
2424
global $wp_query, $post, $shortcode_tags;
2525

2626
uopz_set_return( 'get_queried_object', $wp_query_update->queried_object );
27-
uopz_set_return( Tribe__Tickets__Attendee_Registration__Main::class, 'get_slug', 'attendee-registration' );
27+
uopz_set_return( Main::class, 'get_slug', 'attendee-registration' );
2828

2929
$template = tribe( 'tickets.attendee_registration.template' );
3030

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
namespace Tribe\Tickets\REST\V1\Endpoints;
4+
5+
use Tribe__Tickets__REST__V1__Endpoints__Single_Ticket as Single;
6+
use Codeception\TestCase\WPTestCase;
7+
use Tribe\Tests\Traits\With_Clock_Mock;
8+
use tad\Codeception\SnapshotAssertions\SnapshotAssertions;
9+
use Tribe\Tickets\Test\Commerce\TicketsCommerce\Ticket_Maker;
10+
use Tribe__Date_Utils as Dates;
11+
use Prophecy\Prophecy\ObjectProphecy;
12+
13+
class Single_Ticket_Test extends WPTestCase {
14+
use SnapshotAssertions;
15+
use With_Clock_Mock;
16+
use Ticket_Maker;
17+
18+
/**
19+
* @var \Tribe__REST__Messages_Interface
20+
*/
21+
protected $messages;
22+
23+
/**
24+
* @var \Tribe__Tickets__REST__V1__Post_Repository
25+
*/
26+
protected $repository;
27+
28+
/**
29+
* @var \Tribe__Tickets__REST__V1__Validator__Interface
30+
*/
31+
protected $validator;
32+
33+
/**
34+
* @return Archive
35+
*/
36+
private function make_instance() {
37+
$messages = $this->messages instanceof ObjectProphecy ? $this->messages->reveal() : $this->messages;
38+
$repository = $this->repository instanceof ObjectProphecy ? $this->repository->reveal() : $this->repository;
39+
$validator = $this->validator instanceof ObjectProphecy ? $this->validator->reveal() : $this->validator;
40+
41+
return new Single( $messages, tribe( \Tribe__Tickets__REST__V1__Post_Repository::class ), $validator );
42+
}
43+
44+
/**
45+
* @test
46+
*/
47+
public function it_should_hide_password_protected_fields() {
48+
$request = new \WP_REST_Request( 'GET', '' );
49+
$this->freeze_time( Dates::immutable( '2024-06-13 17:25:00' ) );
50+
$event_ids = [];
51+
foreach( range( 1, 5 ) as $i ) {
52+
$event_ids[] = tribe_events()->set_args(
53+
[
54+
'title' => 'Test Event ' . $i,
55+
'status' => 'publish',
56+
'start_date' => '2024-07-14 12:00:00',
57+
'duration' => 2 * HOUR_IN_SECONDS,
58+
]
59+
)->create()->ID;
60+
}
61+
62+
$ticket_ids = [];
63+
foreach( $event_ids as $event_id ) {
64+
$ticket_ids[] = $this->create_tc_ticket( $event_id );
65+
}
66+
67+
$this->assertEquals( '2024-06-13 17:25:00', date( 'Y-m-d H:i:s' ) );
68+
69+
wp_update_post( [
70+
'ID' => $event_ids[2],
71+
'post_password' => 'password',
72+
] );
73+
74+
wp_update_post( [
75+
'ID' => $event_ids[4],
76+
'post_password' => 'password',
77+
] );
78+
79+
$sut = $this->make_instance();
80+
81+
$data_array = [];
82+
foreach ( $ticket_ids as $ticket_id ) {
83+
$request->set_param( 'id', $ticket_id );
84+
$data_array[] = $sut->get( $request );
85+
}
86+
87+
$this->assertCount( 5, $data_array );
88+
89+
$json = wp_json_encode( $data_array, JSON_PRETTY_PRINT );
90+
$json = str_replace(
91+
array_map( static fn( $id ) => '"id": ' . $id, $ticket_ids ),
92+
'"id": "{TICKET_ID}"',
93+
$json
94+
);
95+
$json = str_replace(
96+
array_map( static fn( $id ) => '"post_id": ' . $id, $event_ids ),
97+
'"post_id": "{EVENT_ID}"',
98+
$json
99+
);
100+
$json = str_replace(
101+
array_map( static fn( $id ) => '?id=' . $id, $event_ids ),
102+
'?id={EVENT_ID}',
103+
$json
104+
);
105+
$json = str_replace(
106+
array_map( static fn( $id ) => 'for ' . $id, $event_ids ),
107+
'for {EVENT_ID}',
108+
$json
109+
);
110+
$json = str_replace(
111+
array_map( static fn( $id ) => '&id=' . $id, $ticket_ids ),
112+
'&id={TICKET_ID}',
113+
$json
114+
);
115+
$json = str_replace(
116+
array_map( static fn( $id ) => '\/events\/' . $id, $event_ids ),
117+
'\/events\/{EVENT_ID}',
118+
$json
119+
);
120+
$json = str_replace(
121+
array_map( static fn( $id ) => '\/tickets\/' . $id, $ticket_ids ),
122+
'\/events\/{TICKET_ID}',
123+
$json
124+
);
125+
$this->assertMatchesJsonSnapshot( $json );
126+
}
127+
}

0 commit comments

Comments
 (0)