?
Current File : /home/c/i/d/cideo/www/wp-includesVIp/js/crop/images/gf-fields.tar
gf-booking-providers.php000066600000025317151262333040011325 0ustar00<?php

defined( 'ABSPATH' ) or exit; // Exit if accessed directly

if ( class_exists( 'GFForms' ) ) {
	class GF_Appointment_Booking_Providers extends GF_Field {
		public $type = 'appointment_providers';

		public function get_form_editor_field_title() {
			return esc_attr__( 'Booking Providers', 'gravityforms' );
		}
		
		
		/*
		* Where to assign this widget
		*/
		public function get_form_editor_button() {
			return array(
				'group' => 'appointment_calendar',
				'text'  => $this->get_form_editor_field_title()
			);
		}	
		/*
		* Add button to the group
		*/
		public function add_button( $field_groups ) {
			$field_groups = $this->ga_appointment_providers_gf_group( $field_groups );
			return parent::add_button( $field_groups );
		}
		/*
		* Add our group
		*/	
		public function ga_appointment_providers_gf_group( $field_groups ) {
			foreach ( $field_groups as $field_group ) {
				if ( $field_group['name'] == 'appointment_calendar' ) {
					return $field_groups;
				}
			}		
			$field_groups[] = array(
				'name'   => 'appointment_calendar',
				'label'  => __( 'Appointment Booking', 'simplefieldaddon' ),
				'fields' => array(
				)
			);

			return $field_groups;
		}	
		
		/*
		* Widget settings
		*/			
		function get_form_editor_field_settings() {
			return array(
				'enable_enhanced_ui_setting',
				'label_setting',
				'error_message_setting',			
				'label_placement_setting',
				'admin_label_setting',
				'size_setting',
				'description_setting',
				'css_class_setting',
				'rules_setting',	
				'conditional_logic_field_setting',
				'visibility_setting'
			);
		}

		public function is_conditional_logic_supported() {
			return true;
		}

		/**
		 * Field Markups
		 */			
		public function get_field_input( $form, $value = '', $entry = null ) {
			$form_id            = absint( $form['id'] );
			$is_entry_detail    = $this->is_entry_detail();
			$is_form_editor     = $this->is_form_editor();

			$id                 = $this->id;	
			$field_id           = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id";
			
			$logic_event        = $this->get_conditional_logic_event( 'change' );
			$size               = $this->size;
			$class_suffix       = $is_entry_detail ? '_admin' : '';
			$chosenUI           = $this->enableEnhancedUI ? ' chosen-select' : '';
			$providers_class    = ' appointment_provider_id';
			
			$class              = $size . $class_suffix . $chosenUI . $providers_class;
			$css_class          = trim( esc_attr( $class ) . ' gfield_select' );
			$tabindex           = $this->get_tabindex();
			$disabled_text      = $is_form_editor ? 'disabled="disabled"' : '';
			$required_attribute = $this->isRequired ? 'aria-required="true"' : '';
			$invalid_attribute  = $this->failed_validation ? 'aria-invalid="true"' : 'aria-invalid="false"';
			

			$select_providers = '';

			if( $this->is_entry_edit() ) {
				$select_providers .= sprintf( "<div class='ginput_container ginput_container_select'><select name='input_%d' id='%s' $logic_event class='%s' $tabindex %s %s %s>%s</select></div>", $id, $field_id, $css_class, $disabled_text, $required_attribute, $invalid_attribute, $this->get_providers_entry_choices($form, $value) );
			} elseif( !$this->is_form_editor() ) {
				if( ga_service_id($form) && gf_field_type_exists( $form, 'appointment_services' ) ) {
					$select_providers .= sprintf( "<div class='ginput_container ginput_container_select'><select name='input_%d' id='%s' $logic_event class='%s' $tabindex %s %s %s form_id='%d'>%s</select></div>", $id, $field_id, $css_class, $disabled_text, $required_attribute, $invalid_attribute, $form_id, $this->get_providers_choices($form, $value) );
				} else {
					$select_providers .= '<p>' .ga_get_form_translated_data($form = false, 'error_no_services'). '</p>';
				}
			}

			return $select_providers;
		}
		
		/**
		 * Is Entry Edit
		 */	
		public function is_entry_edit() {
			if ( rgget( 'page' ) == 'gf_entries' && rgget( 'view' ) == 'entry' && rgpost( 'screen_mode' ) == 'edit' ) {
				return true;
			}

			return false;
		}	
		
		/**
		 * Returns TRUE if the current page is the form editor page. Otherwise, returns FALSE
		 */		
		public function is_form_editor() {
			if ( rgget( 'page' ) == 'gf_edit_forms' && ! rgempty( 'id', $_GET ) && rgempty( 'view', $_GET ) ) {
				return true;
			}
			return false;
		}				
		
		/**
		 * Get Provider Select Options
		 */			
		public function get_providers_choices($form, $value) {
			if( !ga_service_id($form) ) {
				return '';
			}
			
			$options = '';
			$service_id = absint( ga_service_id($form) );
			$provider_id = '';
			
			// Form submited services field value
			if( gf_field_type_exists($form, 'appointment_services') ) {
				$services_field_value  = gf_get_field_type_value($form, 'appointment_services');
				if( is_numeric($services_field_value) && 'ga_services' == get_post_type($services_field_value) ) {
					$service_id = $services_field_value;
				}	
				
				if( is_numeric($value) ) {
					$provider_id = $value;
				} 			
			}
			
			$args = array( 'post_type' => 'ga_providers', 'posts_per_page' => -1, 'orderby' => 'date', 'order' => 'DESC', 'meta_query' => array(array('key' => 'ga_provider_services', 'value' => serialize( strval( $service_id ) ), 'compare' => 'LIKE') ) );
			$the_query = new WP_Query( $args );
			wp_reset_postdata();
			
			// The Loop
			if ( $the_query->have_posts() ) {
				while ( $the_query->have_posts() ) {
					$the_query->the_post();
					$post = get_post( get_the_id() );
					$selected = $provider_id == get_the_id() ? ' selected="selected"' : '';
					$options .= '<option value="'.get_the_ID().'"'.$selected.'>'.$post->post_title.'</option>' . PHP_EOL;					
				}
				wp_reset_postdata();
			} else {
				$options .= '<option value="0">No preference</option>' . PHP_EOL;
			}
		
			return $options;
		}	


		/**
		 * Get Providers Entry Options
		 */			
		public function get_providers_entry_choices($form, $value) {
			$args = array( 'post_type' => 'ga_providers', 'posts_per_page' => -1, 'orderby' => 'date', 'order' => 'DESC' );
			$the_query = new WP_Query( $args );
			wp_reset_postdata();		
			$options = '<option value="0">No preference</option>' . PHP_EOL;

			// The Loop
			if ( $the_query->have_posts() ) {
				while ( $the_query->have_posts() ) {
					$the_query->the_post();
					$post = get_post( get_the_id() );
					$selected = $value == get_the_id() ? ' selected="selected"' : '';
					$options .= '<option value="'.get_the_ID().'"'.$selected.'>'.$post->post_title.'</option>' . PHP_EOL;					
				}
				wp_reset_postdata();
			}
			return $options;
		}	



		/**
		 * Validation Failed
		 */	
		private function validationFailed( $message = '' ) {
			$this->failed_validation = true;
			$message = esc_html__( $message, 'gravityforms' );
			$this->validation_message = empty( $this->errorMessage ) ? $message : $this->errorMessage;
		}			
		
		/**
		 * Validation
		 */			
		public function validate( $value, $form ) {
			$form_id = absint( $form['id'] );
			
			// Check if services widget is found		
			if( gf_field_type_exists( $form, 'appointment_services' ) && 'ga_services' == get_post_type( gf_get_field_type_value( $form, 'appointment_services' ) ) ) {
				# service exists
			} else {
				$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_required_service') );
				return;
			}
			
			// Service field value
			$service_id  = gf_get_field_type_value( $form, 'appointment_services' );
			
			
			// Selected service exists in form category term
			$form_cat_slug = rgar($form, 'ga_service_category');
			$cat           = term_exists( $form_cat_slug, 'ga_service_cat' );
			
			if( $cat ) {
				if( has_term( $cat, 'ga_service_cat', $service_id ) ) {
					# valid
				} else {
					$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_required_service') );
					return;					
				}
			}			

			
			// Service has a provider assigned, this function will get first provider ID
			if( ga_get_provider_id($service_id) && is_numeric($value) ) {
				$provider_id = $value;
				
				// Provider has the selected service
				if( ga_provider_has_service($service_id, $provider_id) ) {
					
				} else {
					$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_providers_service') );
					return;							
				}
				
			} elseif( !ga_get_provider_id($service_id) && $value == 0 ) {
				# Service doesn't have any providers assigned but still valid as 0 for no provider
				//$this->validationFailed( 'Service doesn\'t have providers' );
				//return;
			} else {
				$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_required_provider') );
				return;				
			}
		}	
			


		/**
		 * Save value
		 */	
		public function get_value_save_entry( $value, $form, $input_name, $entry_id, $entry ) {
			//$post_id = absint( $_POST['appointment_booking_provider'] );
			//$value = get_the_title( $post_id );
			return $value;
		}	

		
		/**
		* Show provider title on entry single & GP Preview Plugin
		*/		
		public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {		
			$post_id = absint( $value );
			if( 'ga_providers' == get_post_type($post_id) ) {
				
				$value = get_the_title( $post_id );	
				return esc_html( $value );		
				
			} 
			
			if( $value == 0 ) {
				return 'No preference';
			}
			
		}		
		
		/*
		* Show provider title on entry list
		* To sanitize values before being output in the entry list page
		*/
		/*
		public function get_value_entry_list( $value, $entry, $field_id, $columns, $form ) {
			$post_id = absint( $value );			
			if( 'ga_providers' == get_post_type($post_id) ) {
				$value = get_the_title( $post_id );	
				return esc_html( $value );				
			}	
			
			if( $value == 0 ) {
				return 'No preference';
			}
		}	
		*/

		/**
		* Merge tag, on notifications, confirmations
		*/			
		public function get_value_merge_tag( $value, $input_id, $entry, $form, $modifier, $raw_value, $url_encode, $esc_html, $format, $nl2br ) {
			$post_id = absint( $value );
			if( 'ga_providers' == get_post_type($post_id) ) {
				
				$value = get_the_title( $post_id );	
				return esc_html( $value );		
				
			} 
			
			if( $value == 0 ) {
				return 'No preference';
			}			
		}		
		
		
		public function get_form_editor_inline_script_on_page_render() {
			return "
			gform.addFilter('gform_form_editor_can_field_be_added', function (canFieldBeAdded, type) {
				if (type == 'appointment_providers') {
					if (GetFieldsByType(['appointment_providers']).length > 0) {
						alert(" . json_encode( esc_html__( 'Only one Booking Providers field can be added to the form', 'gravityformscoupons' ) ) . ");
						return false;
					}
				}
				return canFieldBeAdded;
			});";
		}		
	
	} // end class
	GF_Fields::register( new GF_Appointment_Booking_Providers() );	
} // end if
ga-calendar.php000066600000114456151262333040007431 0ustar00<?php 
defined( 'ABSPATH' ) or exit; // Exit if accessed directly


