* SPDX-FileCopyrightText: 2021 Harald Eilertsen * * SPDX-License-Identifier: AGPL-3.0-or-later */ if ( ! class_exists( 'GiglogAdmin_Concert' ) ) { /** * Class to hold all information about a given concert. */ class GiglogAdmin_Concert { // phpcs:disable Squiz.Commenting.VariableComment.Missing private ?int $id; private ?string $cname; private ?GiglogAdmin_Venue $venue; private DateTimeImmutable $cdate; private ?string $tickets; private ?string $eventlink; private ?int $status; private array $roles; private ?DateTimeImmutable $created; private ?DateTimeImmutable $updated; // phpcs:enable public const STATUS_NONE = 0; public const STATUS_ACCRED_REQ = 1; public const STATUS_PHOTO_APPROVED = 2; public const STATUS_TEXT_APPROVED = 3; public const STATUS_ALL_APPROVED = 4; public const STATUS_REJECTED = 5; /** * Constructs a new concert object from an array of attributes. * * The attributes are expected to be named as in the database, * so this constructor can be used to construct the object * directly from the database row. * * @param object $attrs an object or array with the attributes. */ public function __construct( object $attrs ) { $this->id = isset( $attrs->id ) ? $attrs->id : null; $this->cname = isset( $attrs->wpgconcert_name ) ? $attrs->wpgconcert_name : null; $this->cdate = new DateTimeImmutable( $attrs->wpgconcert_date ?? 'now' ); $this->tickets = isset( $attrs->wpgconcert_tickets ) ? $attrs->wpgconcert_tickets : null; $this->eventlink = isset( $attrs->wpgconcert_event ) ? $attrs->wpgconcert_event : null; $this->status = isset( $attrs->wpgconcert_status ) ? $attrs->wpgconcert_status : 0; $this->roles = isset( $attrs->wpgconcert_roles ) ? json_decode( $attrs->wpgconcert_roles, true ) : array(); $this->created = isset( $attrs->created ) ? new DateTimeImmutable( $attrs->created ) : null; $this->updated = isset( $attrs->updated ) ? new DateTimeImmutable( $attrs->updated ) : null; if ( isset( $attrs->venue ) ) { if ( isset( $attrs->wpgvenue_name ) && isset( $attrs->wpgvenue_city ) ) { $venue_attrs = (object) array( 'id' => $attrs->venue, 'wpgvenue_name' => $attrs->wpgvenue_name, 'wpgvenue_city' => $attrs->wpgvenue_city, ); $this->venue = new GiglogAdmin_Venue( $venue_attrs ); } else { $this->venue = GiglogAdmin_Venue::get( $attrs->venue ); } } else { $this->venue = null; } } /** * Return the concert with the given id. * * @param int $id Database id of the concert to fetch. * @return null|self */ public static function get( int $id ) : ?self { $gigs = self::find_concerts( array( 'id' => $id ) ); $count = count( $gigs ); if ( $count > 1 ) { error_log( "Found more than one concert with the same id ({$id})." . ' This should not happen!' ); wp_die( 'Oops! Something went wrong', 500 ); } else if ( $count != 1 ) { return null; } else { return array_shift( $gigs ); } } /** * Create a new concert object. * * @param string $name The concert description. * @param int $venue_id The id of the venue where the concert will be held. * @param string $date The date of the concert. * @param string $ticketlink URL where tickets can be bough. * @param string $eventlink URL for more information about concert. * * @throws GiglogAdmin_DuplicateConcertException If concert is a duplicate. */ public static function create( string $name, int $venue_id, string $date, string $ticketlink, string $eventlink ): ?self { $gigs = self::find_concerts( array( 'name' => $name, 'venue_id' => $venue_id, 'date' => $date, ) ); if ( ! empty( $gigs ) ) { throw new GiglogAdmin_DuplicateConcertException( "Duplicate concert: name: {$name}, venue_id: {$venue_id}, date: {$date}" ); } else { $concert = new GiglogAdmin_Concert( (object) array( 'wpgconcert_name' => $name, 'venue' => $venue_id, 'wpgconcert_date' => $date, 'wpgconcert_tickets' => $ticketlink, 'wpgconcert_event' => $eventlink, ) ); $concert->save(); return self::get( $concert->id() ); } } /** * Update a concert with new content and save to database. * * @param object $attrs Object or array of changed attributes. */ public function update( object $attrs ) : bool { $need_update = false; if ( isset( $attrs->wpgconcert_name ) && $attrs->wpgconcert_name != $this->cname ) { $this->cname = $attrs->wpgconcert_name; $need_update = true; } if ( isset( $attrs->wpgconcert_date ) && $attrs->wpgconcert_date != $this->cdate ) { $this->cdate = new DateTimeImmutable( $attrs->wpgconcert_date ); $need_update = true; } if ( isset( $attrs->wpgconcert_tickets ) && $attrs->wpgconcert_tickets != $this->tickets ) { $this->tickets = $attrs->wpgconcert_tickets; $need_update = true; } if ( isset( $attrs->wpgconcert_event ) && $attrs->wpgconcert_event != $this->eventlink ) { $this->eventlink = $attrs->wpgconcert_eventlink; $need_update = true; } if ( isset( $attrs->wpgconcert_status ) && $attrs->wpgconcert_status != $this->status ) { $this->status = $attrs->wpgconcert_status; $need_update = true; } if ( isset( $attrs->wpgconcert_roles ) && $attrs->wpgconcert_roles != $this->roles ) { $this->roles = $attrs->wpgconcert_roles; $need_update = true; } if ( isset( $attrs->venue ) && $attrs->venue != $this->venue()->id() ) { $this->venue = GiglogAdmin_Venue::get( $attrs->venue ); $need_update = true; } if ( $need_update ) { $this->save(); } return $need_update; } /** * Build a query with given filters. * * @param array $filter Filters to include in the query. * @param bool $count Only return count of result if true. */ private static function _build_query( array $filter = array(), bool $count = false ) : string { global $wpdb; $ct = "{$wpdb->prefix}giglogadmin_concerts"; $vt = "{$wpdb->prefix}giglogadmin_venues"; if ( $count ) { $query = "SELECT count({$ct}.id) "; } else { $query = "SELECT {$ct}.*, {$vt}.wpgvenue_name, {$vt}.wpgvenue_city "; } $query .= "FROM {$ct} LEFT JOIN {$vt} ON {$ct}.venue = {$vt}.id WHERE wpgconcert_date >= CURRENT_TIMESTAMP"; $keymap = array( 'id' => $wpdb->prefix . 'giglogadmin_concerts.id', 'name' => 'wpgconcert_name', 'date' => 'wpgconcert_date', 'month' => 'MONTH(wpgconcert_date)', 'venue_id' => $wpdb->prefix . 'giglogadmin_venues.id', 'venue' => $wpdb->prefix . 'giglogadmin_venues.wpgvenue_name', 'city' => $wpdb->prefix . 'giglogadmin_venues.wpgvenue_city', 'currentuser' => 'wpgconcert_roles', ); $where = array(); $offset = 0; $limit = 15; $page = null; foreach ( $filter as $key => $value ) { switch ( $key ) { case 'name': case 'date': case 'month': case 'venue': case 'city': array_push( $where, $keymap[ $key ] . $wpdb->prepare( '=%s', $value ) ); break; case 'id': case 'venue_id': array_push( $where, $keymap[ $key ] . $wpdb->prepare( '=%d', $value ) ); break; case 'currentuser': array_push( $where, $keymap[ $key ] . $wpdb->prepare( ' like %s', esc_like( $value ) ) ); break; case 'page': $page = intval( $value ); break; case 'offset': $offset = intval( $value ); break; case 'limit': $limit = intval( $value ); break; } } if ( ! empty( $where ) ) { $query .= ' AND ' . implode( ' and ', $where ); } if ( $page !== null ) { $offset = ( $page - 1 ) * $limit; } $query .= ' ORDER BY wpgconcert_date'; $query .= " LIMIT {$offset},{$limit}"; return $query; } /** * Return an array of concert objects optionally limited by a specified * filter. * * Valid filters are: * - 'venue_id' => int : only include concerts at the given venue * - 'city' => string : only include concerts in the given city * * @param array $filter Filter to use for the query. * @return array */ public static function find_concerts( array $filter = array() ) : array { global $wpdb; $query = self::_build_query( $filter, false ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $results = $wpdb->get_results( $query ); return array_map( fn( $c ) => new GiglogAdmin_Concert( $c ), $results ); } /** * Return the number of objects matching the given filter. * * @param array $filter The filter to use for the query. * @return int */ public static function count( array $filter = array() ) : int { global $wpdb; $query = self::_build_query( $filter, true ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $count = $wpdb->get_var( $query ); return $count ? $count : 0; } /** * Return number of pages with the limit of concerts/page * specified in the filter. * * @param array $filter The filter to use for the query. * @return number of pages. */ public static function count_pages( array $filter = array() ) : int { if ( ! isset( $filter['limit'] ) ) { $filter['limit'] = 15; } $num_concerts = self::count( $filter ); return ( intval( $num_concerts / $filter['limit'] ) ) + 1; } /** * Save concert to database. */ public function save() : void { global $wpdb; $columns = array( 'wpgconcert_name' => $this->cname, 'venue' => $this->venue->id(), 'wpgconcert_date' => $this->cdate->format( 'c' ), 'wpgconcert_tickets' => $this->tickets, 'wpgconcert_event' => $this->eventlink, 'wpgconcert_status' => $this->status, 'wpgconcert_roles' => wp_json_encode( $this->roles ), ); if ( null !== $this->id ) { $res = $wpdb->update( $wpdb->prefix . 'giglogadmin_concerts', $columns, array( 'id' => $this->id ) ); } else { $res = $wpdb->insert( $wpdb->prefix . 'giglogadmin_concerts', $columns ); } if ( false === $res ) { $wpdb->print_error( __METHOD__ ); } elseif ( null === $this->id ) { $this->id = $wpdb->insert_id; } } /** * Return database id for concert. */ public function id() : int { return $this->id ? $this->id : 0; } /** * Return the concert "name". */ public function cname() : string { return $this->cname ? $this->cname : ''; } /** * Return the concert venue. */ public function venue() { return $this->venue; } /** * Return the date of the concert. */ public function cdate() : DateTimeImmutable { return $this->cdate; } /** * Return the ticket url for the concert. */ public function tickets() { return $this->tickets ? $this->tickets : ''; } /** * Return the event link for the concert. */ public function eventlink() { return $this->eventlink ? $this->eventlink : ''; } /** * Return the status of the concert. */ public function status() : int { return $this->status; } /** * Set the status of the concert. * * @param int $new_status The new status for the concert. */ public function set_status( int $new_status ) { $this->status = $new_status; } /** * Return the roles defined for this concert. * * @return array */ public function roles() : array { return $this->roles ? $this->roles : array(); } /** * Assign a role for the concert to the given user. * * @param string $role The role to assign. * @param string $username The user to assign the role to. */ public function assign_role( string $role, string $username ) : void { $this->roles[ $role ] = $username; } /** * Unassign any roles for this concert for the given user. * * @param string $username The user to remove from the roles. */ public function remove_user_from_roles( string $username ) : void { $this->roles = array_filter( $this->roles, fn( $u) => $u != $username ); } /** * Return creation time of the concert object. */ public function created() : DateTimeImmutable { return $this->created; } /** * Return time of last update to this concert object. */ public function updated() : DateTimeImmutable { return $this->updated; } } }