query_vars['tribe-edit-orders'] ) && $query->query_vars['tribe-edit-orders'];
if ( ! $is_correct_page ) {
return;
}
// This has no Performance problems, since get_post uses caching and we use this method later on.
$post = get_post( absint( $query->query_vars['p'] ) );
if ( ! $post ) {
return;
}
if ( 'page' !== $post->post_type ) {
return;
}
// Unset the p variable, we dont need it anymore
unset( $query->query_vars['p'] );
// Set `page_id` for faster query
$query->query_vars['page_id'] = $post->ID;
}
/**
* Tries to Flush the Rewrite rules
*
* @return void
*/
public function maybe_regenerate_rewrite_rules() {
// if they don't have any rewrite rules, do nothing
// Don't try to run stuff for non-logged users (too time consuming)
if ( ! is_array( $GLOBALS['wp_rewrite']->rules ) || ! is_user_logged_in() ) {
return;
}
$rules = $this->rewrite_rules_array();
$diff = array_diff( $rules, $GLOBALS['wp_rewrite']->rules );
$key_diff = array_diff_assoc( $rules, $GLOBALS['wp_rewrite']->rules );
if ( empty( $diff ) && empty( $key_diff ) ) {
return;
}
flush_rewrite_rules();
}
/**
* Gets the List of Rewrite rules we are using here
*
* @return array
*/
public function rewrite_rules_array() {
$bases = $this->add_rewrite_base_slug();
$rules = array(
sanitize_title_with_dashes( $bases['tickets'][0] ) . '/([0-9]{1,})/?' => 'index.php?p=$matches[1]&tribe-edit-orders=1',
);
return $rules;
}
/**
* For non events the links will be a little bit weird, but it's the safest way
*
* @param WP_Rewrite $wp_rewrite
*/
public function add_non_event_permalinks( WP_Rewrite $wp_rewrite ) {
$wp_rewrite->rules = $this->rewrite_rules_array() + $wp_rewrite->rules;
}
/**
* Add a new Query Var to allow tickets editing
* @param array $vars
* @return array
*/
public function add_query_vars( $vars ) {
$vars[] = 'tribe-edit-orders';
return $vars;
}
/**
* Update the RSVP and Tickets values for each Attendee
*/
public function update_tickets() {
$is_correct_page = get_query_var( 'tribe-edit-orders', false );
// Now fetch the display and check it
$display = get_query_var( 'eventDisplay', false );
if ( 'tickets' !== $display && ! $is_correct_page ) {
return;
}
if ( empty( $_POST['process-tickets'] ) || ( empty( $_POST['attendee'] ) && empty( $_POST['tribe-tickets-meta'] ) ) ) {
return;
}
$post_id = get_the_ID();
$attendees = ! empty( $_POST['attendee'] ) ? $_POST['attendee'] : array();
foreach ( $attendees as $order_id => $data ) {
/**
* An Action fired for each one of the Attendees that were posted on the Order Tickets page
*
* @var $data Infomation that we are trying to save
* @var $order_id ID of attendee ticket
* @var $post_id ID of event
*/
do_action( 'event_tickets_attendee_update', $data, $order_id, $post_id );
}
/**
* A way for Meta to be saved, because it's grouped in a different way
*
* @var $post_id ID of event
*/
do_action( 'event_tickets_after_attendees_update', $post_id );
// After Editing the Values we Update the Transient
Tribe__Post_Transient::instance()->delete( $post_id, Tribe__Tickets__Tickets::ATTENDEES_CACHE );
// If it's not events CPT
if ( $is_correct_page ) {
$url = home_url( 'tickets/' ) . $post_id;
} else {
$url = get_permalink( $post_id ) . '/tickets';
}
$url = add_query_arg( 'tribe_updated', 1, $url );
wp_safe_redirect( esc_url_raw( $url ) );
exit;
}
/**
* Makes sure only logged users can See the Tickets page.
*
* @return void
*/
public function authorization_redirect() {
global $wp_query;
/**
* @todo Remove this after we implement the Rewrites in Common
*/
$is_event_query = ! empty( $GLOBALS['wp_query']->tribe_is_event_query );
// When it's not Events Query and we have TEC active we dont care
if ( class_exists( 'Tribe__Events__Main' ) && ! $is_event_query ) {
return;
}
// If we got here and it's a 404 + single
if ( is_single() && is_404() ) {
return;
}
// Now fetch the display and check it
$display = get_query_var( 'eventDisplay', false );
if ( 'tickets' !== $display ) {
return;
}
// Only goes to the Redirect if user is not logged in
if ( is_user_logged_in() ) {
return;
}
// Loop back to the Event, this page is only for Logged users
wp_redirect( get_permalink() );
exit;
}
/**
* To allow `tickets` to be translatable we need to add it as a base
*
* @param array $bases The translatable bases
* @return array
*/
public function add_rewrite_base_slug( $bases = array() ) {
/**
* Allows users to filter and change the base for the order page
*
* @param string $slug
* @param array $bases
*/
$bases['tickets'] = (array) apply_filters( 'event_tickets_rewrite_slug_orders_page', 'tickets', $bases );
return $bases;
}
/**
* Adds the Permalink for the tickets end point
*
* @param Tribe__Events__Rewrite $rewrite
*/
public function add_permalink( Tribe__Events__Rewrite $rewrite ) {
// Adds the 'tickets' endpoint for single event pages.
$rewrite->single(
array( '{{ tickets }}' ),
array(
Tribe__Events__Main::POSTTYPE => '%1',
'post_type' => Tribe__Events__Main::POSTTYPE,
'eventDisplay' => 'tickets',
)
);
// Adds the `tickets` endpoint for recurring events
$rewrite->single(
array( '(\d{4}-\d{2}-\d{2})', '{{ tickets }}' ),
array(
Tribe__Events__Main::POSTTYPE => '%1',
'eventDate' => '%2',
'post_type' => Tribe__Events__Main::POSTTYPE,
'eventDisplay' => 'tickets',
)
);
}
/**
* Intercepts the_content from the posts to include the orders structure
*
* @param string $content Normally the_content of a post
* @return string
*/
public function intercept_content( $content = '' ) {
// Prevents firing more then it needs too outside of the loop
$in_the_loop = isset( $GLOBALS['wp_query']->in_the_loop ) && $GLOBALS['wp_query']->in_the_loop;
// Prevents Weird
$is_correct_page = get_query_var( 'tribe-edit-orders', false );
if ( ! $is_correct_page || ! $in_the_loop ) {
return $content;
}
ob_start();
include Tribe__Tickets__Templates::get_template_hierarchy( 'tickets/orders.php' );
$content = ob_get_clean();
return $content;
}
/**
* We need to intercept the template loading and load the correct file
*
* @param string $old_file Non important variable with the previous path
* @param string $template Which template we are dealing with
* @return string The correct File path for the tickets endpoint
*/
public function intercept_template( $old_file, $template ) {
global $wp_query;
/**
* @todo Remove this after we implement the Rewrites in Common
*/
$is_event_query = ! empty( $wp_query->tribe_is_event_query );
// When it's not our query we don't care
if ( ! $is_event_query ) {
return $old_file;
}
// If we got here and it's a 404 + single
if ( is_single() && is_404() ) {
return $old_file;
}
// Now fetch the display and check it
$display = get_query_var( 'eventDisplay', false );
if ( 'tickets' !== $display ) {
return $old_file;
}
// If for some reason it's not `single-event.php` we don't care either
if ( 'single-event.php' !== $template ) {
return $old_file;
}
// Fetch the Correct File using the Tickets Hiearchy
$file = Tribe__Tickets__Templates::get_template_hierarchy( 'tickets/orders.php' );
return $file;
}
/**
* Injects the Link to The front-end Tickets page normally at `tribe_events_single_event_after_the_meta`
*
* @return void
*/
public function inject_link_template() {
$event_id = get_the_ID();
$user_id = get_current_user_id();
if ( ! $this->has_rsvp_attendees( $event_id, $user_id ) && ! $this->has_ticket_attendees( $event_id, $user_id ) ) {
return;
}
$file = Tribe__Tickets__Templates::get_template_hierarchy( 'tickets/orders-link.php' );
include $file;
}
/**
* Injects the Link to The front-end Tickets page to non Events
*
* @param string $content The content form the post
* @return string $content
*/
public function inject_link_template_the_content( $content ) {
// Prevents firing more then it needs too outside of the loop
$in_the_loop = isset( $GLOBALS['wp_query']->in_the_loop ) && $GLOBALS['wp_query']->in_the_loop;
$post_id = get_the_ID();
$user_id = get_current_user_id();
/**
* @todo Remove this after we implement the Rewrites in Common
*/
$is_event_query = ! empty( $GLOBALS['wp_query']->tribe_is_event_query );
// When it's not our query we don't care
if ( ( class_exists( 'Tribe__Events__Main' ) && $is_event_query ) || ! $in_the_loop ) {
return $content;
}
// If we have this we are already on the tickets page
$is_correct_page = get_query_var( 'tribe-edit-orders', false );
if ( $is_correct_page ) {
return $content;
}
if ( ! $this->has_rsvp_attendees( $post_id, $user_id ) && ! $this->has_ticket_attendees( $post_id, $user_id ) ) {
return $content;
}
ob_start();
include Tribe__Tickets__Templates::get_template_hierarchy( 'tickets/orders-link.php' );
$content .= ob_get_clean();
return $content;
}
/**
* Fetches from the Cached attendees list the ones that are relevant for this user and event
* Important to note that this method will bring the attendees organized by order id
*
* @param int $event_id The Event ID it relates to
* @param int|null $user_id An Optional User ID
* @param boolean $include_rsvp If this should include RSVP, which by default is false
* @return array List of Attendees grouped by order id
*/
public function get_event_attendees_by_order( $event_id, $user_id = null, $include_rsvp = false ) {
$attendees = Tribe__Tickets__Tickets::get_event_attendees( $event_id );
$orders = array();
foreach ( $attendees as $key => $attendee ) {
// Ignore RSVP if we don't tell it specifically
if ( 'rsvp' === $attendee['provider_slug'] && ! $include_rsvp ) {
continue;
}
// If we have a user_id then test it and ignore the ones that don't have it
if ( ! is_null( $user_id ) ) {
if ( empty( $attendee['user_id'] ) || $attendee['user_id'] != $user_id ) {
continue;
}
}
$orders[ (int) $attendee['order_id'] ][] = $attendee;
}
return $orders;
}
/**
* Fetches from the Cached attendees list the ones that are relevant for this user and event
* Important to note that this method will bring the attendees from RSVP
*
* @param int $event_id The Event ID it relates to
* @param int|null $user_id An Optional User ID
* @return array Array with the RSVP attendees
*/
public function get_event_rsvp_attendees( $event_id, $user_id = null ) {
$all_attendees = Tribe__Tickets__Tickets::get_event_attendees( $event_id );
$attendees = array();
foreach ( $all_attendees as $key => $attendee ) {
// Skip Non RSVP
if ( 'rsvp' !== $attendee['provider_slug'] ) {
continue;
}
// If we have a user_id then test it and ignore the ones that don't have it
if ( ! is_null( $user_id ) ) {
if ( empty( $attendee['user_id'] ) || $attendee['user_id'] != $user_id ) {
continue;
}
}
$attendees[] = $attendee;
}
return $attendees;
}
/**
* Groups RSVP attendees by purchaser name/email
*
* @param int $event_id The Event ID it relates to
* @param int|null $user_id An optional user ID
* @return array Array with the RSVP attendees grouped by purchaser name/email
*/
public function get_event_rsvp_attendees_by_purchaser( $event_id, $user_id = null ) {
$attendees = $this->get_event_rsvp_attendees( $event_id, $user_id );
if ( ! $attendees ) {
return array();
}
$attendee_groups = array();
foreach ( $attendees as $attendee ) {
$key = $attendee['purchaser_name'] . '::' . $attendee['purchaser_email'];
if ( ! isset( $attendee_groups[ $key ] ) ) {
$attendee_groups[ $key ] = array();
}
$attendee_groups[ $key ][] = $attendee;
}
return $attendee_groups;
}
/**
* Gets a List of Possible RSVP answers
*
* @param string $selected Allows users to check if an option exists or get it's label
* @param bool $just_labels Whether just the options labels should be returned.
*
* @return array|bool An array containing the RSVP states, an array containing the selected
* option data or `false` if the selected option does not exist.
*/
public function get_rsvp_options( $selected = null, $just_labels = true ) {
$options = array(
'yes' => array( 'label' => __( 'Going', 'event-tickets' ), 'decrease_stock_by' => 1 ),
'no' => array( 'label' => __( 'Not Going', 'event-tickets' ), 'decrease_stock_by' => 0 ),
);
/**
* Allow users to add more RSVP options.
*
* Additional RSVP options should be specified in the following formats:
*
* [
* 'slug' => 'Option 1 label',
* 'slug' => [ 'label' => 'Option 3 label' ],
* 'slug' => [ 'label' => 'Option 2 label', 'decrease_stock_by' => 1 ],
* ]
*
* The `decrease_stock_by` key can be omitted and will default to `1`.
*
* @param array $options
* @param string $selected
*/
$options = apply_filters( 'event_tickets_rsvp_options', $options, $selected );
$options = array_filter( $options, array( $this, 'has_rsvp_format' ) );
array_walk( $options, array( $this, 'normalize_rsvp_option' ) );
// If an option was passed return it's label, but if doesn't exist return false
if ( ! is_null( $selected ) ) {
return isset( $options[ $selected ] ) ? $options[ $selected ]['label'] : false;
}
return $just_labels ?
array_combine( array_keys( $options ), wp_list_pluck( $options, 'label' ) )
: $options;
}
/**
* Check if the RSVP is a valid option
*
* @param string $option Which rsvp option to check
* @return boolean
*/
public function is_valid_rsvp_option( $option ) {
return in_array( $option, array_keys( $this->get_rsvp_options() ) );
}
/**
* Counts the Amount of RSVP attendees
*
* @param int $event_id The Event ID it relates to
* @param int|null $user_id An Optional User ID
* @return int
*/
public function count_rsvp_attendees( $event_id, $user_id = null ) {
$rsvp_orders = $this->get_event_rsvp_attendees( $event_id, $user_id );
return count( $rsvp_orders );
}
/**
* Counts the Amount of Tickets attendees
*
* @param int $event_id The Event ID it relates to
* @param int|null $user_id An Optional User ID
* @return int
*/
public function count_ticket_attendees( $event_id, $user_id = null ) {
$ticket_orders = $this->get_event_attendees_by_order( $event_id, $user_id );
$i = 0;
foreach ( $ticket_orders as $orders ) {
$i += count( $orders );
}
return $i;
}
/**
* Verifies if we have RSVP attendees for this user and event
*
* @param int $event_id The Event ID it relates to
* @param int|null $user_id An Optional User ID
* @return int
*/
public function has_rsvp_attendees( $event_id, $user_id = null ) {
$rsvp_orders = $this->get_event_rsvp_attendees( $event_id, $user_id );
return ! empty( $rsvp_orders );
}
/**
* Verifies if we have Tickets attendees for this user and event
*
* @param int $event_id The Event ID it relates to
* @param int|null $user_id An Optional User ID
* @return int
*/
public function has_ticket_attendees( $event_id, $user_id = null ) {
$ticket_orders = $this->get_event_attendees_by_order( $event_id, $user_id );
return ! empty( $ticket_orders );
}
/**
* Gets a String to descript which type of Tickets/RSVP we are dealign with
*
* @param int $event_id The Event ID it relates to
* @param int|null $user_id An Optional User ID
* @param boolean $plurals Return the Strings as Plural
* @return int
*/
public function get_description_rsvp_ticket( $event_id, $user_id = null, $plurals = false ) {
$what_to_update = array();
if ( $this->has_rsvp_attendees( $event_id, $user_id ) ) {
$what_to_update[] = $plurals ? esc_html__( 'RSVPs', 'event-tickets' ) : esc_html__( 'RSVP', 'event-tickets' );
}
if ( $this->has_ticket_attendees( $event_id, $user_id ) ) {
$what_to_update[] = $plurals ? esc_html__( 'Tickets', 'event-tickets' ) : esc_html__( 'Ticket', 'event-tickets' );
}
// Just Return false if array is empty
if ( empty( $what_to_update ) ) {
return false;
}
return implode( esc_html__( ' and ', 'event-tickets' ), $what_to_update );
}
/**
* Creates the HTML for the Select Element for RSVP options
*
* @param string $name The Name of the Field
* @param string $selected The Current selected option
* @param int $event_id The Event/Post ID (optional)
* @param int $ticket_id The Ticket/RSVP ID (optional)
* @return void
*/
public function render_rsvp_selector( $name, $selected, $event_id = null, $ticket_id = null ) {
$options = $this->get_rsvp_options();
?>
is_rsvp_restricted( $event_id, $ticket_id ) ) {
$is_disabled = 'disabled title="' . esc_attr__( 'This RSVP is no longer active.', 'event-tickets' ) . '"';
}
return $is_disabled;
}
/**
* Creates the HTML for the status of the RSVP choice.
*
* @param string $name The Name of the Field
* @param string $selected The Current selected option
* @param int $event_id The Event/Post ID (optional)
* @param int $ticket_id The Ticket/RSVP ID (optional)
* @return void
*/
public function render_rsvp_status( $name, $selected, $event_id = null, $ticket_id = null ) {
$options = $this->get_rsvp_options();
echo sprintf( '%s', esc_html( $options[ $selected ] ) );
}
/**
* Prunes RSVP options that are arrays and are not defining a label.
*
* @param array|string $option
*
* @return bool
*/
protected function has_rsvp_format( $option ) {
if ( ! is_array( $option ) ) {
return true;
}
// label is the bare minimum
if ( ! isset( $option['label'] ) ) {
return false;
}
return empty( $option['decrease_stock_by'] )
|| (
is_numeric( $option['decrease_stock_by'] )
&& intval( $option['decrease_stock_by'] ) == $option['decrease_stock_by']
&& intval( $option['decrease_stock_by'] ) >= 0
);
}
/**
* Normalizes the RSVP option conforming it to the array format.
*
* @param array|string $option
*/
protected function normalize_rsvp_option( &$option ) {
$label_only_format = ! is_array( $option );
if ( $label_only_format ) {
$option = array( 'label' => $option, 'decrease_stock_by' => 1 );
} else {
$option['decrease_stock_by'] = isset( $option['decrease_stock_by'] ) ? $option['decrease_stock_by'] : 1;
}
}
}