File: /home/cpt/public_html/wp-content/plugins/events-manager/classes/em-tickets-bookings.php
<?php
/**
* Deals with the each ticket booked in a single booking.
* Each ticket is grouped by EM_Ticket_Bookings, which is stored as an array in the tickets_bookings object.
*
* You can access/add/unset the array of EM_Ticket_Bookings and its sub array of EM_Ticket_Booking objects in a few ways, with example ticket ID # 34884:
*
* Access the EM_Ticket_Bookings of a ticket:
* $EM_Tickets_Bookings[34884]
* $EM_Tickets_Bookings->tickets_bookings[34884]
*
* Add a new EM_Ticket_Bookings for a ticket:
* $EM_Tickets_Bookings[1234] = new EM_Tickets_Bookings(...)
* $EM_Tickets_Bookings->tickets_bookings[1234] = new EM_Tickets_Bookings(...)
*
* Add a new EM_Ticket_Booking object to existing EM_Ticket_Bookings objects
* $EM_Tickets_Bookings[34884]['uuid'] = new EM_Ticket_Booking(...); // text key - should be a uuid
* $EM_Tickets_Bookings->tickets_bookings[34884]['uuid'] = new EM_Ticket_Booking(...);
* $EM_Tickets_Bookings->tickets_bookings[34884]->tickets_bookings['uuid'] = new EM_Ticket_Booking(...);
*
* Unset works the same way:
* unset($EM_Tickets_Bookings[35280]);
* unset($EM_Tickets_Bookings->tickets_bookings[34884]);
* etc.
*
* @author marcus
*
*/
class EM_Tickets_Bookings extends EM_Object implements Iterator, Countable, ArrayAccess {
/**
* Array of EM_Ticket_Booking objects for a specific event
* @var EM_Ticket_Bookings[]
*/
public $tickets_bookings = array();
protected $tickets_bookings_loaded;
/**
* When adding existing booked tickets via add() with 0 spaces, they get slotted here for deletion during save() so they circumvent validation.
* @var array[EM_Ticket_Booking]
*/
var $tickets_bookings_deleted = array();
/**
* This object belongs to this booking object
* @var EM_Booking
*/
protected $booking;
/**
* The booking ID this object belongs to, saved by preference if booking object is not available, and booking object is obtained after when needed.
* @var float|int|string
*/
protected $booking_id;
/**
* This object belongs to this booking object
* @var EM_Ticket
*/
var $spaces;
var $price;
/**
* Used to prefix any actions/filters on this class, so that extended classes can force their own prefix.
* @var string
*/
public static $n = 'em_tickets_bookings';
public static $sortable_columns = array('ticket_name', 'ticket_description', 'ticket_spaces', 'ticket_price', 'ticket_id', 'ticket_booking_id', 'ticket_booking_spaces', 'ticket_booking_price');
/**
* Creates an EM_Tickets instance.
* @note This function will eventually require an EM_Booking object. At time of writing, this means versions of Events Manager Pro < 3.0 will break.
* @param EM_Booking $EM_Booking
*/
function __construct( $EM_Booking = null ){
if( is_object($EM_Booking) && !empty($EM_Booking->booking_uuid) ){ // all booking objects have a uuid
$this->booking = $EM_Booking;
$this->booking_id = $EM_Booking->booking_id;
}elseif( is_numeric($EM_Booking) ){
$this->booking_id = $EM_Booking;
}
$this->get_ticket_bookings();
do_action( static::$n, $this, $EM_Booking);
}
public function __get( $shortname ){
if( $shortname === 'booking_id' || $shortname === 'id' ){
return $this->booking_id;
} elseif ( $shortname === 'booking' ){
return $this->get_booking();
}
return parent::__get($shortname);
}
public function __set( $prop, $val ){
if( $prop === 'booking' && !empty($val->booking_uuid) ){
$this->booking = $val;
$this->booking_id = $this->booking->booking_id;
// point all booking props to this object
foreach( $this->tickets_bookings as $ticket_booking ) {
$ticket_booking->booking = $this->booking;
}
// reload tickets
$this->tickets_bookings_loaded = false;
$this->get_ticket_bookings(); // refresh ticket bookings
return;
} elseif ( $prop === 'booking_id' ) {
$this->booking_id = absint($val);
}
parent::__set( $prop, $val );
}
public function __call( $function, $args ){
if( method_exists($this->get_booking(), $function) ){
return $this->get_booking()->$function($args);
}
return parent::__call($function, $args);
}
/**
* Return relevant fields that will be used for storage, excluding things such as event and ticket objects that should get reloaded
* @return string[]
*/
public function __sleep(){
$array = array('tickets_bookings','tickets_bookings_loaded');
return apply_filters('em_tickets_bookings_sleep', $array, $this);
}
/**
* Returns an array of individual ticket bookings (single space attendees) for given search $args. If $count is set to true, then the number of results found is returned instead.
* @param $args
*
* @return EM_Ticket_Bookings[]|int
*/
public static function get( $args, $count = false ) {
global $wpdb;
// Pass this onto bookings, to generate the SQL, then wrap it into another SQL to get the ticket bookings themselves
if( $count ) {
$args['array'] = array('booking_id');
}
$args = EM_Bookings::get_default_search( $args ); // sanitize directly
$sql_parts = EM_Bookings::get_sql( $args );
$conditions = $orderbys = $joins = $fields = array();
// quick search hack, if we're looking for what is formatted as a ticket uuid then circumvent searching the bookings
if( preg_match('/^[a-zA-Z0-9]{32}$/', $args['search']) ) {
unset($sql_parts['data']['conditions']['search']);
$conditions['search'] = "ticket_uuid = '{$args['search']}'";
$sql_parts['statement']['where'] = 'WHERE ' . implode(' AND ', $sql_parts['data']['conditions'] ); // recreate conditions without search
}
// generate a bookings subquery
$bookings_sql = static::get_bookings_subquery( $sql_parts, $count );
// execute count or query
if( $count ) {
// we don't need to worry about ordering because only join or condition for ordering
$tickets_bookings_sql = static::get_built_count_sql( $bookings_sql, array('conditions' => $conditions) );
return $wpdb->get_var($tickets_bookings_sql);
} else {
// get all the Ticket_Booking fields and concat them with a tb. prefix into $fields
$EM_Ticket_Booking = new EM_Ticket_Booking();
foreach ( array_keys($EM_Ticket_Booking->fields) as $field ) {
$fields[$field] = $field === 'ticket_booking_id' ? 'DISTINCT tb.' . $field : 'tb.' . $field;
}
// add outer ordering and potentially ordering by ticket data requiring a join
foreach ( $sql_parts['data']['orderbys'] as $orderby ) {
$orderby = explode(' ', $orderby); // in case we have order, remove it
$orderby_field = $orderby[0];
$orderbys[$orderby_field] = 'b.' . $orderby_field;
}
if( !empty($args['orderby']) ) {
if( !is_array($args['orderby']) ) {
$args['orderby'] = explode(',', str_replace(' ', '', $args['orderby']));
}
foreach ( $args['orderby'] as $orderby ) {
$orderby = explode(' ', $orderby); // in case we have order, remove it
$orderby_field = $orderby[0];
if( in_array($orderby_field, static::$sortable_columns) ) {
if( in_array( $orderby_field, ['ticket_id', 'ticket_booking_id', 'ticket_booking_price']) ) {
// these fields are located in the tickets_bookings_table and do not require a join
$orderbys[$orderby_field] = 'tb.' . $orderby_field;
} elseif ( $orderby_field == 'ticket_booking_spaces' ) {
// we need to group and sum, not join
$fields['ticket_booking_spaces'] = 'SUM(ticket_booking_spaces) AS ticket_booked_spaces';
$orderbys[$orderby_field] = 'ticket_booked_spaces';
} else {
// the rest require a join to the tickets table
$orderbys[$orderby_field] = 't.' . $orderby_field;
$joins['tickets'] = 'LEFT JOIN ' . EM_TICKETS_TABLE . ' t ON t.ticket_id = tb.ticket_id';
}
} elseif ( preg_match('/^attendee_/', $orderby_field) ) {
// we search attendee metas for this field
$field = preg_replace('/^attendee_/', '', $orderby_field);
$joins['attendees'] = "LEFT JOIN ( SELECT ticket_booking_id, meta_value AS {$orderby_field} FROM " . EM_TICKETS_BOOKINGS_META_TABLE . " WHERE meta_key='{$field}') {$orderby_field} ON {$orderby_field}.ticket_booking_id = tb.ticket_booking_id";
$orderbys[$orderby_field] = $orderby_field . '.' . $orderby_field;
$fields[] = $orderby_field . '.' . $orderby_field;
}
}
}
$orderbys = self::build_sql_orderby($args, $orderbys, $args['order']);
$orderby = !empty($orderbys) ? ' ORDER BY ' . implode( ', ', $orderbys ) : '';
// build the SQL
$tickets_bookings_sql = static::get_built_sql( $bookings_sql, $fields, $joins, $conditions );
// we would also need to determine if we SHOULD join, and also make these sortable under specific views,
$tickets_bookings_sql .= $orderby . "\n" . $sql_parts['statement']['limit'] . "\n" . $sql_parts['statement']['offset'];
// run it
return static::get_results( $tickets_bookings_sql );
}
}
/**
* @param $sql
*
* @return EM_Ticket_Bookings[]
*/
public static function get_results( $sql ) {
global $wpdb;
$tickets_bookings = array();
$tickets_bookings_results = $wpdb->get_results($sql, ARRAY_A);
foreach( $tickets_bookings_results as $ticket_bookings ){
unset($ticket_bookings['ticket_spaces']);
$tickets_bookings[$ticket_bookings['booking_id'].'-'.$ticket_bookings['ticket_id']] = new EM_Ticket_Bookings($ticket_bookings);
}
return $tickets_bookings;
}
public static function get_built_sql( $bookings_sql, $fields, $joins, $conditions = array() ){
$condition = !empty($conditions) ? " WHERE " . implode(' AND ', $conditions) : '';
return "SELECT ". implode(', ', $fields) ." FROM " . EM_TICKETS_BOOKINGS_TABLE . " tb INNER JOIN ({$bookings_sql}) b ON b.booking_id = tb.booking_id " . implode(' ', $joins) . $condition . " GROUP BY tb.booking_id, tb.ticket_id ";
}
public static function get_built_count_sql( $bookings_sql, $extras = array( 'joins' => [], 'conditions' => [] ) ) {
$joins = $extras['joins'] ?? [];
$condition = !empty($extras['conditions']) ? " WHERE " . implode(' AND ', $extras['conditions']) : '';
return "SELECT COUNT(*) FROM (SELECT b.booking_id FROM " . EM_TICKETS_BOOKINGS_TABLE . " tb INNER JOIN ({$bookings_sql}) b ON b.booking_id = tb.booking_id " . implode(' ', $joins) . $condition . " GROUP BY tb.booking_id, tb.ticket_id) bookings";
}
public static function get_bookings_subquery( $sql_parts, $count = false ) {
global $wpdb;
// build SQL subquery/join statement without limits, offsets, etc
$sql_subquery_statement = $sql_parts['statement'];
unset($sql_subquery_statement['limit'], $sql_subquery_statement['offset'], $sql_subquery_statement['orderby']);
// execute count or query
if( $count ) {
$bookings_sql = implode("\n", $sql_subquery_statement);
} else {
// we need to add specific subquery selector fields to avoid conflicts with the outer SQL statement
$selectors = [ EM_BOOKINGS_TABLE . '.booking_id' ];
$fields = array();
// first, get all fields from EM_BOOKINGS_TABLE, then all the joins, into separate array items
$fields[EM_BOOKINGS_TABLE] = $wpdb->get_col('SHOW COLUMNS FROM '. EM_BOOKINGS_TABLE);
foreach( $sql_parts['data']['joins'] as $table => $join ){
if( !preg_match('/^field\./', $table) ) {
$fields[$table] = $wpdb->get_col('SHOW COLUMNS FROM '. $table);
} else {
// acccount for special joins particularly when ordering
$field_name = str_replace('field.', '', $table);
$fields[$field_name] = array($field_name);
}
}
// now, build a unique set of selectors, first finder taking precedence
$selector_fields = array('booking_id');
// clean orderbys in case some ASC and DESC strings made it here
$orderbys_clean = [];
foreach( $sql_parts['data']['orderbys'] as $orderby ) {
$orderbys_clean[] = str_replace([' ASC', ' DESC', ' asc', ' desc'], '', $orderby);
}
// add orderby fields to selectors
foreach( $fields as $table => $table_fields ){
foreach( $table_fields as $field ){
if ( in_array($field, $orderbys_clean) && !in_array($field, $selector_fields) ) {
$selector_fields[] = $field;
$selectors[] = $table . '.' . $field;
}
}
}
// now build the subquery with the selectors
$sql_subquery_statement['select'] = 'SELECT ' . implode(', ', $selectors) . ' FROM ' . EM_BOOKINGS_TABLE;
$bookings_sql = implode("\n", $sql_subquery_statement);
}
return $bookings_sql;
}
/**
* @return EM_Ticket_Bookings|false
*/
public function get_first(){
$this->get_ticket_bookings();
return reset($this->tickets_bookings);
}
/**
* Return a specific EM_Ticket_Bookings object if a valid $ticket_id is supplied, or alternatively returns all EM_Ticket_Bookings objects registered to this object.
* If when requesting a $ticket_id and no EM_Ticket_Bookings object exists for it within the object, a new blank object is created and appended to the tickets_bookings property, with 0 spaces and 0 price.
* @param EM_Ticket|int $ticket
* @return EM_Ticket_Bookings|EM_Ticket_Bookings[]
*/
public function get_ticket_bookings( $ticket = false ){
$ticket_id = is_object($ticket) ? $ticket->ticket_id : absint($ticket);
if( !$this->tickets_bookings_loaded && !empty($this->booking_id) ){
// we could get tickets individually via EM_Ticket_Bookings, but this is one db call vs multiple
global $wpdb;
$sql = "SELECT * FROM ". EM_TICKETS_BOOKINGS_TABLE ." WHERE booking_id ='{$this->booking_id}' ORDER BY ticket_booking_id ASC";
$results = $wpdb->get_results($sql, ARRAY_A);
//Get tickets belonging to this tickets booking.
$tickets_bookings = array();
foreach ($results as $ticket_booking){
$ticket_booking['booking'] = $this->get_booking();
$EM_Ticket_Booking = new EM_Ticket_Booking($ticket_booking);
if( empty($tickets_bookings[$EM_Ticket_Booking->ticket_id]) ) $tickets_bookings[$EM_Ticket_Booking->ticket_id] = array();
$tickets_bookings[$EM_Ticket_Booking->ticket_id][]= $EM_Ticket_Booking;
}
foreach( $tickets_bookings as $id => $ticket_bookings ){
$this->tickets_bookings[$id] = new EM_Ticket_Bookings($ticket_bookings);
}
}
$this->tickets_bookings_loaded = true;
if( $ticket_id ){
if( empty($this->tickets_bookings[$ticket_id]) ){
$this->tickets_bookings[$ticket_id] = new EM_Ticket_Bookings( array('ticket_id' => $ticket_id, 'booking' => $this->get_booking() ) );
}
return $this->tickets_bookings[$ticket_id];
}
return $this->tickets_bookings;
}
public function get_post( $override_availability = false ){
if( !empty($_REQUEST['em_tickets']) ){
foreach( $_REQUEST['em_tickets'] as $ticket_id => $values){
//make sure ticket exists
$ticket_id = absint($ticket_id);
if( !empty($values['spaces']) || $this->booking_id ){ // if spaces booked for first time, editing and spaces are 0 (in case we need to delete anything)
// get an EM_Ticket_Bookings object, which will be added if non-existent, $EM_Ticket_Bookings is therefore passed by reference.
$EM_Ticket_Bookings = $this->get_ticket_bookings($ticket_id);
if( !$EM_Ticket_Bookings->get_post() ){
$this->add_error($EM_Ticket_Bookings->get_errors());
}
// make sure things are recalculated
$this->price = 0; //so price calculations are reset
$this->get_spaces(true);
$this->get_price();
}
}
}
return apply_filters( static::$n . '_get_post', empty($this->errors), $this, $override_availability );
}
public function validate( $override_availability = false ){
if( count($this->tickets_bookings) > 0 ){
foreach($this->tickets_bookings as $EM_Ticket_Bookings){ /* @var $EM_Ticket_Bookings EM_Ticket_Bookings */
if ( !$EM_Ticket_Bookings->validate( $override_availability ) ){
$this->errors = array_merge($this->errors, $EM_Ticket_Bookings->get_errors());
}
}
}
return apply_filters( static::$n . '_validate', empty($this->errors), $this, $override_availability );
}
/**
* Saves the ticket bookings for this booking into the database, whether a new or existing booking
* @return boolean
*/
function save(){
do_action(static::$n . '_save_pre',$this);
//save/update tickets
foreach( $this->tickets_bookings as $EM_Ticket_Booking ){
$result = $EM_Ticket_Booking->save();
if(!$result){
$this->errors = array_merge($this->errors, $EM_Ticket_Booking->get_errors());
}
}
//delete old tickets if set to 0 in an update
foreach($this->tickets_bookings_deleted as $EM_Ticket_Booking ){
$result = $EM_Ticket_Booking->delete();
if(!$result){
$this->errors = array_merge($this->errors, $EM_Ticket_Booking->get_errors());
}
}
//return result
if( count($this->errors) > 0 ){
$this->feedback_message = __('There was a problem saving the booking.', 'events-manager');
$this->errors[] = __('There was a problem saving the booking.', 'events-manager');
return apply_filters(static::$n . '_save', false, $this);
}
return apply_filters(static::$n . '_save', true, $this);
}
/**
* Adds a ticket booking to the object, equivalent of adding directly to the array of tickets_bookings
*
* @param EM_Ticket_Booking $EM_Ticket_Booking
* @return bool
*/
function add( $EM_Ticket_Booking ){
if( $EM_Ticket_Booking instanceof EM_Ticket_Booking ) {
$this->get_ticket_bookings($EM_Ticket_Booking->ticket_id)->tickets_bookings[$EM_Ticket_Booking->ticket_uuid] = $EM_Ticket_Booking;
return true;
}
return false;
}
/**
* Checks if this set has a specific ticket booked, returning the key of the ticket in the EM_Tickets_Bookings->ticket_bookings array
* @param int $ticket_id
* @return mixed
*/
function has_ticket( $ticket_id ){
foreach ($this->tickets_bookings as $key => $EM_Ticket_Booking){
if( $EM_Ticket_Booking->ticket_id == $ticket_id ){
return apply_filters(static::$n . '_has_ticket',$key,$this);
}
}
return apply_filters(static::$n . '_has_ticket',false,$this);
}
/**
* Smart event locator, saves a database read if possible.
*/
function get_booking(){
if( !$this->booking || $this->booking->booking_id !== $this->booking_id ) {
$this->booking = new EM_Booking( $this->booking_id );
}
return apply_filters(static::$n . '_get_booking', $this->booking, $this);
}
/**
* Delete all ticket bookings
* @return boolean
*/
function delete(){
global $wpdb;
$result = false;
if( $this->get_booking()->can_manage() ){
$result_meta = $wpdb->query("DELETE FROM ".EM_TICKETS_BOOKINGS_META_TABLE." WHERE ticket_booking_id IN (SELECT ticket_booking_id FROM ".EM_TICKETS_BOOKINGS_TABLE." WHERE booking_id='{$this->booking_id}')");
$result = $wpdb->query("DELETE FROM ".EM_TICKETS_BOOKINGS_TABLE." WHERE booking_id='{$this->booking_id}'");
}
return apply_filters(static::$n . '_delete', ($result !== false && $result_meta !== false), $this);
}
/**
* Get the total number of spaces booked in this booking. Seting $force_reset to true will recheck spaces, even if previously done so.
* @param unknown_type $force_refresh
* @return mixed
*/
function get_spaces( $force_refresh = false ){
if( $force_refresh || $this->spaces == 0 ){
$spaces = 0;
foreach( $this->tickets_bookings as $EM_Ticket_Bookings ){
$spaces += $EM_Ticket_Bookings->get_spaces( $force_refresh );
}
$this->spaces = $spaces;
}
return apply_filters(static::$n . '_get_spaces',$this->spaces,$this);
}
/**
* Gets the total price for this whole booking by adding up subtotals of booked tickets. Seting $force_reset to true will recheck spaces, even if previously done so.
* @param boolean $format
* @return float
*/
function get_price( $format = false ){
if( $this->price == 0 ){
$price = $this->calculate_price( true );
// deprecated, use the _calculate_price filter instead
$this->price = apply_filters(static::$n . '_get_price', $price, $this);
}
if($format){
return $this->format_price($this->price);
}
return $this->price;
}
function calculate_price( $force_refresh = false ){
if( $this->price == null || $force_refresh ){
$price = 0;
foreach($this->tickets_bookings as $EM_Ticket_Bookings ){
$price += $EM_Ticket_Bookings->calculate_price( $force_refresh );
}
$this->price = apply_filters(static::$n . '_calculate_price', $price, $this, $force_refresh);
}
return $this->price;
}
/* WIP - not really used as we just use get() to split bookings into results and order by attendee meta, the main search filtering is doen at booking level
* @see wp-content/plugins/events-manager/classes/EM_Object#build_sql_conditions()
*/
public static function build_sql_conditions( $args = array() ){
$conditions = parent::build_sql_conditions($args);
if( is_numeric($args['status']) ){
$conditions['status'] = 'ticket_status='.$args['status'];
}
return apply_filters(static::$n . '_build_sql_conditions', $conditions, $args);
}
/* Overrides EM_Object method to apply a filter to result
* @see wp-content/plugins/events-manager/classes/EM_Object#build_sql_orderby()
*/
public static function build_sql_orderby( $args, $accepted_fields, $default_order = 'ASC' ){
return apply_filters( static::$n . '_build_sql_orderby', parent::build_sql_orderby($args, $accepted_fields, get_option('dbem_events_default_order')), $args, $accepted_fields, $default_order );
}
/*
* WIP - not really used as we just use get() to split bookings into results and order by attendee meta, the main search filtering is doen at booking level
* @param array $array_or_defaults may be the array to override defaults
* @param array $array
* @return array
* @uses EM_Object#get_default_search()
*/
public static function get_default_search( $array_or_defaults = array(), $array = array() ){
$defaults = array(
'status' => false,
'person' => true //to add later, search by person's tickets...
);
//sort out whether defaults were supplied or just the array of search values
if( empty($array) ){
$array = $array_or_defaults;
}else{
$defaults = array_merge($defaults, $array_or_defaults);
}
//specific functionality
$defaults['owner'] = !current_user_can('manage_others_bookings') ? get_current_user_id():false;
return apply_filters(static::$n . '_get_default_search', parent::get_default_search($defaults,$array), $array, $defaults);
}
//Iterator Implementation
#[\ReturnTypeWillChange]
/**
* @return void
*/
public function rewind(){
$this->get_ticket_bookings();
reset($this->tickets_bookings);
}
#[\ReturnTypeWillChange]
/**
* @return EM_Ticket_Bookings
*/
public function current(){
return current($this->tickets_bookings);
}
#[\ReturnTypeWillChange]
/**
* @return int Ticket ID
*/
public function key(){
return key($this->tickets_bookings);
}
#[\ReturnTypeWillChange]
/**
* @return EM_Ticket_Bookings
*/
public function next(){
return next($this->tickets_bookings);
}
#[\ReturnTypeWillChange]
public function valid(){
$key = key($this->tickets_bookings);
return ($key !== NULL && $key !== FALSE);
}
//Countable Implementation
#[\ReturnTypeWillChange]
/**
* @return int
*/
public function count(){
return count($this->tickets_bookings);
}
// ArrayAccess Implementation
#[\ReturnTypeWillChange]
/**
* @param $offset
* @param $value
* @return void
*/
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->tickets_bookings[] = $value;
} else {
$this->tickets_bookings[$offset] = $value;
}
}
#[\ReturnTypeWillChange]
/**
* @param $offset
* @return bool
*/
public function offsetExists($offset) {
return isset($this->tickets_bookings[$offset]);
}
#[\ReturnTypeWillChange]
/**
* @param $offset
* @return void
*/
public function offsetUnset($offset) {
unset($this->tickets_bookings[$offset]);
}
#[\ReturnTypeWillChange]
/**
* @param $offset
* @return EM_Ticket_Bookings|null
*/
public function offsetGet($offset) {
return isset($this->tickets_bookings[$offset]) ? $this->tickets_bookings[$offset] : null;
}
public function __debugInfo(){
$object = clone($this);
$object->booking = !empty($this->booking_id) ? 'Booking ID #'.$this->booking_id : 'New Booking - No ID';
$object->shortnames = 'Removed for export, uncomment from __debugInfo()';
$object->mime_types = 'Removed for export, uncomment from __debugInfo()';
if( empty($object->errors) ) $object->errors = false;
return (Array) $object;
}
}
?>