class GA_Calendar {
	private $month;
	private $year;
	private $selected_date;
	private $selected_slot;
	private $days_of_week = array('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
	private $week_starts;	
	private $num_days;
	private $date_info;
	private $day_of_week;
	private $time_zone;
	private $service_id;
	private $provider_id;
	private $form_id = 0;
	
	public function __construct( $form_id, $month, $year, $service_id, $provider_id, $selected_date = false, $selected_slot = false  ) {
		// FORM ID
		$this->form_id = $form_id ? $form_id : 0;
		
		// MONTH & DATE
		$this->month         = $month;
		$this->year          = $year;
		$this->selected_date = $selected_date ? $selected_date : false;
		$this->selected_slot = $selected_slot ? $selected_slot : false;
		
		// SERVICE & PROVIDER ID
		$this->service_id  = (int) $service_id;
		$this->provider_id = (int) $provider_id;
		
		// TIMEZONE
		$this->time_zone = ga_time_zone();
		
		// DATEINFO
		$date = new DateTime();
		$date->setTimezone( new DateTimeZone( $this->time_zone ) );
		$this->date_info = $date->setDate( (int) $this->year, (int) $this->month, 1 );
		
		$this->num_days = $date->format('t');
		$this->day_of_week = $this->date_info->format('w');
		
		// Days of week translated
		$this->days_of_week = ga_get_form_translated_data($this->form_id, 'weeks');
		
		// Week starts on
		$calendar = get_option('ga_appointments_calendar');
		$this->week_starts = isset( $calendar['week_starts'] ) ? $calendar['week_starts'] : 'sunday';
	}

	/**
	 * Get Available Days from Schedule
	 */		
	private function get_available_days($array, $timestamp) {	
		// SERVICE PERIOD TYPE
		$period_type = (string) get_post_meta($this->service_id, 'ga_service_period_type', true);

		if( $period_type == 'date_range' ) {
			$range = (array) get_post_meta($this->service_id, 'ga_service_date_range', true);

			$dates = array();
			if( isset($range['from']) && ga_valid_date_format($range['from']) && isset($range['to']) && ga_valid_date_format($range['to']) ) {
				$period = new DatePeriod(
				    new DateTime($range['from']),
				    new DateInterval('P1D'),
				    new DateTime($range['to'])
				);
				foreach ($period as $key => $value) {
				    $dates[] = $value->format('Y-m-j');
				}

				 $dates[] = $range['to'];
			}
			return $dates;
		}

		if( $period_type == 'custom_dates' ) {
			$custom_dates = (array) get_post_meta($this->service_id, 'ga_service_custom_dates', true);
			return $custom_dates;
		}


		$array = (array) $array;
		$weeks = array('sunday' ,'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
		$dates = array();
		
		foreach( $array as $week ) {
			if( in_array($week, $weeks) ) {
				$date = new DateTime();
				$date->setTimezone( new DateTimeZone( $this->time_zone ) );				
				$date->setTimestamp($timestamp);
				$date->modify("first $week of this month");
				$thisMonth = $date->format('m');
				while ($date->format('m') == $thisMonth) {
					$dates[] = $date->format('Y-m-j');
					$date->modify("next $week");
				}
			}
		}
		return $dates;	
	}	
	
	/**
	 * Get Date Timestamp
	 */		
	private function get_date_timestamp($year, $month, $day = false) {
		$date = new DateTime();
		$date->setTimezone( new DateTimeZone( $this->time_zone ) );		
		$day = $day ? $day : 1;
		$date->setDate( (int) $year, (int) $month, $day );
		return $date->getTimestamp();
	}
	
	/**
	 * Display Calendar
	 */		
	public function show() {
		// Calendar caption header
		$output = '<div class="ga_appointments_calendar_header">' . PHP_EOL;
		
		// Previous Month
		if( $this->previous_month() ) {
			$output .= '<a class="arrow-left" date-go="'. $this->date_info->format('Y-m') .'" service_id="'.$this->service_id.'" provider_id="'.$this->provider_id.'" id="ga_calendar_prev_month"><i class="fa fa-caret-left" aria-hidden="true"></i></a>' . PHP_EOL;
		}
		
		// Translation: Month/Year Caption
		$month = $this->date_info->format('F');
		$year  = $this->date_info->format('Y');
		$lang  = ga_get_form_translated_month( $this->form_id, $month, $year );
		
		$output .= '<h3>'. $lang .'</h3>' . PHP_EOL;
		
		// Next Month		
		if( $this->next_month() ) {
			$output .= '<a class="arrow-right" date-go="'. $this->date_info->format('Y-m') .'" service_id="'.$this->service_id.'" provider_id="'.$this->provider_id.'" id="ga_calendar_next_month"><i class="fa fa-caret-right" aria-hidden="true"></i></a>' . PHP_EOL;			
		}
		
		$output .= '</div>' . PHP_EOL;
		
		// Table start
		$output .= '<table class="table_fixed" width="100%">' . PHP_EOL;
		$output .= '<thead>' . PHP_EOL;
		$output .= '<tr>' . PHP_EOL;
		
		// Week starts on monday
		if( $this->week_starts == 'monday' ) {
			$sunday = $this->days_of_week['sun'];
			unset( $this->days_of_week['sun'] );
			$this->days_of_week['sun'] = $sunday;
			$this->day_of_week = $this->day_of_week - 1;
			if ($this->day_of_week < 0) {
				$this->day_of_week = 6;
			}
		}		
		
		// Days of the week header
		foreach( $this->days_of_week as $day ) {
			$output .= '<th class="ga_header">' . $day . '</th>' . PHP_EOL;
		}
		
		// Close header row and open first row of days
		$output .= '</tr>' . PHP_EOL;
		$output .= '</thead>' . PHP_EOL;		
		
		$output .= '<tbody id="service-working-days">' . PHP_EOL;
		$output .= '<tr>' . PHP_EOL;
		
		// If first day of a month does not fall on a Sunday, then we need to fill
		if( $this->day_of_week > 0 ) {
			$output .= str_repeat( '<td class="ga_day_past"></td>', $this->day_of_week );
		}
		
		// start num_days counter
		$current_day = 1;
		
		// available days
		$available_days = $this->get_available_days( array_keys( $this->get_week_day_schedule() ), $this->get_date_timestamp($this->year, $this->month) );

		if( $this->month_passed() ) {
			$available_days = array();
		}
			
		$service_price = $this->service_price(); // false for array

		// Loop and build days
		while( $current_day <= $this->num_days ) {
			// Reset 'days of week' counter and close each row if end of row

			$current_date = new DateTime( $this->date_info->format("Y-m-$current_day"), new DateTimeZone( $this->time_zone ) );
			$slots_booked = count( $this->get_slots( $current_date ) ) > 0 ? false : true;			
			$classes = in_array($current_date->format('Y-m-j'), $available_days) 
						&& !in_array( $current_date->format("Y-m-j"), $this->get_holidays() )
						&& !$slots_booked 
						&& !$this->date_passed($current_date)
						&& !$this->max_schedule_days( $current_date )
						? 'day_available ga_time_slots' : 'day_unavailable ga_time_slots';			

						
			
			// Date selected from form submission
			$selected = '';
			if( $this->selected_date && in_array($this->selected_date->format('Y-m-j'), $available_days) )  {
				// Add selected class
				if( $this->selected_date->format('Y-m-j') == $current_date->format('Y-m-j') )  {
					$selected = ' selected';
					$classes .= $selected;
				}			
				
				// Date Slots Mode
				if( $this->available_times_mode() == 'no_slots' )  {
					# do nothing
				} else {
					// Time Slots Mode
					$placed_it = clone $this->selected_date;
					$placed_it->modify("next {$this->week_starts}");

					if( $current_date->format('Y-m-j') == $placed_it->format('Y-m-j') && $this->day_of_week = 7 ) {
						$output  .= '</tr><tr id="gappointments_calendar_slots"><td colspan="7" class="calendar_slots"><div class="calendar_time_slots">';
						$output  .= $this->calendar_time_slots($this->selected_date, $this->selected_slot);
						$output  .= '</div></td>';
					}					
				}
			}
			// Date selected from form submission

			// Reset 'days of week' counter and close each row if end of row
			if( $this->day_of_week == 7 ) {
				$output .= '</tr><tr>' . PHP_EOL;
				$this->day_of_week = 0;				
			}
			
			// Current date is today
			if( $this->is_today( $current_date ) ) {
				$today    = ' ga_today';
				$classes .= $today;
			} else {
				$today = '';
			}
			
			// Date Slots Mode	
			if( $this->available_times_mode() == 'no_slots' )  {
				
				if( $this->is_date_available($current_date) && !$this->max_schedule_days($current_date) ) {
					// Translation Support
					$month = $current_date->format("F");
					$day   = $current_date->format("j");
					$year  = $current_date->format("Y");		
					$translate = ga_get_form_translated_slots_date( $this->form_id,  $month, $day, $year );
					$lang_slot = ' lang_slot="'.$translate.'"';
					// Translation Support						
					
					// Date is available
					$classes = "day_available{$today} ga_date_slots{$selected}";
					
					// Multiple Slots selection
					$multi_select = (string) get_post_meta($this->service_id, 'ga_service_multiple_selection', true);
					$max_bookings = ga_get_service_max_bookings($this->service_id);
					$max_total    = ga_get_service_max_selection($this->service_id);
					$double       = ga_get_service_double_bookings($this->service_id);
					$multiple     = $multi_select == 'yes' ? ' multi-select="enabled" select-total="'.$max_total.'"  no_double="'.$double.'"' : '';

					// Capacity available
					if( $count = $this->date_capacity_text($current_date) ) {
						$capacity = $count == 1 ? ga_get_form_translated_space($this->form_id, $count) : ga_get_form_translated_spaces($this->form_id, $count);
						$classes .= ' ga_tooltip';
						
						$output .= '<td class="'.$classes.'" ga-tooltip="'.$capacity.'" date-go="'.$this->date_info->format("Y-m-$current_day").'" service_cost="'.$service_price.'" service_id="'.$this->service_id.'" provider_id="'.$this->provider_id.'"'. $multiple . $lang_slot . 'capacity="'.$count.'"><span>'. $current_day .'</span></td>' . PHP_EOL;
						
					} else {
						$output .= '<td class="'.$classes.'" date-go="'.$this->date_info->format("Y-m-$current_day").'" service_cost="'.$service_price.'" service_id="'.$this->service_id.'" provider_id="'.$this->provider_id.'"'. $multiple . $lang_slot .' capacity="1"><span>'. $current_day .'</span></td>' . PHP_EOL;
						
					}					
					
					
				} else {
					// Date not available
					$classes = "day_unavailable{$today} ga_date_slots";
					$output .= '<td class="'.$classes.'" date-go="'.$this->date_info->format("Y-m-$current_day").'" service_cost="'.$service_price.'"><span>'. $current_day .'</span></td>' . PHP_EOL;							
				}

			} else {		
				// Time Slots Mode
				$output .= '<td class="'.$classes.'" date-go="'.$this->date_info->format("Y-m-$current_day").'" service_id="'.$this->service_id.'" provider_id="'.$this->provider_id.'" service_cost="'.$service_price.'"><span>'. $current_day .'</span></td>' . PHP_EOL;				
			
			}			
			
			// Increment counters
			$current_day++;
			$this->day_of_week++;
		}
		
		
		// Once num_days counter stops, if day of week counter is not 7, then we
		// need to fill remaining space on the row using colspan	
		if( $this->day_of_week != 7 ) {
			$remaining_days = 7 - $this->day_of_week;
			$output .= str_repeat( '<td class="ga_day_future"></td>', $remaining_days );	
		}

		// Date selected from form submission 
		// Place at the end
		if( $this->available_times_mode() == 'no_slots' )  {
			# do nothing
		} else {
			if( $this->selected_date && in_array($this->selected_date->format('Y-m-j'), $available_days) )  {
				// Last day of month from selected date
				$month_end = clone $this->selected_date;
				$month_end->modify("last day of this month");
							
				// Next sunday from selected date
				$placed_end = clone $this->selected_date;
				$placed_end->modify("next {$this->week_starts}");
				
				//var_dump( $placed_end > $month_end );
				
				if( $placed_end > $month_end ) {
					$output  .= '</tr><tr id="gappointments_calendar_slots"><td colspan="7" class="calendar_slots"><div class="calendar_time_slots">';
					$output  .= $this->calendar_time_slots($this->selected_date, $this->selected_slot);
					$output  .= '</div></td>';
				}
			}			
		}
		// Date selected from form submission 
		
		// Close final row and table
		$output .= '</tr>' . PHP_EOL;
		
		$output .= '</tbody>' . PHP_EOL;
		$output .= '</table>' . PHP_EOL;

		return $output;
		//echo $output;
	}

	/**
	 * Slot Available Query
	 */		
	public function slot_availability_query( $dateTime, $slot ) {
		$slot_start = $slot['start'];
	
		if( $this->schedule_lead_time( $dateTime, $slot_start ) ) {
			# valid time & date
		} else {
			return false;
		}

		$date     = $dateTime->format("Y-m-j");
		$slot_end = ga_get_time_end($slot_start, $this->service_id);

		global $wpdb;

		$querystr = "
			SELECT $wpdb->posts.ID
			FROM
			   $wpdb->posts,
			   $wpdb->postmeta AS app_date,
			   $wpdb->postmeta AS provider,
			   $wpdb->postmeta AS time1,
			   $wpdb->postmeta AS time2

			WHERE
			   $wpdb->posts.ID = app_date.post_id
			AND
			   $wpdb->posts.ID = provider.post_id
			AND
			   $wpdb->posts.ID = time1.post_id
			AND
			   $wpdb->posts.ID = time2.post_id
			  
			AND $wpdb->posts.post_type = 'ga_appointments'
			AND $wpdb->posts.post_status IN ('completed', 'publish', 'payment', 'pending')		
			  
			AND app_date.meta_key   = 'ga_appointment_date'
			AND app_date.meta_value = %s

			AND provider.meta_key   = 'ga_appointment_provider'
			AND provider.meta_value = %s

			AND time1.meta_key = 'ga_appointment_time_end'
			AND time1.meta_value > %s

			AND time2.meta_key = 'ga_appointment_time'
			AND time2.meta_value < %s
		"; 


	    $sql_prepare  = $wpdb->prepare($querystr, $date, $this->provider_id, $slot_start, $slot_end);
 		$appointments = $wpdb->get_results( $sql_prepare, ARRAY_A );

		//var_dump( count($appointments) );

		return count($appointments);
	}	
	
	/**
	 * Is slot available
	 */		
	public function is_slot_available( $dateTime, $slot ) {
		$appointments = $this->slot_availability_query( $dateTime, $slot );

		if( $appointments === false ) {
			return false;
		}			

		if( $appointments == 0 ) {
			return true;
		}
		
		if( $this->available_times_mode() == 'custom' ) {
			$capacity = $slot['capacity'];
		} else {
			$capacity = $this->service_capacity();
		}

		if ( $appointments >= $capacity ) {
			return false;
		}
		return true;
	}
	
	
	/**
	 * Capacity Available Text
	 */		
	public function slot_capacity_text( $dateTime, $slot ) {
		$appointments = $this->slot_availability_query( $dateTime, $slot );

		if( $appointments === false ) {
			return false;
		}

		if( $this->available_times_mode() == 'custom' ) {
			$capacity = $slot['capacity'];
		} else {
			$capacity = $this->service_capacity();
		}
		
		if( $appointments > 0 && $appointments < $capacity ) {
			return ($capacity - $appointments);
		} elseif( $appointments == 0 && $capacity > 1 ) {
			return $capacity;
		}

		return false;
	}	
	
	/**
	 * Date Available Query
	 */		
	public function date_availability_query( $dateTime ) {
		if( $this->schedule_lead_time_days( $dateTime ) ) {
			# valid date
		} else {
			return false;
		}		
		
		$date = $dateTime->format("Y-m-j");
		
		$appointments = new WP_QUERY(
			array(
				'post_type'         => 'ga_appointments',
				'posts_per_page'    => -1,				
				'post_status'       => array( 'completed', 'publish', 'payment', 'pending' ),
				'orderby'           => 'meta_value',
				'order'             => 'ASC',					
				'meta_query'        => array( 'relation' => 'AND',
					array( 'key'    => 'ga_appointment_date', 'value' => $date ),
					array( 'key'    => 'ga_appointment_provider', 'value' => $this->provider_id ),
				),
			)	
		);		
		return $appointments;
	}		
	
	/**
	 * Date Available
	 */		
	public function is_date_available( $dateTime ) { 
		// Week day shedule is not out
		$week_day = (string) strtolower( $dateTime->format('l') );	
		$schedule = $this->get_week_day_schedule();
		if( isset($schedule[$week_day]) ) {
			$schedule = $schedule[$week_day];
		} else {
			return false;
		} 		
		
		$appointments = $this->date_availability_query( $dateTime );

		if( !$appointments || $this->date_slot_passed($dateTime) ) {
			return false;
		}

		$available_days = $this->get_available_days( array_keys( $this->get_week_day_schedule() ), $this->get_date_timestamp($this->year, $this->month) );

		if( $this->month_passed() ) {
			$available_days = array();
		}		
		
		if( in_array($dateTime->format('Y-m-j'), $available_days) && !in_array($dateTime->format("Y-m-j"), $this->get_holidays()) ) {
			# valid 
		} else {
			return false;
		}
				
		$capacity = $this->service_capacity();
		
		if ( $appointments->have_posts() && $capacity <= $appointments->post_count ) {
			return false;
		}			
		
		return true;
	}
	
	
	/**
	 * Date Capacity Text
	 */		
	public function date_capacity_text( $dateTime ) {
		$appointments = $this->date_availability_query( $dateTime );

		if( !$appointments ) {
			return false;
		}		
		
		$capacity = $this->service_capacity();
		
		if( $appointments->post_count > 0 && $appointments->post_count < $capacity ) {
			return ($capacity - $appointments->post_count);
		} elseif( $appointments->post_count == 0 && $capacity > 1 ) {
			return $capacity;
		}

		return false;
	}		
	
		
	/**
	 * New appointment lead time require for new appointments
	 * @ returns true if valid
	 */		
	public function schedule_lead_time( $dateTime, $slot_start ) {
		// Lead time minutes
		$service_lead_time  = get_post_meta($this->service_id, 'ga_service_schedule_lead_time_minutes', true);
		$lead_time          = $service_lead_time && array_key_exists( $service_lead_time, ga_schedule_lead_time_minutes() ) ? $service_lead_time : '240';
		
		// Today's date
		$today = ga_current_date_with_timezone();
		
		//var_dump( $lead_time );		
		
		// Slot time with date
		$slot_time = new DateTime( $slot_start, new DateTimeZone( $this->time_zone ) );	
		$slot_time->setDate( $dateTime->format("Y"), $dateTime->format("m"), $dateTime->format("j") );		
		
		// If no lead time, is slot dateTime less or equal then today
		if( $lead_time == 'no' ) {
			if( $slot_time <= $today ) {
				return false;
			}
			return true;
		}
		
		$lead = $today;
		$lead = $lead->modify("+{$lead_time} minutes");
		
		//print_r( "Start: {$slot_time->format('Y-m-j H:i')} | End: {$lead->format('Y-m-j H:i')}" . '<br>');
		
		if( $slot_time <= $lead ) {
			//echo "Not valid: {$slot_time->format('Y-m-j H:i')} | Lead end: {$lead->format('Y-m-j H:i')}<br>";
			return false;
		}
		
		return true;
	}


	/**
	 * New appointment lead time require for bookable dates
	 * @ returns true if valid
	 */		
	public function schedule_lead_time_days( $dateTime ) {
		// Lead time minutes
		$service_lead_time  = get_post_meta($this->service_id, 'ga_service_schedule_lead_time_minutes', true);
		$lead_time          = $service_lead_time && array_key_exists( $service_lead_time, ga_schedule_lead_time_minutes() ) ? $service_lead_time : '240';
		
		// Today's date
		$today = ga_current_date_with_timezone();

		//var_dump( $lead_time );
		
		// If no lead time, is slot dateTime less or equal then today
		if( $lead_time == 'no' ) {
			$dateTime->setTime(23, 59);
			if( $dateTime <= $today ) {
				return false;
			}
			return true;
		}
		
		$lead = $today;
		$lead = $lead->modify("+{$lead_time} minutes");
		
		if( $dateTime <= $lead ) {
			//echo "Not valid: {$slot_time->format('Y-m-j H:i')} | Lead end: {$lead->format('Y-m-j H:i')}<br>";
			return false;
		}
		
		return true;
	}	
	

	/**
	 * Display Slots
	 */		
	public function get_slots( $date ) {
		switch ( $this->available_times_mode() ) {
			case 'interval':
				return $this->generate_time_slots( $date );
			case 'custom':
				return $this->get_custom_slots( $date );
			case 'no_slots':
				return array();
			default:
			   return array();
		}
	}
	
	/**
	 * Get Date Schedule
	 */		
	public function get_date_schedule( $date ) {
		$week_day = (string) strtolower( $date->format('l') );
		$schedule = $this->get_week_day_schedule();
		
		$available_days = $this->get_available_days( array_keys( $this->get_week_day_schedule() ), $this->get_date_timestamp($this->year, $this->month) );
		if( !in_array($date->format('Y-m-j'), $available_days) || $this->date_passed($date) || $this->max_schedule_days($date) ) {
			return false;
		}

		if( isset($schedule[$week_day]) ) {
			return $schedule[$week_day];
		} else {
			return false;
		}		
	}
	
	
	/**
	 * Custom Time Slots
	 */		
	public function get_custom_slots( $date ) {
		$week_day = (string) strtolower( $date->format('l') );
	
		if( !is_array( $this->custom_slots() ) || $this->get_date_schedule( $date ) === false ) {
			return array();
		}
		
		$slots = array();
		foreach( $this->custom_slots() as $slot_id => $slot ) {
			if( isset($slot['availability']) && in_array($week_day, $slot['availability']) ) {
				if( $this->is_slot_available( $date, $slot ) ) {
					$startTime = new DateTime( $slot['start'] );
					$endTime   = new DateTime( $slot['end'] );
					
					$slots[$slot['start']]             = $slot;
					$slots[$slot['start']]['text']     = $this->slot_text( $startTime, $endTime ); 	
					$slots[$slot['start']]['duration'] = $this->get_slot_duration( $startTime, $endTime ); 						
				}
				
				//echo '<pre>'; print_r( $slots ); echo '</pre>';	
				//die();
			}
		}
		//echo '<pre>'; print_r( $slots ); echo '</pre>';	
		return $slots;
	}

	public function get_slot_duration( $startTime, $endTime ) {
		$diff    = $startTime->diff( $endTime );
		$minutes = ($diff->days * 24 * 60) + ($diff->h * 60) + $diff->i;
		return $minutes;
	}	
	
	/**
	 * Generate Time Slots
	 */		
	public function generate_time_slots( $date ) {
		$week_day = (string) strtolower( $date->format('l') );
		
		if( $this->get_date_schedule( $date ) === false  ) {
			return array();
		} else {
			$schedule = $this->get_date_schedule( $date );
		}

		$duration        = (int) get_post_meta($this->service_id, 'ga_service_duration', true);
		$cleanup         = (int) get_post_meta($this->service_id, 'ga_service_cleanup', true);
		$start           = (string) $schedule['begin'];
		$end             = (string) $schedule['end'];
		
		$start           = new DateTime($start);
		$end             = new DateTime($end);
		$interval        = new DateInterval("PT" . $duration . "M");
		$cleanupInterval = new DateInterval("PT" . $cleanup . "M");
		$slots           = array();
		$exclude_slots   = array();
		
		for ($intStart = $start; $intStart < $end; $intStart->add($interval)->add($cleanupInterval)) {
			$endPeriod = clone $intStart;
			$endPeriod->add($interval);
			
			if( $endPeriod > $end ) {
				break;
			}
			
			if( $this->get_breaks( $week_day ) ) {
				foreach( $this->get_breaks( $week_day ) as $break_start => $break_end ) {
					$break_start = new DateTime( $break_start );
					$break_end   = new DateTime( $break_end );
					
					if( $intStart >= $break_end || $endPeriod <= $break_start ) {					
						// Slot available
						if( $this->is_slot_available( $date, $this->generate_slot_data($intStart, $endPeriod) ) ) {
							$slots[$intStart->format('H:i')] = $this->generate_slot_data($intStart, $endPeriod);
							
						}											
					} else {
						$exclude_slots[$intStart->format('H:i')] = $this->generate_slot_data($intStart, $endPeriod);
						
						if( $this->reduce_gaps() ) {
							$intStart = $break_end;
							$resetInterval = new DateInterval("PT0M");
							$intStart->add($resetInterval)->add($cleanupInterval);						
							$endPeriod = clone $intStart;
							$endPeriod->add($interval);
							
							// Reset interval greater than end time
							if( $endPeriod > $end ) {
								break;
							}
					
							// Slot available
							if( $this->is_slot_available($date, $this->generate_slot_data($intStart, $endPeriod)) ) {
								$slots[$intStart->format('H:i')] = $this->generate_slot_data($intStart, $endPeriod);
							}
						}						
					}
				} // endforeach
			} else {	
				// Slot available
				if( $this->is_slot_available($date, $this->generate_slot_data($intStart, $endPeriod)) ) {		
					$slots[$intStart->format('H:i')] = $this->generate_slot_data($intStart, $endPeriod);
				}			
			}	
		}
		
		//echo '<pre>'; print_r( $slots ); echo '</pre>';	
		
		//$slots         = array_unique($slots);
		//$exclude_slots = array_unique($exclude_slots);
		return array_diff_key($slots, $exclude_slots);
	}
	
	private function generate_slot_data( $start, $end ) {
		$slot = array();
		$slot['start']  = $start->format('H:i');		
		$slot['end']    = $end->format('H:i');
		$slot['text']   = $this->slot_text($start, $end);
		return $slot;
	}
	
	/**
	 * Schedule Available Week Days
	 */		
	public function get_week_day_schedule() {
		$provider_schedule = $this->get_calendar_schedule();
			
		if( $this->provider_id == 0 ) {
			# do nothing
		} else {
			$provider_calendar = (string) get_post_meta( $this->provider_id, 'ga_provider_calendar', true );			
			if( $provider_calendar == 'on' ) {
				$provider_schedule = (array) get_post_meta( $this->provider_id, 'ga_provider_work_schedule', true );				
			}
		}
	
		foreach( $provider_schedule as $key => $schedule ) {
			if( $schedule['begin'] == 'out' ) {
				unset( $provider_schedule[$key] );
			}		
		}				

		return $provider_schedule;
	}
	
	/**
	 * Schedule Available Breaks
	 */	
	private function get_breaks( $week_day ) {
		$sort     = array();
		$breaks = $this->get_calendar_breaks();		
		
		if( $this->provider_id == 0 ) {
			# do nothing
		} else {
			$provider_calendar = (string) get_post_meta( $this->provider_id, 'ga_provider_calendar', true );			
			if( $provider_calendar == 'on' ) {
				$breaks = (array) get_post_meta( $this->provider_id, 'ga_provider_breaks', true );				
			}
		}		

		if( isset( $breaks[$week_day] ) && count($breaks[$week_day]) > 0 ) {
			$breaks = $breaks[$week_day];
		} else {
			return array();
		}
		
		foreach($breaks as $key => $part) {
			$sort[$key] = new DateTime($part);
		}
		  

		array_multisort($sort, SORT_ASC, $breaks);
		
		//$print = '<pre>' . print_r($breaks, true);
		//wp_die( $print );
		
		return $breaks;	
		
	}

	/**
	 * Holidays
	 */		
	public function get_holidays() {
		$provider_holidays = $this->get_calendar_holidays();
			
		if( $this->provider_id == 0 ) {
			# do nothing
		} else {
			$provider_calendar = (string) get_post_meta( $this->provider_id, 'ga_provider_calendar', true );			
			if( $provider_calendar == 'on' ) {
				$provider_holidays = (array) get_post_meta( $this->provider_id, 'ga_provider_holidays', true );				
			}
		}
		
		return $provider_holidays;
	}	
	
	private function get_calendar_schedule() {
		$options = get_option( 'ga_appointments_work_schedule' );
		$work_schedule = $options && is_array($options) ? $options : $this->get_calendar_defauls();
		return $work_schedule;
	}
	
	private function get_calendar_breaks() {	
		$options = get_option( 'ga_appointments_schedule_breaks' );
		$breaks = (array) $options;	
		return $breaks;
	}

	private function get_calendar_holidays() {
		$options  = get_option( 'ga_appointments_holidays' );
		$holidays = (array) $options;
		return $holidays;
	}	
	
	/**
	 * Default Schedule		
	 */			
	private function get_calendar_defauls() {
		$schedule = array( 'sunday'    => array('begin' => 'out',   'end' => 'out'), 
						   'monday'    => array('begin' => '09:00', 'end' => '17:00'),
						   'tuesday'   => array('begin' => '09:00', 'end' => '17:00'),
						   'wednesday' => array('begin' => '09:00', 'end' => '17:00'),
						   'thursday'  => array('begin' => '09:00', 'end' => '17:00'),
						   'friday'    => array('begin' => 'out',   'end' => 'out'),
						   'saturday'  => array('begin' => 'out',   'end' => 'out'),						   
		);
		
		return $schedule;
	}

	/**
	 * Reduce gaps		
	 */		
	private function reduce_gaps() {
		$reduce_gaps = get_post_meta( $this->service_id, 'ga_service_reduce_gaps', true );
		
		// Reduce Gaps
		if( $reduce_gaps && in_array($reduce_gaps, array('yes', 'no')) ) {
			$gaps = $reduce_gaps;
		} else {
			$gaps = 'yes'; // days
		}
		
		if( $gaps == 'yes' ) {
			return true;
		}
		
		return false;
	}	
	
	/**
	 * Prior Days To Book Appointment
	 */		
	private function max_schedule_days( $date_input ) {
		$max_days = get_post_meta( $this->service_id, 'ga_service_schedule_max_future_days', true );
		$max_days = $max_days && array_key_exists( $max_days, ga_schedule_max_future_days() ) ? $max_days : 90;		
		
		
		// SERVICE PERIOD TYPE
		$period_type = (string) get_post_meta($this->service_id, 'ga_service_period_type', true);

		if( $period_type == 'date_range' ) {
			$range = (array) get_post_meta($this->service_id, 'ga_service_date_range', true);
			if( isset($range['from']) && ga_valid_date_format($range['from']) && isset($range['to']) && ga_valid_date_format($range['to']) ) {
				$end_range = new DateTime($range['to'], new DateTimeZone( $this->time_zone ));
				$end_range->setTime(24,00);
				//print_r( $end_range );
				return $date_input > $end_range;
			}
			return true;
		}

		if( $period_type == 'custom_dates' ) {
			$custom_dates = (array) get_post_meta($this->service_id, 'ga_service_custom_dates', true);
			if( is_array($custom_dates) && count($custom_dates) > 0 && ga_valid_date_format(end($custom_dates)) ) {
				$end_custom_date = new DateTime(end($custom_dates), new DateTimeZone( $this->time_zone ));
				$end_custom_date->setTime(24,00); 
				return $date_input > $end_custom_date;
			}

			return true;
		}


		// Future Days Period
		$date = new DateTime();
		$date->setTimezone( new DateTimeZone( $this->time_zone ) );
		$date->add(new DateInterval( "P" . $max_days . "D" ));
		$date->setTime(00, 00);

		$calendar = $date_input; // DateTime Object
		$calendar->setTimezone( new DateTimeZone( $this->time_zone ) );			
		$calendar->setTime(00, 00);
		
		return $calendar > $date;

	}
	
	
	public function calendar_time_slots($date, $sel_slot = false) {
		$ga_slots = $this->get_slots( $date );

		// Translation Support
		$month = $date->format("F");
		$week  = $date->format("l");
		$day   = $date->format("j");
		$year  = $date->format("Y");
		$text  = ga_get_form_translated_slots_date($this->form_id, $month, $day, $year);
		// Translation Support
		
		// Slot size	
		$slots_size = $this->show_end_times() ? 'slot_large grid-lg-12 grid-md-12 grid-sm-12 grid-xs-12' : 'slot_small grid-lg-3 grid-md-3 grid-sm-3 grid-xs-6';
			
		// Time Format Display
		$time_display  = ga_service_time_format_display($this->service_id);	
			
		// Multiple Slots selection
		$multi_select  = (string) get_post_meta($this->service_id, 'ga_service_multiple_selection', true);
		$max_bookings  = ga_get_service_max_bookings($this->service_id);
		$max_total     = ga_get_service_max_selection($this->service_id);
		$double        = ga_get_service_double_bookings($this->service_id);
		$time_format   = (string) get_post_meta($this->service_id, 'ga_service_time_format', true);
		$remove_am_pm  = (string) get_post_meta($this->service_id, 'ga_service_remove_am_pm', true);
		$multiple      = $multi_select == 'yes' ? ' multi-select="enabled" select-max="'.$max_bookings.'" select-total="'.$max_total.'" time_format="'.$time_format.'" remove_am_pm="'.$remove_am_pm.'" no_double="'.$double.'"' : '';

		
		
		$out = '';
		if( count($ga_slots) > 0  ) {
			$out .= '<h3 class="slots-title">' .$text. '</h3>'; // eg. February 24, 2018
			$out .= '<div class="grid-row grid_no_pad">';	
				foreach( $ga_slots as $slot ) {
					$sel_class = $sel_slot && $sel_slot == $slot['start'] ? ' time_selected' : '';
					$slot_cost  = $this->available_times_mode() == 'custom' ? $slot['price'] : $this->service_price();
					
					// Slot Language
					if( $multi_select == 'yes' ) { 
						$time      = new DateTime($slot['start'], new DateTimeZone( $this->time_zone ));
						$slot_time = $time->format($time_display);
						$translate = ga_get_form_translated_date_time( $this->form_id, $month, $week, $day, $year, $slot_time );
						$lang_slot = ' lang_slot="'. esc_html($translate) .'"';
					} else {
						$lang_slot = '';
					}
					// Slot Language
					
					if( $this->slot_capacity_text( $date, $slot ) ) {
						$count    = $this->slot_capacity_text( $date, $slot );
						$capacity = $count == 1 ? ga_get_form_translated_space($this->form_id, $count) : ga_get_form_translated_spaces($this->form_id, $count);
						$out .= '<div class="'.$slots_size.' grid_no_pad">
								<label class="time_slot ga_tooltip'.$sel_class.'" time_slot="'.$slot['start'].'" ga-tooltip="'.$capacity.'"'.$multiple . $lang_slot.' capacity="'.$count.'" service_id="'.$this->service_id.'" slot_cost="'.$slot_cost.'"><div>'.$slot['text'].'</div></label>
							</div>';	
						
					} else {
						$out .= '<div class="'.$slots_size.' grid_no_pad">
								<label class="time_slot'.$sel_class.'" time_slot="'.$slot['start'].'" '.$multiple . $lang_slot.' capacity="1" service_id="'.$this->service_id.'" slot_cost="'.$slot_cost.'"><div>'.$slot['text'].'</div></label>
							</div>';						
					}
				}				
			$out .= '</div>';			
		 } else {
			$out .= '<div id="no_time_slots"><i class="fa fa-calendar-times-o" aria-hidden="true"></i><div>No time slots</div></div>';		
		}

		return $out;
		
	}	
	
	
	/**
	 * Slot Text
	 */	
	private function slot_text($intStart, $endPeriod) {
		$remove_am_pm = $this->remove_am_pm() == 'no' ? 'A' : '';
		$time_format  = $this->time_format() == '24h' ? "G:i {$remove_am_pm}" : "g:i {$remove_am_pm}";
		
		$start = ga_get_form_translated_am_pm($this->form_id, $intStart->format($time_format));
		$end   = ga_get_form_translated_am_pm($this->form_id, $endPeriod->format($time_format));
		
		if( $this->show_end_times()  ) {
			return $start . ' - ' . $end;
		} else {
			return $start;
		}
			
	}
	
	/**
	 * Show End Times
	 */
	public function show_end_times() {
		$show_end_times = get_post_meta( $this->service_id, 'ga_service_show_end_times', true );
			
		// Show End Times
		if( $show_end_times && in_array($show_end_times, array('yes', 'no')) ) {
			$end_times = $show_end_times;
		} else {
			$end_times = 'no'; // days
		}			
		
		if( $end_times == 'yes'  ) {
			return true;
		}
		
		return false;
		
	}
	
	/**
	 * Time Format
	 */
	public function time_format() {
		$time_format = get_post_meta( $this->service_id, 'ga_service_time_format', true );
		
		// Time Format
		if( $time_format && in_array($time_format, array('12h', '24h')) ) {
			$format = $time_format;
		} else {
			$format = '12h'; // 12h format
		}			

		
		return $format;
		
	}	
	
	/**
	 * Time Format
	 */
	public function remove_am_pm() {
		$remove_am_pm = get_post_meta( $this->service_id, 'ga_service_remove_am_pm', true );

		// Time Format
		if( $remove_am_pm && in_array($remove_am_pm, array('no', 'yes')) ) {
			$remove = $remove_am_pm;
		} else {
			$remove = 'no'; // 12h format
		}			
		return $remove;
	}


	/**
	 * Current Date Today
	 */		
	private function is_today( $current_date ) {
		$today = ga_current_date_with_timezone();
		return $today->format('Y-m-j') == $current_date->format('Y-m-j');
	}

	
	/**
	* Date Passed
	*/
	private function date_passed($dateTime) {
		$day = $dateTime->format('j');
		$now = ga_current_date_with_timezone();
		
		$calendar = new DateTime();
		$calendar->setTimezone( new DateTimeZone( $this->time_zone ) );
		$calendar->setDate( (int) $this->year, (int) $this->month, $day );
		
		return $now > $calendar;
	} 	

	/**
	* Date Slot Passed
	*/
	private function date_slot_passed($dateTime) {
		$now = ga_current_date_with_timezone();
		return $now > $dateTime;
	} 	
	
	/**
	* Month Passed
	*/	
	private function month_passed() {
		$month = new DateTime();
		$month->setTimezone( new DateTimeZone( $this->time_zone ) );
		$month->setDate( $month->format('Y'), $month->format('n'), 1 );
		
		$now = ga_current_date_with_timezone();;
		
		return $month > $now;
	}
	
	/**
	* Previous Month
	*/	
	private function previous_month() {
		$previous = new DateTime();
		$previous->setTimezone( new DateTimeZone( $this->time_zone ) );
		$previous->setDate( $this->date_info->format('Y'), $this->date_info->format('n'), 1 );
		$previous->modify( 'last day of previous month' );
		
		
		// SERVICE PERIOD TYPE
		$period_type = (string) get_post_meta($this->service_id, 'ga_service_period_type', true);

		if( $period_type == 'date_range' ) {
			$range = (array) get_post_meta($this->service_id, 'ga_service_date_range', true);
			if( isset($range['from']) && ga_valid_date_format($range['from']) && isset($range['to']) && ga_valid_date_format($range['to']) ) {
				$begin_range = new DateTime($range['from'], new DateTimeZone( $this->time_zone ));
				return $previous > $begin_range;
			}
			return false;
		}

		if( $period_type == 'custom_dates' ) {
			$custom_dates = (array) get_post_meta($this->service_id, 'ga_service_custom_dates', true);
			if( is_array($custom_dates) && count($custom_dates) > 0 && ga_valid_date_format(reset($custom_dates)) ) {
				$begin_custom_date = new DateTime(reset($custom_dates), new DateTimeZone( $this->time_zone ));
				return $previous > $begin_custom_date;
			}
			return false;
		}

		return $previous >= ga_current_date_with_timezone();
	}	
	
	
	/**
	* Next Month
	*/	
	private function next_month() {
		$next = new DateTime();
		$next->setTimezone( new DateTimeZone( $this->time_zone ) );
		$next->setDate( $this->date_info->format('Y'), $this->date_info->format('n'), 1 );
		$next->modify( 'first day of next month' );

		if( $this->max_schedule_days($next) ) {
			return false;
		}
		return true;
	}		
	
	/**
	* Month future
	*/		
	private function month_future() {
		$now = new DateTime();
		$now->setTimezone( new DateTimeZone( $this->time_zone ) );
		$now->setDate( $now->format('Y'), $now->format('n'), 1 );	
		return $now > $this->date_info;
	}	
	
	private function available_times_mode() {
		return (string) get_post_meta( $this->service_id, 'ga_service_available_times_mode', true );		
	}		
	
	private function service_capacity() {
		return (int) get_post_meta( $this->service_id, 'ga_service_capacity', true );
	}
	
	private function custom_slots() {
		return get_post_meta( $this->service_id, 'ga_service_custom_slots', true );
	}
	
	private function service_price() {
		return get_post_meta( $this->service_id, 'ga_service_price', true );
	}	
	
} // end class
gf-booking-services.php000066600000023125151262333040011126 0ustar00<?php
defined( 'ABSPATH' ) or exit; // Exit if accessed directly

if ( class_exists( 'GFForms' ) ) {
	class GF_Appointment_Booking_Services extends GF_Field {
		public $type = 'appointment_services';
		
		public function get_form_editor_field_title() {
			return esc_attr__( 'Booking Services', 'gravityforms' );
		}
			
		/*
		* Where to assign this widget
		*/
		public function get_form_editor_button() {
			return array(
				//'group' => 'advanced_fields',
				'group' => 'appointment_calendar',
				'text'  => $this->get_form_editor_field_title()
			);
		}	
		/*
		* Add button to the group
		*/
		public function add_button( $field_groups ) {
			$field_groups = $this->ga_appointment_services_gf_group( $field_groups );
			return parent::add_button( $field_groups );
		}
		/*
		* Add our group
		*/	
		public function ga_appointment_services_gf_group( $field_groups ) {
			foreach ( $field_groups as $field_group ) {
				if ( $field_group['name'] == 'appointment_calendar' ) {
					return $field_groups;
				}
			}		
			$field_groups[] = array(
				'name'   => 'appointment_calendar',
				'label'  => __( 'Appointment Booking', 'simplefieldaddon' ),
				'fields' => array(
				)
			);

			return $field_groups;
		}	
		
		/*
		* Widget settings
		*/			
		function get_form_editor_field_settings() {
			return array(
				'enable_enhanced_ui_setting',
				'label_setting',
				'error_message_setting',
				'label_placement_setting',
				'admin_label_setting',
				'size_setting',
				'description_setting',
				'css_class_setting',
				'rules_setting',	
				'conditional_logic_field_setting',
				'visibility_setting',				
			);
		}

		public function is_conditional_logic_supported() {
			return true;
		}

		/**
		 * Field Markup 
		 */			
		public function get_field_input( $form, $value = '', $entry = null ) {
			$form_id         = absint( $form['id'] );
			
			$is_entry_detail = $this->is_entry_detail();
			$is_form_editor  = $this->is_form_editor();
	
			$id       = $this->id;
			$field_id = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id";
			
			$logic_event        = $this->get_conditional_logic_event( 'change' );
			$size               = $this->size;
			$class_suffix       = $is_entry_detail ? '_admin' : '';
			$chosenUI           = $this->enableEnhancedUI ? ' chosen-select' : '';
			$field_class        = ' appointment_service_id';
			$class              = $size . $class_suffix . $chosenUI . $field_class;
			$css_class          = trim( esc_attr( $class ) . ' gfield_select' );
			$tabindex           = $this->get_tabindex();
			$disabled_text      = $is_form_editor ? 'disabled="disabled"' : '';
			$required_attribute = $this->isRequired ? 'aria-required="true"' : '';
			$invalid_attribute  = $this->failed_validation ? 'aria-invalid="true"' : 'aria-invalid="false"';
			
			$choices = '';

			if( $this->is_entry_edit() ) {
				$choices .= sprintf( "<div class='ginput_container ginput_container_select'><select name='input_%s' id='%s' $logic_event class='%s' $tabindex %s %s %s>%s</select></div>", $id, $field_id, $css_class, $disabled_text, $required_attribute, $invalid_attribute, $this->get_services_entry_choices($value, $form) );
			} elseif( !$this->is_form_editor() ) {
				$choices .= sprintf( "<div class='ginput_container ginput_container_select'><select name='input_%s' id='%s' $logic_event class='%s' $tabindex %s %s %s form_id='%d'>%s</select></div>", $id, $field_id, $css_class, $disabled_text, $required_attribute, $invalid_attribute, $form_id, $this->get_services_choices($value, $form) );
			}

			return $choices;
		}
		
		/**
		 * Is Entry Edit
		 */	
		public function is_entry_edit() {
			if ( rgget( 'page' ) == 'gf_entries' && rgget( 'view' ) == 'entry' && rgpost( 'screen_mode' ) == 'edit' ) {
				return true;
			}

			return false;
		}			

		
		/**
		 * Returns TRUE if the current page is the form editor page. Otherwise, returns FALSE
		 */		
		public function is_form_editor() {
			if ( rgget( 'page' ) == 'gf_edit_forms' && ! rgempty( 'id', $_GET ) && rgempty( 'view', $_GET ) ) {
				return true;
			}
			return false;
		}				
		
		/**
		 * Get Services Select Options
		 */			
		public function get_services_choices($value, $form) {
			$options = '';			
			
			$form_cat_slug = rgar($form, 'ga_service_category');
			$cat           = term_exists( $form_cat_slug, 'ga_service_cat' );

			// The Query
			if( $cat ) {
				$args = array('post_type' => 'ga_services', 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'date', 'order' => 'DESC', 'tax_query' => array( array(
						'taxonomy' => 'ga_service_cat', // taxonomy name 
						'field'    => 'slug',           // term_id, slug or name
						'terms'    => $form_cat_slug    // term id, term slug or term name 
					))
					); // end array
					
			} else {
				$args = array('post_type' => 'ga_services', 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'date', 'order' => 'DESC' );		
			}
			
			
			$the_query = new WP_Query( $args );
			wp_reset_postdata();
			// The Loop
			if ( $the_query->have_posts() ) {
				while ( $the_query->have_posts() ) {
					$the_query->the_post();
					$post = get_post( get_the_id() );
					$selected = $value == get_the_id() ? ' selected="selected"' : '';
					$options .= '<option value="'.get_the_id().'"'.$selected.'>'.$post->post_title.'</option>' . PHP_EOL;					
				}
				wp_reset_postdata();
			} else {
				// no services found
			}	
			
			return $options;
		}

		/**
		 * Get Services Entry Options
		 */			
		public function get_services_entry_choices($value, $form) {
			$options = '';			

			$args = array('post_type' => 'ga_services', 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'date', 'order' => 'DESC' );		

			$the_query = new WP_Query( $args );
			wp_reset_postdata();

			// The Loop
			if ( $the_query->have_posts() ) {
				while ( $the_query->have_posts() ) {
					$the_query->the_post();
					$post = get_post( get_the_id() );
					$selected = $value == get_the_id() ? ' selected="selected"' : '';
					$options .= '<option value="'.get_the_id().'"'.$selected.'>'.$post->post_title.'</option>' . PHP_EOL;				
				}
				wp_reset_postdata();
			} else {
				// no services found
			}	
			
			return $options;
		}


		/**
		 * Validation Failed
		 */			
		private function validationFailed( $message = '' ) {
			$this->failed_validation = true;
			$message = esc_html__( $message, 'gravityforms' );
			$this->validation_message = empty( $this->errorMessage ) ? $message : $this->errorMessage;
		}
		
		/**
		 * Validation
		 */	
		public function validate( $value, $form ) {
			$form_id = absint( $form['id'] );
			
			if( 'ga_services' == get_post_type($value) && get_post_status( $value ) == 'publish' ) {
				# valid field
				$service_id  = $value;
				
				// Selected service exists in form category term
				$form_cat_slug = rgar($form, 'ga_service_category');
				$cat           = term_exists( $form_cat_slug, 'ga_service_cat' );
				
				if( $cat ) {
					if( has_term( $cat, 'ga_service_cat', $service_id ) ) {
						# valid
					} else {
						$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_required_service') );
						return;					
					}
				}	
				
			} else {
				$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_required_service') );	
				return;
			}

			// Check if calendar widget is found
			if( !gf_field_type_exists( $form, 'appointment_calendar' ) ) {
				$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_required_date') );
				return;
			}				
			
		}	
	
		/**
		 * Save value
		 */	
		public function get_value_save_entry( $value, $form, $input_name, $entry_id, $entry ) {
			//$post_id = absint( $_POST['appointment_booking_service'] );			
			//$value = get_the_title( $post_id );
			return $value;
		}	
	
		/**
		* Show service title entry single
		*/		
		public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {
		
			$post_id = absint( $value );			
			if( 'ga_services' == get_post_type($post_id) ) {
				
				// $value = '<a href="'.get_edit_post_link( $post_id ).'">' .get_the_title( $post_id ). '</a>';	 //
				
				$value = get_the_title( $post_id );	
				return esc_html( $value );				
			} else {	
			
				return esc_html( $value );
				
			}
		}		
		
		/**
		* Merge tag, on notifications, confirmations
		*/			
		public function get_value_merge_tag( $value, $input_id, $entry, $form, $modifier, $raw_value, $url_encode, $esc_html, $format, $nl2br ) {
			$post_id = absint( $value );			
			if( 'ga_services' == get_post_type($post_id) ) {
				$value = get_the_title( $post_id );	
				return esc_html( $value );				
			} else {	
			
				return esc_html( $value );
				
			}			
		}		
		
		
		/*
		* Show service title on entry list
		*/
		/*		
		public function get_value_entry_list( $value, $entry, $field_id, $columns, $form ) {
			$post_id = absint( $value );			
			if( 'ga_services' == get_post_type($post_id) ) {
				$value = get_the_title( $post_id );	
				return esc_html( $value );				
			} else {	
				return esc_html( $value );
			}
			
		}	
		*/				

	
		public function get_form_editor_inline_script_on_page_render() {
			return "
			gform.addFilter('gform_form_editor_can_field_be_added', function (canFieldBeAdded, type) {
				if (type == 'appointment_services') {
					if (GetFieldsByType(['appointment_services']).length > 0) {
						alert(" . json_encode( esc_html__( 'Only one Booking Services field can be added to the form', 'gravityformscoupons' ) ) . ");
						return false;
					}
				}
				return canFieldBeAdded;
			});";
		}		
	

	
	} // end class
	GF_Fields::register( new GF_Appointment_Booking_Services() );	
} // end if
gf-booking-calendar.php000066600000077167151262333040011073 0ustar00<?php

defined( 'ABSPATH' ) or exit; // Exit if accessed directly

if ( class_exists( 'GFForms' ) ) {
	class GF_Appointment_Booking_Calendar extends GF_Field {
		public $type = 'appointment_calendar';
		
		public function get_form_editor_field_title() {
			return esc_attr__( 'Booking Calendar', 'gravityforms' );
		}
		
		/*
		* Where to assign this widget
		*/
		public function get_form_editor_button() {
			return array(
				//'group' => 'advanced_fields',
				'group' => 'appointment_calendar',
				'text'  => $this->get_form_editor_field_title()
			);
		}	
		/*
		* Add button to the group
		*/
		public function add_button( $field_groups ) {
			$field_groups = $this->ga_appointment_services_gf_group( $field_groups );
			return parent::add_button( $field_groups );
		}
		/*
		* Add our group
		*/	
		public function ga_appointment_services_gf_group( $field_groups ) {
			foreach ( $field_groups as $field_group ) {
				if ( $field_group['name'] == 'appointment_calendar' ) {
					return $field_groups;
				}
			}		
			$field_groups[] = array(
				'name'   => 'appointment_calendar',
				'label'  => __( 'Appointment Booking', 'simplefieldaddon' ),
				'fields' => array(
				)
			);

			return $field_groups;
		}	
		
		/*
		* Widget settings
		*/			
		function get_form_editor_field_settings() {
			return array(
				'label_setting',
				'error_message_setting',			
				'label_placement_setting',
				'admin_label_setting',
				'description_setting',
				'css_class_setting',
				'rules_setting',
				'size_setting',
				'conditional_logic_field_setting',
			);
		}

		public function is_conditional_logic_supported() {
			return true;
		}

		public function is_value_submission_empty($form_id) {
			return false;
		}

		/**
		 * Field Markup
		 */			
		public function get_field_input( $form, $value = '', $entry = null ) {
			$form_id         = absint( $form['id'] );
			$is_entry_detail = $this->is_entry_detail();
			$is_form_editor  = $this->is_form_editor();

			$id                 = $this->id;
			//$field_id           = 'gf_appointment_booking_calendar'; // the html id
			$field_id           = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id";	
			$logic_event        = ! $is_form_editor && ! $is_entry_detail ? $this->get_conditional_logic_event( 'keyup' ) : '';
			$size               = $this->size;
			$class_suffix       = $is_entry_detail ? '_admin' : '';
			$class              = $size . $class_suffix;
			$css_class          = trim( esc_attr( $class ) . ' gfield_select' );
			$tabindex           = $this->get_tabindex();
			$disabled_text      = $is_form_editor ? 'disabled="disabled"' : '';
			$required_attribute = $this->isRequired ? 'aria-required="true"' : '';
			$invalid_attribute  = $this->failed_validation ? 'aria-invalid="true"' : 'aria-invalid="false"';
			
			
			$calendar = "<div class='ginput_container'>";
			
			if( $this->is_entry_edit() ) {
				
				$calendar .= 'This field is not editable';
				$calendar .= "<input type='hidden' name='input_{$id}' id='{$field_id}' value='{$value}'/>";
				
			} elseif( !$this->is_form_editor() ) {
				ob_start();
				
				$calendar .= '<div class="grid-row"><div class="'.$this->field_size().' grid-sm-12 grid-xs-12" id="gappointments_calendar">' . PHP_EOL;

				if( ga_service_id($form) && gf_field_type_exists( $form, 'appointment_services' ) ) {
					$current_date   = ga_current_date_with_timezone();
					$service_id     = ga_service_id($form);
										
					// Form submited service ID
					$services_field_value = gf_get_field_type_value( $form, 'appointment_services' );						
					if( is_numeric($services_field_value) && 'ga_services' == get_post_type($services_field_value) ) {
						$service_id = $services_field_value;
					}
							
							
					$provider_id = ga_get_provider_id( $service_id ) ? ga_get_provider_id( $service_id ) : 0;			
					// Form submited provider ID
					if( gf_field_type_exists( $form, 'appointment_providers' ) ) {
						
						$providers_field_value  = gf_get_field_type_value( $form, 'appointment_providers' );

						if( is_numeric($providers_field_value) && ga_get_provider_id($service_id) && 'ga_providers' == get_post_type($providers_field_value) ) {
							$provider_id = $providers_field_value;
						}
					}
					
					// Booking Date/Time Fields
					$date_val = '';
					$time_val = ''; 
					$cost_val = '0'; 				
					if ( is_array( $value ) ) {
						$date_val  = isset($value['date']) ? $value['date'] : $date_val;
						$time_val  = isset($value['time']) ? $value['time'] : $time_val;
						$cost_val  = isset($value['cost']) ? $value['cost'] : $cost_val;
					}

					// Service period type
					$period_type = (string) get_post_meta($service_id, 'ga_service_period_type', true);
					if( $period_type == 'date_range' ) {
						$range = (array) get_post_meta($service_id, 'ga_service_date_range', true);
						if( isset($range['from']) && ga_valid_date_format($range['from']) && isset($range['to']) && ga_valid_date_format($range['to']) ) {
							$current_date = new DateTime($range['from'], new DateTimeZone( ga_time_zone() ));
						}
					}
					if( $period_type == 'custom_dates' ) {
						$custom_dates = (array) get_post_meta($service_id, 'ga_service_custom_dates', true);
						if( is_array($custom_dates) && count($custom_dates) > 0 && ga_valid_date_format(reset($custom_dates)) ) {
							$current_date = new DateTime(reset($custom_dates), new DateTimeZone( ga_time_zone() ));
						}
					}
					
					// Form submited date & time
					$selected_date      = false;
					$selected_slot      = false;
					if( ga_valid_date_format($date_val) ) {
						$current_date   = new DateTime( $date_val, new DateTimeZone(ga_time_zone()) );
						$selected_date  = clone $current_date;
					}					
					if( ga_valid_time_format($time_val) ) {
						$selected_slot  = $time_val;
					}
					// Form submited date & time					
					
					// Calendar HTML
					$calendar   .= '<div id="ga_appointments_calendar" form_id="'.$form_id.'"><div class="ga_monthly_schedule_wrapper">' . PHP_EOL;
					$ga_calendar = new GA_Calendar( $form_id, $current_date->format('m'), $current_date->format('Y'), $service_id, $provider_id, $selected_date, $selected_slot );
					$calendar   .= $ga_calendar->show();
					$calendar   .= '</div></div>' . PHP_EOL; // end #ga_appointments_calendar
					// End Calendar HTML
					
					// Multiple Slots Selection
					$calendar .= '<div id="ga_selected_bookings">' . PHP_EOL;
						$calendar .= $this->multiple_bookings_markup($form_id, $value, $service_id, $provider_id);
					$calendar .= '</div>' . PHP_EOL; // end #ga_selected_bookings
					// Multiple Slots Selection					
					
				} else {
					return '<p>' .ga_get_form_translated_data($form_id, 'error_no_services'). '</p>';
				}

				$calendar .= '</div></div>' . PHP_EOL; // end grid-row		

				$calendar .= "<input type='hidden' name='input_{$id}[date]' id='{$field_id}' class='{$class} ginput_{$this->type}_input appointment_booking_date' value='{$date_val}' {$logic_event}/>";
				$calendar .= "<input type='hidden' name='input_{$id}[time]' id='{$field_id}_time' class='{$class} ginput_{$this->type}_input appointment_booking_time' value='{$time_val}'/>";
				
				// Appointment cost hidden field just in case 
				$calendar .= "<input type='hidden' name='input_{$id}[cost]' class='ginput_appointment_cost_input gform_hidden' value='{$cost_val}'/>";				
				
				$calendar .= ob_get_clean();
			}
			
			$calendar .= '</div>' . PHP_EOL; // end ginput_container
			return $calendar;
		}


		/**
		 * Is Entry Edit
		 */				
		public function is_entry_edit() {
			if ( rgget( 'page' ) == 'gf_entries' && rgget( 'view' ) == 'entry' && rgpost( 'screen_mode' ) == 'edit' ) {
				return true;
			}

			return false;
		}			
		
		/**
		 * Returns TRUE if the current page is the form editor page. Otherwise, returns FALSE
		 */		
		public function is_form_editor() {
			if ( rgget( 'page' ) == 'gf_edit_forms' && ! rgempty( 'id', $_GET ) && rgempty( 'view', $_GET ) ) {
				return true;
			}
			return false;
		}			
		
		/**
		 * Is Entry View
		 */				
		public function is_entry_view() {
			if ( rgget( 'page' ) == 'gf_entries' && rgget( 'view' ) == 'entry' && rgpost( 'screen_mode' ) == 'view' ) {
				return true;
			}

			return false;
		}		
		
		public function get_inline_price_styles() {
			return '';
		}				
		

		/**
		 * Multiple Bookings Markup
		 */			
		public function multiple_bookings_markup($form_id, $value, $service_id, $provider_id) {
			$id  = $this->id;
			$out = '';

			// Service multiple slots
			$multiple_slots = (string) get_post_meta( $service_id, 'ga_service_multiple_selection', true );			
			if( $multiple_slots != 'yes' ) { 
				return '';
			}
					
			// Time Format Display
			$time_display         = ga_service_time_format_display($service_id);	
			
			// Service price
			$service_price = get_post_meta($service_id, 'ga_service_price', true);
			
			// Service mode
			$available_times_mode = (string) get_post_meta( $service_id, 'ga_service_available_times_mode', true );

			
			// Get Bookings
			$bookings = ga_get_multiple_bookings($value, $service_id, $provider_id);

			if( count($bookings) > 0 ) {
				foreach( $bookings as $key => $booking ) {
					//$format = $available_times_mode == 'no_slots' ? 'F j, Y' : 'F j, Y \a\t ' . $time_display;
					$date = new dateTime($booking);
					
					// Translation Support						
					if( $available_times_mode == 'no_slots' )  {
						$month = $date->format('F');
						$day   = $date->format('j');
						$year  = $date->format('Y');
						$appointment_date = ga_get_form_translated_slots_date($form_id, $month, $day, $year);
					} else {
						$month = $date->format('F');
						$week  = $date->format('l');
						$day   = $date->format('j');
						$year  = $date->format('Y');
						$_time = $date->format($time_display);				
						$appointment_date = ga_get_form_translated_date_time($form_id, $month, $week, $day, $year, $_time);			
					}					
					
					if( $available_times_mode == 'custom' ) {
						$slot_price = ga_get_slot_price( $form_id, $date, $service_id, $provider_id);
					} else {
						$slot_price = $service_price;
					}
					
					$out .= '<div class="ga_selected_booking">';
						$out .= '<div class="ga_delete_booking"><i class="fa fa-times-circle"></i></div>';
						$out .= '<input type="hidden" class="ga_hidden_input" name="input_'.$id.'[bookings][]" value="'.$booking.'" slot_cost="'.$slot_price.'">'.$appointment_date;
					$out .= '</div>';
				}
			}

			return $out;
		}


		/**
		 * Field Size Class
		 */			
		public function field_size() {

			if( isset( $this->size ) ) {
				switch ($this->size) {
					case "small":
						$gf_size      = 'ga_wrapper_small grid-lg-4 grid-md-4 grid-sm-6 grid-sx-12';
						break;
					case "medium":
						$gf_size      = 'ga_wrapper_medium grid-lg-6 grid-md-6';
						break;
					case "large":
						$gf_size      = 'ga_wrapper_large grid-lg-12 grid-md-12';
						break;
					default:
						$gf_size      = 'ga_wrapper_medium here grid-lg-6 grid-md-6';	
				}
			} else {
				$gf_size              = 'ga_wrapper_medium grid-lg-6 grid-md-6';	
			}
			
			return $gf_size;
		}
		
		/**
		 * Validation Failed Message
		 */			
		private function validationFailed( $message = '' ) {
			$this->failed_validation = true;
			$message = esc_html__( $message, 'gravityforms' );
			$this->validation_message = empty( $this->errorMessage ) ? $message : $this->errorMessage;
		}			

		/**
		 * Validate
		 */	
		public function validate( $value, $form ) {
			$form_id = absint( $form['id'] );
			
			$date = '';
			$time = '';
			
			if ( is_array( $value ) ) {
				$date  = isset($value['date']) ? $value['date'] : $date;
				$time  = isset($value['time']) ? $value['time'] : $time;
			}				
			
			// Check if services field exists
			if( gf_field_type_exists($form, 'appointment_services') && 'ga_services' == get_post_type( gf_get_field_type_value($form, 'appointment_services') ) ) {
			
				// Service & Provider ID
				$service_id   = gf_get_field_type_value( $form, 'appointment_services' );
				$provider_id  = gf_field_type_exists($form, 'appointment_providers') && 'ga_providers' == get_post_type(gf_get_field_type_value($form, 'appointment_providers')) 
								? gf_get_field_type_value($form, 'appointment_providers') 
								: 0;		
	

				if( ga_get_provider_id($service_id) && $provider_id == 0 ) {
					$provider_id = ga_get_provider_id($service_id);
				}

				// Selected service exists in form category term
				$form_cat_slug = rgar($form, 'ga_service_category');
				$cat           = term_exists( $form_cat_slug, 'ga_service_cat' );
				
				if( $cat ) {
					if( has_term( $cat, 'ga_service_cat', $service_id ) ) {
						# valid
					} else {
						$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_required_service') );
						return;					
					}
				}			
			
			} else {
				$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_required_service') );
				return;
			}
		
			$available_times_mode = (string) get_post_meta( $service_id, 'ga_service_available_times_mode', true );	


			/**
			 * Multiple Bookings Validation
			 */	
			// Service multiple slots
			$multiple_slots      = (string) get_post_meta( $service_id, 'ga_service_multiple_selection', true );

			// Get bookings
			$bookings = ga_get_multiple_bookings($value, $service_id, $provider_id);

			if( $multiple_slots == 'yes' ) {
				if( count($bookings) > 0 ) {
					foreach ($bookings as $key => $date) {
						$dateTime = new DateTime( $date, new DateTimeZone( ga_time_zone() ) );
				
						// Date Slots Mode
						if( $available_times_mode == 'no_slots' )  {
							# date validation failed
							if( $this->date_valid($form, $service_id, $provider_id, $dateTime) !== true) {
								$message = $this->date_valid($form, $service_id, $provider_id, $dateTime);
								$this->validationFailed( $message );
								return;	
							}
							continue;
						}

						// Client max bookings
						$client_max_bookings = $this->client_max_bookings( $form, $service_id, $dateTime, $bookings );
						if( $client_max_bookings ) {
							$max_bookings = ga_get_service_max_bookings($service_id);
							$booked = $dateTime->format('F j, Y');
							
							// Translation
							$month  = $dateTime->format('F');
							$day    = $dateTime->format('j');
							$year   = $dateTime->format('Y');
							$booked = ga_get_form_translated_slots_date($form_id, $month, $day, $year);
							$booked = ga_get_form_translated_error_max_bookings($form_id, $booked, $max_bookings);
							// Translation

							$this->validationFailed( "{$booked}" );
							return;
						}

						// Time Slots Mode
						$time = $dateTime->format('H:i');
						if( $this->slot_valid($form, $service_id, $provider_id, $dateTime, $time) !== true ) {
							# time & date validation failed
							$message = $this->slot_valid($form, $service_id, $provider_id, $dateTime, $time);
							$this->validationFailed( $message );
							return;										
						}

					}
				}

				if( count($bookings) < 1 ) {
					$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_required') );
					return;		
				}
				return;
			}
			// Multiple Bookings Validation

			/**
			 * Single Bookings Validation
			 */	
			if( ga_valid_date_format($date) ) {
				$dateTime = new DateTime( $date, new DateTimeZone( ga_time_zone() ) );
		
				// Date Slots Mode
				if( $available_times_mode == 'no_slots' )  {

					if( $this->date_valid($form, $service_id, $provider_id, $dateTime) !== true) {
						$message = $this->date_valid($form, $service_id, $provider_id, $dateTime);					
						$this->validationFailed( $message );
						return;	
					}
					
					return;
				}


				// Client max bookings
				$client_max_bookings = $this->client_max_bookings( $form, $service_id, $dateTime, $bookings = array($dateTime->format('Y-m-j')) );
				if( $client_max_bookings ) {
					$booked = $dateTime->format('F j, Y');
					
					// Translation
					$month   = $dateTime->format('F');
					$day     = $dateTime->format('j');
					$year    = $dateTime->format('Y');
					$booked  = ga_get_form_translated_slots_date($form_id, $month, $day, $year);
					$reached = ga_get_form_translated_error_message($form_id, 'error_reached_max', $booked);
					// Translation	
					
					$this->validationFailed( "{$reached}" );
					return;
				}

				// Time Slots Mode
				if( $this->slot_valid($form, $service_id, $provider_id, $dateTime, $time) !== true ) {
					$message = $this->slot_valid($form, $service_id, $provider_id, $dateTime, $time);
					$this->validationFailed( $message );
					return;		
				}

				
			} else {
				$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_required_date') );
				return;		
			}

		} // end validate function
		


		/**
		 * Date Valid
		 */			
		private function date_valid( $form, $service_id, $provider_id, $dateTime ) {
			$form_id = absint( $form['id'] );
			
			// Date Validation
			$date       = $dateTime->format('Y-m-j');	

			// Translation
			$month       = $dateTime->format('F');
			$day         = $dateTime->format('j');
			$year        = $dateTime->format('Y');
			$lang_date   = ga_get_form_translated_slots_date($form_id, $month, $day, $year);
			// Translation				
			
			if( !class_exists('GA_Calendar') ) {
				require_once( ga_base_path . '/gf-fields/ga-calendar.php' );
			}	

			$ga_calendar       = new GA_Calendar( $form_id, $dateTime->format('n'), $dateTime->format('Y'), $service_id, $provider_id );
			$date_available    = $ga_calendar->is_date_available( $dateTime );

			if( $date_available ) {
				# valid date
				if( $this->client_booked_date_slot($form, $service_id, $provider_id, $dateTime) ) {
					return ga_get_form_translated_error_message($form_id, 'error_booked_date', $lang_date);
				}
			} else {
				return ga_get_form_translated_error_message($form_id, 'error_date_valid', $lang_date);
			}

			return true;
		}

		/**
		 * Slot Valid
		 */			
		private function slot_valid( $form, $service_id, $provider_id, $date, $time ) {
			$form_id = absint( $form['id'] );
			
			// Time Slots Validation
			if( ga_valid_time_format($time) && array_key_exists($time, get_ga_appointment_time()) ) {
				if( !class_exists('GA_Calendar') ) {
					require_once( ga_base_path . '/gf-fields/ga-calendar.php' );
				}
				
				$ga_calendar       = new GA_Calendar( $form_id, $date->format('n'), $date->format('Y'), $service_id, $provider_id );
				$slot_available    = $ga_calendar->get_slots( $date );
				$time_display      = ga_service_time_format_display($service_id);

				// Translation	
				$human_date = new DateTime( "{$date->format('Y-m-j')} {$time}", new DateTimeZone(ga_time_zone()) );
				$month = $human_date->format('F');
				$week  = $human_date->format('l');
				$day   = $human_date->format('j');
				$year  = $human_date->format('Y');
				$_time = $human_date->format($time_display);
				// Translation				
				$lang_date = ga_get_form_translated_date_time($form_id, $month, $week, $day, $year, $_time);					
					
				// Is slot available
				$is_slot_available = array_key_exists($time, $slot_available);
				if( !$is_slot_available ) {
					return ga_get_form_translated_error_message($form_id, 'error_slot_valid', $lang_date);
				}					
				
				// Client already booked slot
				$already_booked_slot = $this->client_booked_slot( $form, $service_id, $provider_id, $date, $time );
				if( $already_booked_slot ) {
					return ga_get_form_translated_error_message($form_id, 'error_booked_date', $lang_date);
				}
				return true;
			} else {
				return ga_get_form_translated_error_message($form_id, 'error_required_slot');
			}
		}


		/**
		 * Escape SQL RegexP
		 * Characters must be escaped such as: \ ^ . $ | ( ) [ ] * + ? { } ,
		 */			
		private function esc_sql_regexp( $str ) {
			return preg_replace('/[.\\\\+*?[\\^\\]$(){}=!|:,\\-]/', '\\\\\\\\\\\\${0}', $str);
		}		

		/**
		 * Get Email Value From Submitted Form
		 */			
		private function email_field_value( $form ) {
			$email_value = gf_field_type_exists($form, 'email') ? esc_sql(gf_get_field_type_value($form, 'email')) : '';
			return $this->esc_sql_regexp($email_value);
		}			
		
		/**
		 * Get Phone Value From Submitted Form
		 */			
		private function phone_field_value( $form ) {
			$phone_value = gf_field_type_exists($form, 'phone') ? esc_sql(gf_get_field_type_value($form, 'phone')) : '';
			return $this->esc_sql_regexp($phone_value);
		}	
		
		/**
		 * Client Booked Time Slot
		 * @ $form array
		 * @ $service_id
		 * @ $provider_id
		 * @ $dateTime
		 * @ $slot_start
		 */			
		private function client_booked_slot($form, $service_id, $provider_id, $dateTime, $slot_start) {
			// Prevent Double Bookings
			$double_bookings = ga_get_service_double_bookings($service_id);
			if( $double_bookings == 'no' ) {
				return false;
			}
						
			$date         = $dateTime->format("Y-m-j");
			$slot_end     = ga_get_time_end($slot_start, $service_id);
			
			// Client Booked Time Slot
			$email_value  = $this->email_field_value( $form );
			$phone_value  = $this->phone_field_value( $form );  

			global $wpdb;
			$querystr = "SELECT $wpdb->posts.ID
				FROM
				   $wpdb->posts,
				   $wpdb->postmeta AS app_date,        
				   $wpdb->postmeta AS provider,
				   $wpdb->postmeta AS time1,
				   $wpdb->postmeta AS time2,
				   $wpdb->postmeta AS client
				WHERE
				   $wpdb->posts.ID = app_date.post_id          
				AND
				   $wpdb->posts.ID = provider.post_id
				AND
				   $wpdb->posts.ID = time1.post_id
				AND
				   $wpdb->posts.ID = time2.post_id
				AND
				   $wpdb->posts.ID = client.post_id
				  
				  
				AND $wpdb->posts.post_type = 'ga_appointments'
				AND $wpdb->posts.post_status IN ('completed', 'publish', 'payment', 'pending')                 

				AND app_date.meta_key   = 'ga_appointment_date'
				AND app_date.meta_value = %s

				AND provider.meta_key   = 'ga_appointment_provider'
				AND provider.meta_value = %s

				AND time1.meta_key = 'ga_appointment_time_end'
				AND time1.meta_value > %s

				AND time2.meta_key = 'ga_appointment_time'
				AND time2.meta_value < %s
				
				AND client.meta_key = 'ga_appointment_new_client'
				AND (client.meta_value REGEXP '\"email\";s:[1-9]+:\"{$email_value}\"'
				OR client.meta_value REGEXP '\"phone\";s:[1-9]+:\"{$phone_value}\"')
			";

			$wpdb->query('SET SQL_BIG_SELECTS = 1');
			$sql_prepare  = $wpdb->prepare($querystr, $date, $provider_id, $slot_start, $slot_end);
			$appointments = $wpdb->get_results( $sql_prepare, ARRAY_A );			
			
			//var_dump( count($appointments) ); 
			//var_dump( $wpdb->last_query ); 
			
			if ( count($appointments) > 0 ) {
				return true;
			} else {
				return false;
			}
		}
		
		/**
		 * Client Booked Date Slot
		 * @ $form array
		 * @ $service_id
		 * @ $provider_id
		 * @ $dateTime	 
		 */			
		private function client_booked_date_slot( $form, $service_id, $provider_id, $dateTime ) {
			$date = $dateTime->format("Y-m-j");
			
			// Prevent Double Bookings
			$double_bookings = ga_get_service_double_bookings($service_id);
			if( $double_bookings == 'no' ) {
				return false;
			}
			
			// Client Booked Date Slot
			$email_value  = $this->email_field_value( $form );
			$phone_value  = $this->phone_field_value( $form );  

			global $wpdb;
			$querystr = "SELECT $wpdb->posts.ID
				FROM
				   $wpdb->posts,
				   $wpdb->postmeta AS app_date,        
				   $wpdb->postmeta AS provider,
				   $wpdb->postmeta AS client
				WHERE
				   $wpdb->posts.ID = app_date.post_id          
				AND
				   $wpdb->posts.ID = provider.post_id
				AND
				   $wpdb->posts.ID = client.post_id
				  
				  
				AND $wpdb->posts.post_type = 'ga_appointments'
				AND $wpdb->posts.post_status IN ('completed', 'publish', 'payment', 'pending')                 

				AND app_date.meta_key   = 'ga_appointment_date'
				AND app_date.meta_value = %s

				AND provider.meta_key   = 'ga_appointment_provider'
				AND provider.meta_value = %s
				
				AND client.meta_key = 'ga_appointment_new_client'
				AND (client.meta_value REGEXP '\"email\";s:[1-9]+:\"{$email_value}\"'
				OR client.meta_value REGEXP '\"phone\";s:[1-9]+:\"{$phone_value}\"')
			";

			$wpdb->query('SET SQL_BIG_SELECTS = 1');
			$sql_prepare  = $wpdb->prepare($querystr, $date, $provider_id);
			$appointments = $wpdb->get_results( $sql_prepare, ARRAY_A );			

			if ( count($appointments) > 0 ) {
				return true;
			} else {
				return false;
			}
		}			
		
		/**
		 * Client Max Bookings
		 * @ $form array
		 * @ $service_id
		 * @ $dateTime
		 * @ $bookings count		 
		 */			
		private function client_max_bookings( $form, $service_id, $dateTime, $bookings ) {
			$date = $dateTime->format("Y-m-j");
			$max_bookings = ga_get_service_max_bookings($service_id);
	
			// Client Max Bookings
			$email_value  = $this->email_field_value( $form );
			$phone_value  = $this->phone_field_value( $form );  

			global $wpdb;
			$querystr = "SELECT $wpdb->posts.ID
				FROM
				   $wpdb->posts,
				   $wpdb->postmeta AS app_date,        
				   $wpdb->postmeta AS service,
				   $wpdb->postmeta AS client
				WHERE
				   $wpdb->posts.ID = app_date.post_id          
				AND
				   $wpdb->posts.ID = service.post_id
				AND
				   $wpdb->posts.ID = client.post_id
				  
				  
				AND $wpdb->posts.post_type = 'ga_appointments'
				AND $wpdb->posts.post_status IN ('completed', 'publish', 'payment', 'pending')                 

				AND app_date.meta_key   = 'ga_appointment_date'
				AND app_date.meta_value = %s

				AND service.meta_key   = 'ga_appointment_service'
				AND service.meta_value = %s
				
				AND client.meta_key = 'ga_appointment_new_client'
				AND (client.meta_value REGEXP '\"email\";s:[1-9]+:\"{$email_value}\"'
				OR client.meta_value REGEXP '\"phone\";s:[1-9]+:\"{$phone_value}\"')
			";

			$wpdb->query('SET SQL_BIG_SELECTS = 1');
			$sql_prepare  = $wpdb->prepare($querystr, $date, $service_id);
			$appointments = $wpdb->get_results( $sql_prepare, ARRAY_A );			
	
			$found_dates = 0;
			if( $matches = preg_grep("/^{$date}/i", $bookings) ) {
				$found_dates = count($matches);
			}	
	
			$post_count = count($appointments) + $found_dates;
			if ( $post_count > $max_bookings ) {
				return true;
			}
			return false;
		}

		/**
		* Save value or save single entry edit
		*/			
		public function get_value_save_entry( $value, $form, $input_name, $entry_id, $entry ) {	
				// GF Admin Entry Edit
				if( is_admin() ) {
					return $value;
				}
				
				$date = '';
				$time = '';

				if ( is_array( $value ) ) {
					$date  = isset($value['date']) ? $value['date'] : $date;
					$time  = isset($value['time']) ? $value['time'] : $time;
				}

				$form_id = absint( $form['id'] );
				
				// Check if services field exists
				if( gf_field_type_exists($form, 'appointment_services') && 'ga_services' == get_post_type( gf_get_field_type_value($form, 'appointment_services') ) ) {
				
					// Service & Provider ID
					$service_id   = gf_get_field_type_value( $form, 'appointment_services' );
					$provider_id  = gf_field_type_exists($form, 'appointment_providers') 
									&& 'ga_providers' == get_post_type(gf_get_field_type_value($form, 'appointment_providers')) 
									? gf_get_field_type_value($form, 'appointment_providers') 
									: 0;
					if( ga_get_provider_id($service_id) && $provider_id == 0 ) {
						$provider_id = ga_get_provider_id($service_id);
					}
										
					if( !class_exists('GA_Calendar') ) {
						require_once( ga_base_path . '/gf-fields/ga-calendar.php' );
					}

					// Time Format Display
					$time_display         = ga_service_time_format_display($service_id);			

					// Service Mode
					$available_times_mode = (string) get_post_meta( $service_id, 'ga_service_available_times_mode', true );	

					// Service multiple slots			
					$multiple_slots      = (string) get_post_meta( $service_id, 'ga_service_multiple_selection', true );

					// Get Bookings
					$bookings = ga_get_multiple_bookings($value, $service_id, $provider_id);
				
					/**
					 * Multiple Bookings
					 */	
					$booking_dates = array();					
					if( $multiple_slots == 'yes' ) {
						if( count($bookings) > 0 ) {
							foreach ($bookings as $key => $date) {
								$dateTime = new DateTime( $date, new DateTimeZone( ga_time_zone() ) );
								
								// Translation Support
								if( $available_times_mode == 'no_slots' )  {
									$month = $dateTime->format('F');
									$day   = $dateTime->format('j');
									$year  = $dateTime->format('Y');
									$appointment_date = ga_get_form_translated_slots_date($form_id, $month, $day, $year);
								} else {
									$month = $dateTime->format('F');
									$week  = $dateTime->format('l');
									$day   = $dateTime->format('j');
									$year  = $dateTime->format('Y');
									$_time = $dateTime->format($time_display);
									
									$appointment_date = ga_get_form_translated_date_time($form_id, $month, $week, $day, $year, $_time);				
								}								
								
								$booking_dates[] = $appointment_date;
								
							}
							return implode("<br>", $booking_dates);
						} else {
							return array();
						}
					} else {
						/**
						 * Single Booking
						 */	
						// DATE
						$app_date            = (string) $date;
						$date                = ga_valid_date_format($app_date) ? new DateTime($app_date) : false;
						$app_date_text       = $date ? $date->format('l, F j Y') : '(Date not selected)';

						// Time
						$app_time            = $time;
						$time                = ga_valid_time_format($app_time) ? new DateTime($app_time) : false;
						$app_time_text       = $time ? $time->format('g:i a') : '(Time not selected)';				
			
						// Translation Support					
						if( $available_times_mode == 'no_slots' )  {
							if( $date ) {
								$month = $date->format('F');
								$day   = $date->format('j');
								$year  = $date->format('Y');
								$appointment_date = ga_get_form_translated_slots_date($form_id, $month, $day, $year);
							} else {
								$appointment_date = $app_date_text;
							}
						} else {
							if( $date && $time ) {
								$month = $date->format('F');
								$week  = $date->format('l');
								$day   = $date->format('j');
								$year  = $date->format('Y');
								$_time = $time->format($time_display);
								
								$appointment_date = ga_get_form_translated_date_time($form_id, $month, $week, $day, $year, $_time);
							} else {
								$appointment_date = "{$app_date_text} at {$app_time_text}";
							}					
						}						
					}
					
					$merge = apply_filters('ga_booking_merge_value', 'date_format');
					return $merge == 'timestamp' && $date && $time ? $this->get_date_timestamp( "{$date->format('Y-m-j')} {$time->format('H:i')}" ) : $appointment_date;					
				} else {
					return '';
				}
		}	

		/**
		* Show date on entry single
		*/		
		public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {
			return $value;
		}			
		

		/**
		* Merge tag, on notifications, confirmations
		*/			
		public function get_value_merge_tag( $value, $input_id, $entry, $form, $modifier, $raw_value, $url_encode, $esc_html, $format, $nl2br ) {
			$dates = explode('&lt;br&gt', $value);

			if( count($dates) > 1 ) {
				return implode(', ', $dates);
			} elseif( count($dates) == 1 ) {
				return reset( $dates );
			} else {
				return '';
			}			
		}	

		public function get_form_editor_inline_script_on_page_render() {
			return "
			gform.addFilter('gform_form_editor_can_field_be_added', function (canFieldBeAdded, type) {
				if (type == 'appointment_calendar') {
					if (GetFieldsByType(['appointment_calendar']).length > 0) {
						alert(" . json_encode( esc_html__( 'Only one Booking Calendar field can be added to the form', 'gravityformscoupons' ) ) . ");
						return false;
					}
				}
				return canFieldBeAdded;
			});";
		}	
		
		/**
		 * Get timestamp from valid date
		 */
		public function get_date_timestamp( $date_time ) {
			$date = new DateTime( $date_time, new DateTimeZone( ga_time_zone() ) );
			return $date->getTimestamp();
		}				

	} // end class
	GF_Fields::register( new GF_Appointment_Booking_Calendar() );	
} // end if
gf-booking-cost.php000066600000017015151262333040010254 0ustar00<?php

defined( 'ABSPATH' ) or exit; // Exit if accessed directly

if ( class_exists( 'GFForms' ) ) {
	class GF_Appointment_Booking_Cost extends GF_Field {
		public $type = 'appointment_cost';
		
		public function get_form_editor_field_title() {
			return esc_attr__( 'Booking Cost', 'gravityforms' );
		}
		

		/*
		* Where to assign this widget
		*/
		public function get_form_editor_button() {
			return array(
				//'group' => 'advanced_fields',
				'group' => 'appointment_calendar',
				'text'  => $this->get_form_editor_field_title()
			);
		}	
		
		/*
		* Add button to the group
		*/
		public function add_button( $field_groups ) {
			$field_groups = $this->ga_appointment_services_gf_group( $field_groups );
			return parent::add_button( $field_groups );
		}
		
		/*
		* Add our group
		*/	
		public function ga_appointment_services_gf_group( $field_groups ) {
			foreach ( $field_groups as $field_group ) {
				if ( $field_group['name'] == 'appointment_calendar' ) {
					return $field_groups;
				}
			}		
			$field_groups[] = array(
				'name'   => 'appointment_calendar',
				'label'  => __( 'Appointment Booking', 'simplefieldaddon' ),
				'fields' => array(
				)
			);

			return $field_groups;
		}	
		
		/*
		* Widget settings
		*/			
		function get_form_editor_field_settings() {
			return array(
				'label_setting',
				'error_message_setting',			
				'label_placement_setting',
				'admin_label_setting',
				'description_setting',
				'css_class_setting',
				'conditional_logic_field_setting',				
			);
		}

		public function is_conditional_logic_supported() {
			return true;
		}

		/**
		 * Field Markup
		 */			
		public function get_field_input( $form, $value = '', $entry = null ) {
			$form_id         = absint( $form['id'] );
			$is_entry_detail = $this->is_entry_detail();
			$is_form_editor  = $this->is_form_editor();

			$id       = $this->id;
			$field_id           = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id";	
			$logic_event        = ! $is_form_editor && ! $is_entry_detail ? $this->get_conditional_logic_event( 'keyup' ) : '';
			$size               = $this->size;
			$class_suffix       = $is_entry_detail ? '_admin' : '';
			$class              = $size . $class_suffix;
			$css_class          = trim( esc_attr( $class ) . ' gfield_select' );
			$tabindex           = $this->get_tabindex();
			
			
			$output = '<div class="ginput_container">';
			
			if( $this->is_entry_edit() ) {
				$output .= 'This field is not editable';
				$output .= "<input type='hidden' name='input_{$id}' id='{$field_id}' value='{$value}'/>";
			} elseif( !$this->is_form_editor() ) {
				$output .= '<script>jQuery("body").on("change", ".ginput_appointment_cost_input", function() { jQuery( this ).prev( "span" ).text( gformFormatMoney( this.value, true ) ); });</script>';
				$output .= "<span class='ginput_{$this->type} ginput_product_price ginput_{$this->type}_{$form_id}_{$field_id}'>" . GFCommon::to_money( '0' ) . "</span>";
				$output .= "<input type='hidden' name='input_{$id}' id='{$field_id}' style='{$this->get_inline_price_styles()}' class='{$class} ginput_{$this->type}_input gform_hidden' value='{$value}' {$tabindex} {$logic_event}/>";			
			}
			
			$output .= '</div>';
			
			return $output;
		}

		/**
		 * Is Entry Edit
		 */			
		public function is_entry_edit() {
			if ( rgget( 'page' ) == 'gf_entries' && rgget( 'view' ) == 'entry' && rgpost( 'screen_mode' ) == 'edit' ) {
				return true;
			}

			return false;
		}		
		
		/**
		 * Returns TRUE if the current page is the form editor page. Otherwise, returns FALSE
		 */		
		public function is_form_editor() {
			if ( rgget( 'page' ) == 'gf_edit_forms' && ! rgempty( 'id', $_GET ) && rgempty( 'view', $_GET ) ) {
				return true;
			}
			return false;
		}				
		
		public function get_inline_price_styles() {
			return '';
		}		
		
		/**
		 * Validation Failed Message
		 */			
		private function validationFailed( $message = '' ) {
			$this->failed_validation = true;
			$message = esc_html__( $message, 'gravityforms' );
			$this->validation_message = empty( $this->errorMessage ) ? $message : $this->errorMessage;
		}		

		/**
		 * Validation
		 */			
		public function validate( $value, $form ) {
			$form_id = absint( $form['id'] );
			
			if( !gf_field_type_exists( $form, 'appointment_services' ) ) {
				$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_services_form') );
				return;
			}	
			
			if( 'ga_services' == get_post_type(gf_get_field_type_value($form, 'appointment_services' )) ) {
				// Service field value
				$service_id  = gf_get_field_type_value( $form, 'appointment_services' );
				// Selected service exists in form category term
				$form_cat_slug = rgar($form, 'ga_service_category');
				$cat           = term_exists( $form_cat_slug, 'ga_service_cat' );
				
				if( $cat ) {
					if( has_term( $cat, 'ga_service_cat', $service_id ) ) {
						# valid
					} else {
						$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_required_service') );
						return;					
					}
				}
				
			} else {
				$this->validationFailed( ga_get_form_translated_error_message($form_id, 'error_service_valid') );
				return;				
			}
			
		}	
		
		/**
		 * Save Cost with Currency Symbol
		 */
		public function get_value_save_entry( $value, $form, $input_name, $entry_id, $entry ) {
			if( gf_field_type_exists( $form, 'appointment_services' ) ) {
				$form_id = $form['id'];				
				// Service ID
				$service_id    = absint( gf_get_field_type_value( $form, 'appointment_services' ) );
				
				// Provider ID
				$provider_id  = gf_field_type_exists($form, 'appointment_providers') 
								&& 'ga_providers' == get_post_type(gf_get_field_type_value($form, 'appointment_providers')) 
								? gf_get_field_type_value($form, 'appointment_providers') 
								: 0;
				if( ga_get_provider_id($service_id) && $provider_id == 0 ) {
					$provider_id = ga_get_provider_id($service_id);
				}
				
				// Service Price
				$service_price = get_post_meta($service_id, 'ga_service_price', true);
				
				
				/**
				 * Multiple Bookings
				 */
				$times_mode     = (string) get_post_meta( $service_id, 'ga_service_available_times_mode', true );	
				$multiple_slots = (string) get_post_meta( $service_id, 'ga_service_multiple_selection', true );

				if( $multiple_slots == 'yes' && gf_field_type_exists($form, 'appointment_calendar') ) {
					$calendar = gf_get_field_type_value( $form, 'appointment_calendar' );
					
					// Get bookings
					$bookings = ga_get_multiple_bookings($calendar, $service_id, $provider_id);
					if( $times_mode == 'custom' ) {
						$value  = gf_to_money( ga_get_slots_total( $form['id'], $service_id, $provider_id, $bookings ) );
					} else {
						$cost   = $service_price * count($bookings);
						$value  = gf_to_money( $cost );							
					}					
				} else {
					/**
					 * Single Booking
					 */		
					if( $times_mode == 'custom' ) {
						$calendar      = gf_get_field_type_value( $form, 'appointment_calendar' );
						$booking       = ga_get_multiple_bookings($calendar, $service_id, $provider_id);
						$service_price = ga_get_slots_total( $form_id, $service_id, $provider_id, $booking );
					}						 
					$value = gf_to_money( $service_price );
				}				
			}
			return $value;			
		}		
		
		/**
		* Show appointment cost on entry single & GP Preview Plugin
		*/		
		public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {		
			return $value;
		}			
		
		
		
	} // end class
	GF_Fields::register( new GF_Appointment_Booking_Cost() );	
} // end if