WordPress Online Booking and Scheduling Plugin – Bookly - Version 13.0

Version Description

  • Added support for Recurring Appointments (Add-on)
  • Added new status Rejected for appointments
  • Added ability to send notifications for deleted appointments
  • Added buttons for quick access to Bookly documentation and support team
Download this release

Release Info

Developer Ladela
Plugin Icon 128x128 WordPress Online Booking and Scheduling Plugin – Bookly
Version 13.0
Comparing to
See all releases

Code changes from version 11.3.1 to 13.0

Files changed (76) hide show
  1. autoload.php +18 -18
  2. backend/Backend.php +120 -130
  3. backend/modules/appearance/Controller.php +193 -201
  4. backend/modules/appearance/lib/Helper.php +58 -0
  5. backend/modules/appearance/resources/js/appearance.js +598 -583
  6. backend/modules/appearance/templates/_1_service.php +178 -194
  7. backend/modules/appearance/templates/_3_time.php +165 -165
  8. backend/modules/appearance/templates/_4_cart.php +0 -132
  9. backend/modules/appearance/templates/_5_cart.php +135 -0
  10. backend/modules/appearance/templates/_5_details.php +0 -63
  11. backend/modules/appearance/templates/_6_details.php +44 -0
  12. backend/modules/appearance/templates/_6_payment.php +0 -67
  13. backend/modules/appearance/templates/_7_done.php +0 -7
  14. backend/modules/appearance/templates/_7_payment.php +69 -0
  15. backend/modules/appearance/templates/_8_complete.php +7 -0
  16. backend/modules/appearance/templates/_card_payment.php +37 -37
  17. backend/modules/appearance/templates/_codes.php +25 -25
  18. backend/modules/appearance/templates/_progress_tracker.php +18 -12
  19. backend/modules/appearance/templates/index.php +127 -125
  20. backend/modules/appointments/Controller.php +255 -245
  21. backend/modules/appointments/resources/js/appointments.js +57 -33
  22. backend/modules/appointments/templates/_export_dialog.php +39 -39
  23. backend/modules/appointments/templates/_print_dialog.php +33 -1
  24. backend/modules/appointments/templates/index.php +121 -118
  25. backend/modules/calendar/Components.php +30 -4
  26. backend/modules/calendar/Controller.php +747 -583
  27. backend/modules/calendar/resources/css/fullcalendar.min.css +4 -4
  28. backend/modules/calendar/resources/js/calendar.js +400 -370
  29. backend/modules/calendar/resources/js/delete_dialog.js +9 -0
  30. backend/modules/calendar/resources/js/fullcalendar.min.js +8 -8
  31. backend/modules/calendar/resources/js/ng-appointment_dialog.js +981 -728
  32. backend/modules/calendar/templates/_appointment_dialog.php +14 -14
  33. backend/modules/calendar/templates/_customer_details_dialog.php +98 -97
  34. backend/modules/calendar/templates/_delete_dialog.php +32 -0
  35. backend/modules/calendar/templates/calendar.php +93 -91
  36. backend/modules/coupons/Controller.php +65 -63
  37. backend/modules/coupons/forms/Coupon.php +18 -18
  38. backend/modules/coupons/resources/js/coupons.js +63 -64
  39. backend/modules/coupons/templates/_modal.php +66 -1
  40. backend/modules/coupons/templates/index.php +43 -44
  41. backend/modules/custom_fields/Controller.php +89 -87
  42. backend/modules/custom_fields/resources/js/custom_fields.js +2 -2
  43. backend/modules/custom_fields/templates/_services.php +31 -31
  44. backend/modules/custom_fields/templates/index.php +2 -7
  45. backend/modules/customers/Components.php +7 -7
  46. backend/modules/customers/Controller.php +234 -234
  47. backend/modules/customers/forms/Customer.php +25 -25
  48. backend/modules/customers/resources/js/customers.js +7 -7
  49. backend/modules/customers/resources/js/ng-customer_dialog.js +121 -121
  50. backend/modules/customers/templates/_customer_dialog.php +2 -6
  51. backend/modules/customers/templates/_import.php +35 -35
  52. backend/modules/customers/templates/index.php +11 -11
  53. backend/modules/debug/Controller.php +172 -51
  54. backend/modules/debug/resources/css/style.css +22 -0
  55. backend/modules/debug/resources/js/debug.js +6 -0
  56. backend/modules/debug/templates/index.php +28 -0
  57. backend/modules/notifications/Controller.php +109 -107
  58. backend/modules/notifications/forms/Notifications.php +214 -212
  59. backend/modules/notifications/resources/js/ng-app.js +8 -8
  60. backend/modules/notifications/resources/js/notification.js +33 -33
  61. backend/modules/notifications/templates/_codes.php +2 -1
  62. backend/modules/notifications/templates/_codes_cart.php +16 -16
  63. backend/modules/notifications/templates/_test_email_notifications_modal.php +19 -14
  64. backend/modules/notifications/templates/index.php +142 -150
  65. backend/modules/payments/Components.php +1 -1
  66. backend/modules/payments/Controller.php +265 -262
  67. backend/modules/payments/resources/js/ng-payment_details_dialog.js +3 -3
  68. backend/modules/payments/resources/js/payments.js +183 -183
  69. backend/modules/payments/templates/_payment_details_dialog.php +18 -20
  70. backend/modules/payments/templates/details.php +131 -131
  71. backend/modules/payments/templates/index.php +90 -89
  72. backend/modules/services/Controller.php +292 -287
  73. backend/modules/services/forms/Category.php +22 -22
  74. backend/modules/services/forms/Service.php +74 -51
  75. backend/modules/services/resources/js/service.js +511 -511
  76. backend/modules/services/templates/_list.php +258 -310
autoload.php CHANGED
@@ -1,19 +1,19 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Bookly Lite autoload.
5
- * @param $class
6
- */
7
- function bookly_lite_loader( $class )
8
- {
9
- if ( preg_match( '/^BooklyLite\\\\(.+)?([^\\\\]+)$/U', ltrim( $class, '\\' ), $match ) ) {
10
- $file = __DIR__ . DIRECTORY_SEPARATOR
11
- . strtolower( str_replace( '\\', DIRECTORY_SEPARATOR, preg_replace( '/([a-z])([A-Z])/', '$1_$2', $match[1] ) ) )
12
- . $match[2]
13
- . '.php';
14
- if ( is_readable( $file ) ) {
15
- require_once $file;
16
- }
17
- }
18
- }
19
  spl_autoload_register( 'bookly_lite_loader', true, true );
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+
3
+ /**
4
+ * BooklyLite autoload.
5
+ * @param $class
6
+ */
7
+ function bookly_lite_loader( $class )
8
+ {
9
+ if ( preg_match( '/^BooklyLite\\\\(.+)?([^\\\\]+)$/U', ltrim( $class, '\\' ), $match ) ) {
10
+ $file = __DIR__ . DIRECTORY_SEPARATOR
11
+ . strtolower( str_replace( '\\', DIRECTORY_SEPARATOR, preg_replace( '/([a-z])([A-Z])/', '$1_$2', $match[1] ) ) )
12
+ . $match[2]
13
+ . '.php';
14
+ if ( is_readable( $file ) ) {
15
+ require_once $file;
16
+ }
17
+ }
18
+ }
19
  spl_autoload_register( 'bookly_lite_loader', true, true );
backend/Backend.php CHANGED
@@ -1,131 +1,121 @@
1
- <?php
2
- namespace BooklyLite\Backend;
3
-
4
- use BooklyLite\Backend\Modules;
5
- use BooklyLite\Frontend;
6
- use BooklyLite\Lib;
7
-
8
- /**
9
- * Class Backend
10
- * @package BooklyLite\Backend
11
- */
12
- class Backend
13
- {
14
- public function __construct()
15
- {
16
- // Backend controllers.
17
- $this->apearanceController = Modules\Appearance\Controller::getInstance();
18
- $this->appointmentsController = Modules\Appointments\Controller::getInstance();
19
- $this->calendarController = Modules\Calendar\Controller::getInstance();
20
- $this->couponsController = Modules\Coupons\Controller::getInstance();
21
- $this->customerController = Modules\Customers\Controller::getInstance();
22
- $this->customFieldsController = Modules\CustomFields\Controller::getInstance();
23
- $this->debugController = Modules\Debug\Controller::getInstance();
24
- $this->notificationsController = Modules\Notifications\Controller::getInstance();
25
- $this->paymentController = Modules\Payments\Controller::getInstance();
26
- $this->serviceController = Modules\Services\Controller::getInstance();
27
- $this->settingsController = Modules\Settings\Controller::getInstance();
28
- $this->smsController = Modules\Sms\Controller::getInstance();
29
- $this->staffController = Modules\Staff\Controller::getInstance();
30
-
31
- // Frontend controllers that work via admin-ajax.php.
32
- $this->bookingController = Frontend\Modules\Booking\Controller::getInstance();
33
- $this->customerProfileController = Frontend\Modules\CustomerProfile\Controller::getInstance();
34
- if ( ! Lib\Config::isPaymentDisabled( Lib\Entities\Payment::TYPE_AUTHORIZENET ) ) {
35
- $this->authorizeNetController = Frontend\Modules\AuthorizeNet\Controller::getInstance();
36
- }
37
- if ( ! Lib\Config::isPaymentDisabled( Lib\Entities\Payment::TYPE_PAYULATAM ) ) {
38
- $this->payulatamController = Frontend\Modules\PayuLatam\Controller::getInstance();
39
- }
40
- if ( ! Lib\Config::isPaymentDisabled( Lib\Entities\Payment::TYPE_STRIPE ) ) {
41
- $this->stripeController = Frontend\Modules\Stripe\Controller::getInstance();
42
- }
43
- $this->wooCommerceController = Frontend\Modules\WooCommerce\Controller::getInstance();
44
-
45
- add_action( 'admin_menu', array( $this, 'addAdminMenu' ) );
46
- add_action( 'wp_loaded', array( $this, 'init' ) );
47
- add_action( 'admin_init', array( $this, 'addTinyMCEPlugin' ) );
48
- }
49
-
50
- public function init()
51
- {
52
- if ( ! session_id() ) {
53
- @session_start();
54
- }
55
- }
56
-
57
- public function addTinyMCEPlugin()
58
- {
59
- new Modules\TinyMce\Plugin();
60
- }
61
-
62
- public function addAdminMenu()
63
- {
64
- /** @var \WP_User $current_user */
65
- global $current_user;
66
-
67
- // Translated submenu pages.
68
- $calendar = __( 'Calendar', 'bookly' );
69
- $appointments = __( 'Appointments', 'bookly' );
70
- $staff_members = __( 'Staff Members', 'bookly' );
71
- $services = __( 'Services', 'bookly' );
72
- $sms = __( 'SMS Notifications', 'bookly' );
73
- $notifications = __( 'Email Notifications', 'bookly' );
74
- $customers = __( 'Customers', 'bookly' );
75
- $payments = __( 'Payments', 'bookly' );
76
- $appearance = __( 'Appearance', 'bookly' );
77
- $settings = __( 'Settings', 'bookly' );
78
- $coupons = __( 'Coupons', 'bookly' );
79
- $custom_fields = __( 'Custom Fields', 'bookly' );
80
-
81
- if ( $current_user->has_cap( 'administrator' ) || Lib\Entities\Staff::query()->where( 'wp_user_id', $current_user->ID )->count() ) {
82
- if ( function_exists( 'add_options_page' ) ) {
83
- $dynamic_position = '80.0000001' . mt_rand( 1, 1000 ); // position always is under `Settings`
84
- add_menu_page( 'Bookly', 'Bookly', 'read', 'ab-system', '',
85
- plugins_url( 'resources/images/menu.png', __FILE__ ), $dynamic_position );
86
- add_submenu_page( 'ab-system', $calendar, $calendar, 'read', 'ab-calendar',
87
- array( $this->calendarController, 'index' ) );
88
- add_submenu_page( 'ab-system', $appointments, $appointments, 'manage_options', 'ab-appointments',
89
- array( $this->appointmentsController, 'index' ) );
90
- do_action( 'bookly_render_menu_after_appointments' );
91
- if ( $current_user->has_cap( 'administrator' ) ) {
92
- add_submenu_page( 'ab-system', $staff_members, $staff_members, 'manage_options', Modules\Staff\Controller::page_slug,
93
- array( $this->staffController, 'index' ) );
94
- } else {
95
- if ( get_option( 'ab_settings_allow_staff_members_edit_profile' ) == 1 ) {
96
- add_submenu_page( 'ab-system', __( 'Profile', 'bookly' ), __( 'Profile', 'bookly' ), 'read', Modules\Staff\Controller::page_slug,
97
- array( $this->staffController, 'index' ) );
98
- }
99
- }
100
- add_submenu_page( 'ab-system', $services, $services, 'manage_options', Modules\Services\Controller::page_slug,
101
- array( $this->serviceController, 'index' ) );
102
- add_submenu_page( 'ab-system', $customers, $customers, 'manage_options', Modules\Customers\Controller::page_slug,
103
- array( $this->customerController, 'index' ) );
104
- add_submenu_page( 'ab-system', $notifications, $notifications, 'manage_options', 'ab-notifications',
105
- array( $this->notificationsController, 'index' ) );
106
- add_submenu_page( 'ab-system', $sms, $sms, 'manage_options', Modules\Sms\Controller::page_slug,
107
- array( $this->smsController, 'index' ) );
108
- add_submenu_page( 'ab-system', $payments, $payments, 'manage_options', 'ab-payments',
109
- array( $this->paymentController, 'index' ) );
110
- add_submenu_page( 'ab-system', $appearance, $appearance, 'manage_options', 'ab-appearance',
111
- array( $this->apearanceController, 'index' ) );
112
- add_submenu_page( 'ab-system', $custom_fields, $custom_fields, 'manage_options', 'ab-custom-fields',
113
- array( $this->customFieldsController, 'index' ) );
114
- add_submenu_page( 'ab-system', $coupons, $coupons, 'manage_options', 'ab-coupons',
115
- array( $this->couponsController, 'index' ) );
116
- add_submenu_page( 'ab-system', $settings, $settings, 'manage_options', Modules\Settings\Controller::page_slug,
117
- array( $this->settingsController, 'index' ) );
118
-
119
- if ( isset ( $_GET['page'] ) && $_GET['page'] == 'ab-debug' ) {
120
- add_submenu_page( 'ab-system', 'Debug', 'Debug', 'manage_options', 'ab-debug',
121
- array( $this->debugController, 'index' ) );
122
- }
123
-
124
- global $submenu;
125
- do_action( 'bookly_admin_menu', 'ab-system' );
126
- unset ( $submenu['ab-system'][0] );
127
- }
128
- }
129
- }
130
-
131
  }
1
+ <?php
2
+ namespace BooklyLite\Backend;
3
+
4
+ use BooklyLite\Backend\Modules;
5
+ use BooklyLite\Frontend;
6
+ use BooklyLite\Lib;
7
+
8
+ /**
9
+ * Class Backend
10
+ * @package BooklyLite\Backend
11
+ */
12
+ class Backend
13
+ {
14
+ public function __construct()
15
+ {
16
+ // Backend controllers.
17
+ $this->apearanceController = Modules\Appearance\Controller::getInstance();
18
+ $this->appointmentsController = Modules\Appointments\Controller::getInstance();
19
+ $this->calendarController = Modules\Calendar\Controller::getInstance();
20
+ $this->couponsController = Modules\Coupons\Controller::getInstance();
21
+ $this->customerController = Modules\Customers\Controller::getInstance();
22
+ $this->customFieldsController = Modules\CustomFields\Controller::getInstance();
23
+ $this->debugController = Modules\Debug\Controller::getInstance();
24
+ $this->notificationsController = Modules\Notifications\Controller::getInstance();
25
+ $this->paymentController = Modules\Payments\Controller::getInstance();
26
+ $this->serviceController = Modules\Services\Controller::getInstance();
27
+ $this->settingsController = Modules\Settings\Controller::getInstance();
28
+ $this->supportController = Modules\Support\Controller::getInstance();
29
+ $this->smsController = Modules\Sms\Controller::getInstance();
30
+ $this->staffController = Modules\Staff\Controller::getInstance();
31
+
32
+ // Frontend controllers that work via admin-ajax.php.
33
+ $this->bookingController = Frontend\Modules\Booking\Controller::getInstance();
34
+ $this->customerProfileController = Frontend\Modules\CustomerProfile\Controller::getInstance();
35
+
36
+ add_action( 'admin_menu', array( $this, 'addAdminMenu' ) );
37
+ add_action( 'wp_loaded', array( $this, 'init' ) );
38
+ add_action( 'admin_init', array( $this, 'addTinyMCEPlugin' ) );
39
+ }
40
+
41
+ public function init()
42
+ {
43
+ if ( ! session_id() ) {
44
+ @session_start();
45
+ }
46
+ }
47
+
48
+ public function addTinyMCEPlugin()
49
+ {
50
+ new Modules\TinyMce\Plugin();
51
+ }
52
+
53
+ public function addAdminMenu()
54
+ {
55
+ /** @var \WP_User $current_user */
56
+ global $current_user;
57
+
58
+ if ( $current_user->has_cap( 'administrator' ) || Lib\Entities\Staff::query()->where( 'wp_user_id', $current_user->ID )->count() ) {
59
+ $dynamic_position = '80.0000001' . mt_rand( 1, 1000 ); // position always is under `Settings`
60
+ add_menu_page( 'Bookly', 'Bookly', 'read', 'bookly-menu', '',
61
+ plugins_url( 'resources/images/menu.png', __FILE__ ), $dynamic_position );
62
+
63
+ // Translated submenu pages.
64
+ $calendar = __( 'Calendar', 'bookly' );
65
+ $appointments = __( 'Appointments', 'bookly' );
66
+ $staff_members = __( 'Staff Members', 'bookly' );
67
+ $services = __( 'Services', 'bookly' );
68
+ $sms = __( 'SMS Notifications', 'bookly' );
69
+ $notifications = __( 'Email Notifications', 'bookly' );
70
+ $customers = __( 'Customers', 'bookly' );
71
+ $payments = __( 'Payments', 'bookly' );
72
+ $appearance = __( 'Appearance', 'bookly' );
73
+ $settings = __( 'Settings', 'bookly' );
74
+ $coupons = __( 'Coupons', 'bookly' );
75
+ $custom_fields = __( 'Custom Fields', 'bookly' );
76
+
77
+ add_submenu_page( 'bookly-menu', $calendar, $calendar, 'read',
78
+ Modules\Calendar\Controller::page_slug, array( $this->calendarController, 'index' ) );
79
+ add_submenu_page( 'bookly-menu', $appointments, $appointments, 'manage_options',
80
+ Modules\Appointments\Controller::page_slug, array( $this->appointmentsController, 'index' ) );
81
+ do_action( 'bookly_render_menu_after_appointments' );
82
+ if ( $current_user->has_cap( 'administrator' ) ) {
83
+ add_submenu_page( 'bookly-menu', $staff_members, $staff_members, 'manage_options',
84
+ Modules\Staff\Controller::page_slug, array( $this->staffController, 'index' ) );
85
+ } else {
86
+ if ( get_option( 'bookly_gen_allow_staff_edit_profile' ) == 1 ) {
87
+ add_submenu_page( 'bookly-menu', __( 'Profile', 'bookly' ), __( 'Profile', 'bookly' ), 'read',
88
+ Modules\Staff\Controller::page_slug, array( $this->staffController, 'index' ) );
89
+ }
90
+ }
91
+ add_submenu_page( 'bookly-menu', $services, $services, 'manage_options',
92
+ Modules\Services\Controller::page_slug, array( $this->serviceController, 'index' ) );
93
+ add_submenu_page( 'bookly-menu', $customers, $customers, 'manage_options',
94
+ Modules\Customers\Controller::page_slug, array( $this->customerController, 'index' ) );
95
+ add_submenu_page( 'bookly-menu', $notifications, $notifications, 'manage_options',
96
+ Modules\Notifications\Controller::page_slug, array( $this->notificationsController, 'index' ) );
97
+ add_submenu_page( 'bookly-menu', $sms, $sms, 'manage_options',
98
+ Modules\Sms\Controller::page_slug, array( $this->smsController, 'index' ) );
99
+ add_submenu_page( 'bookly-menu', $payments, $payments, 'manage_options',
100
+ Modules\Payments\Controller::page_slug, array( $this->paymentController, 'index' ) );
101
+ add_submenu_page( 'bookly-menu', $appearance, $appearance, 'manage_options',
102
+ Modules\Appearance\Controller::page_slug, array( $this->apearanceController, 'index' ) );
103
+ add_submenu_page( 'bookly-menu', $custom_fields, $custom_fields, 'manage_options',
104
+ Modules\CustomFields\Controller::page_slug, array( $this->customFieldsController, 'index' ) );
105
+ add_submenu_page( 'bookly-menu', $coupons, $coupons, 'manage_options',
106
+ Modules\Coupons\Controller::page_slug, array( $this->couponsController, 'index' ) );
107
+ add_submenu_page( 'bookly-menu', $settings, $settings, 'manage_options',
108
+ Modules\Settings\Controller::page_slug, array( $this->settingsController, 'index' ) );
109
+
110
+ if ( isset ( $_GET['page'] ) && $_GET['page'] == 'bookly-debug' ) {
111
+ add_submenu_page( 'bookly-menu', 'Debug', 'Debug', 'manage_options',
112
+ Modules\Debug\Controller::page_slug, array( $this->debugController, 'index' ) );
113
+ }
114
+
115
+ global $submenu;
116
+ do_action( 'bookly_admin_menu', 'bookly-menu' );
117
+ unset ( $submenu['bookly-menu'][0] );
118
+ }
119
+ }
120
+
 
 
 
 
 
 
 
 
 
 
121
  }
backend/modules/appearance/Controller.php CHANGED
@@ -1,202 +1,194 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Appearance;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Appearance
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- /**
13
- * Default Action
14
- */
15
- public function index()
16
- {
17
- /** @var \WP_Locale $wp_locale */
18
- global $wp_locale;
19
-
20
- $this->enqueueStyles( array(
21
- 'frontend' => array_merge(
22
- ( get_option( 'ab_settings_phone_default_country' ) == 'disabled'
23
- ? array()
24
- : array( 'css/intlTelInput.css' ) ),
25
- array(
26
- 'css/ladda.min.css',
27
- 'css/picker.classic.css',
28
- 'css/picker.classic.date.css',
29
- 'css/bookly-main.css',
30
- )
31
- ),
32
- 'backend' => array(
33
- 'bootstrap/css/bootstrap-theme.min.css',
34
- 'css/bootstrap-editable.css',
35
- ),
36
- 'wp' => array( 'wp-color-picker' ),
37
- ) );
38
-
39
- $this->enqueueScripts( array(
40
- 'backend' => array(
41
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
42
- 'js/bootstrap-editable.min.js' => array( 'jquery' ),
43
- 'js/alert.js' => array( 'jquery' ),
44
- ),
45
- 'frontend' => array_merge(
46
- array(
47
- 'js/picker.js' => array( 'jquery' ),
48
- 'js/picker.date.js' => array( 'jquery' ),
49
- 'js/spin.min.js' => array( 'jquery' ),
50
- 'js/ladda.min.js' => array( 'jquery' ),
51
- ),
52
- get_option( 'ab_settings_phone_default_country' ) == 'disabled'
53
- ? array()
54
- : array( 'js/intlTelInput.min.js' => array( 'jquery' ) )
55
- ),
56
- 'wp' => array( 'wp-color-picker' ),
57
- 'module' => array( 'js/appearance.js' => array( 'jquery' ) )
58
- ) );
59
-
60
- wp_localize_script( 'ab-picker.date.js', 'BooklyL10n', array(
61
- 'today' => __( 'Today', 'bookly' ),
62
- 'months' => array_values( $wp_locale->month ),
63
- 'days' => array_values( $wp_locale->weekday_abbrev ),
64
- 'nextMonth' => __( 'Next month', 'bookly' ),
65
- 'prevMonth' => __( 'Previous month', 'bookly' ),
66
- 'date_format' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_PICKADATE ),
67
- 'start_of_week' => (int) get_option( 'start_of_week' ),
68
- 'saved' => __( 'Settings saved.', 'bookly' ),
69
- 'intlTelInput' => array(
70
- 'enabled' => ( get_option( 'ab_settings_phone_default_country' ) != 'disabled' ),
71
- 'utils' => plugins_url( 'intlTelInput.utils.js', Lib\Plugin::getDirectory() . '/frontend/resources/js/intlTelInput.utils.js' ),
72
- 'country' => get_option( 'ab_settings_phone_default_country' ),
73
- )
74
- ) );
75
-
76
- // Initialize steps (tabs).
77
- $this->steps = array(
78
- 1 => get_option( 'ab_appearance_text_step_service' ),
79
- get_option( 'ab_appearance_text_step_extras' ),
80
- get_option( 'ab_appearance_text_step_time' ),
81
- get_option( 'ab_appearance_text_step_cart' ),
82
- get_option( 'ab_appearance_text_step_details' ),
83
- get_option( 'ab_appearance_text_step_payment' ),
84
- get_option( 'ab_appearance_text_step_done' )
85
- );
86
-
87
- // Render general layout.
88
- $this->render( 'index' );
89
- }
90
-
91
- /**
92
- * Update options
93
- */
94
- public function executeUpdateAppearanceOptions()
95
- {
96
- if ( $this->hasParameter( 'options' ) ) {
97
- $get_option = $this->getParameter( 'options' );
98
- $options = array(
99
- // Info text.
100
- 'ab_appearance_text_info_cart_step' => $get_option['text_info_cart_step'],
101
- 'ab_appearance_text_info_complete_step' => $get_option['text_info_complete_step'],
102
- 'ab_appearance_text_info_coupon' => $get_option['text_info_coupon'],
103
- 'ab_appearance_text_info_details_step' => $get_option['text_info_details_step'],
104
- 'ab_appearance_text_info_details_step_guest' => $get_option['text_info_details_step_guest'],
105
- 'ab_appearance_text_info_payment_step' => $get_option['text_info_payment_step'],
106
- 'ab_appearance_text_info_service_step' => $get_option['text_info_service_step'],
107
- 'ab_appearance_text_info_time_step' => $get_option['text_info_time_step'],
108
- // Color.
109
- 'ab_appearance_color' => $get_option['color'],
110
- // Step, label and option texts.
111
- 'ab_appearance_text_button_apply' => $get_option['text_button_apply'],
112
- 'ab_appearance_text_button_back' => $get_option['text_button_back'],
113
- 'ab_appearance_text_button_book_more' => $get_option['text_button_book_more'],
114
- 'ab_appearance_text_button_next' => $get_option['text_button_next'],
115
- 'ab_appearance_text_label_category' => $get_option['text_label_category'],
116
- 'ab_appearance_text_label_ccard_code' => $get_option['text_label_ccard_code'],
117
- 'ab_appearance_text_label_ccard_expire' => $get_option['text_label_ccard_expire'],
118
- 'ab_appearance_text_label_ccard_number' => $get_option['text_label_ccard_number'],
119
- 'ab_appearance_text_label_coupon' => $get_option['text_label_coupon'],
120
- 'ab_appearance_text_label_email' => $get_option['text_label_email'],
121
- 'ab_appearance_text_label_employee' => $get_option['text_label_employee'],
122
- 'ab_appearance_text_label_finish_by' => $get_option['text_label_finish_by'],
123
- 'ab_appearance_text_label_name' => $get_option['text_label_name'],
124
- 'ab_appearance_text_label_number_of_persons' => $get_option['text_label_number_of_persons'],
125
- 'ab_appearance_text_label_pay_ccard' => $get_option['text_label_pay_ccard'],
126
- 'ab_appearance_text_label_pay_locally' => $get_option['text_label_pay_locally'],
127
- 'ab_appearance_text_label_pay_mollie' => $get_option['text_label_pay_mollie'],
128
- 'ab_appearance_text_label_pay_paypal' => $get_option['text_label_pay_paypal'],
129
- 'ab_appearance_text_label_phone' => $get_option['text_label_phone'],
130
- 'ab_appearance_text_label_select_date' => $get_option['text_label_select_date'],
131
- 'ab_appearance_text_label_service' => $get_option['text_label_service'],
132
- 'ab_appearance_text_label_start_from' => $get_option['text_label_start_from'],
133
- 'ab_appearance_text_option_category' => $get_option['text_option_category'],
134
- 'ab_appearance_text_option_employee' => $get_option['text_option_employee'],
135
- 'ab_appearance_text_option_service' => $get_option['text_option_service'],
136
- 'ab_appearance_text_step_cart' => $get_option['text_step_cart'],
137
- 'ab_appearance_text_step_details' => $get_option['text_step_details'],
138
- 'ab_appearance_text_step_done' => $get_option['text_step_done'],
139
- 'ab_appearance_text_step_payment' => $get_option['text_step_payment'],
140
- 'ab_appearance_text_step_service' => $get_option['text_step_service'],
141
- 'ab_appearance_text_step_time' => $get_option['text_step_time'],
142
- // Validator errors.
143
- 'ab_appearance_text_required_email' => $get_option['text_required_email'],
144
- 'ab_appearance_text_required_employee' => $get_option['text_required_employee'],
145
- 'ab_appearance_text_required_name' => $get_option['text_required_name'],
146
- 'ab_appearance_text_required_phone' => $get_option['text_required_phone'],
147
- 'ab_appearance_text_required_service' => $get_option['text_required_service'],
148
- // Checkboxes.
149
- 'ab_appearance_required_employee' => $get_option['required_employee'],
150
- 'ab_appearance_show_blocked_timeslots' => $get_option['blocked_timeslots'],
151
- 'ab_appearance_show_calendar' => $get_option['show_calendar'],
152
- 'ab_appearance_show_day_one_column' => $get_option['day_one_column'],
153
- 'ab_appearance_show_progress_tracker' => $get_option['progress_tracker'],
154
- 'ab_appearance_staff_name_with_price' => $get_option['staff_name_with_price'],
155
- );
156
-
157
- if ( Lib\Config::extrasEnabled() ) {
158
- $options['ab_appearance_text_info_extras_step'] = $get_option['text_info_extras_step'];
159
- $options['ab_appearance_text_step_extras'] = $get_option['text_step_extras'];
160
- }
161
- if ( Lib\Config::locationsEnabled() ) {
162
- $options['ab_appearance_required_location'] = $get_option['required_location'];
163
- $options['ab_appearance_text_label_location'] = $get_option['text_label_location'];
164
- $options['ab_appearance_text_option_location'] = $get_option['text_option_location'];
165
- $options['ab_appearance_text_required_location'] = $get_option['text_required_location'];
166
- }
167
- if ( Lib\Config::multiplyAppointmentsEnabled() ) {
168
- $options['ab_appearance_text_label_multiply'] = $get_option['text_label_multiply'];
169
- }
170
-
171
- // Save options.
172
- foreach ( $options as $option_name => $option_value ) {
173
- update_option( $option_name, $option_value );
174
- // Register string for translate in WPML.
175
- if ( strpos( $option_name, 'ab_appearance_text_' ) === 0 ) {
176
- do_action( 'wpml_register_single_string', 'bookly', $option_name, $option_value );
177
- }
178
- }
179
- }
180
- exit;
181
- }
182
-
183
- /**
184
- * Ajax request to dismiss appearance notice for current user.
185
- */
186
- public function executeDismissAppearanceNotice()
187
- {
188
- update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_appearance_notice', 1 );
189
- }
190
-
191
- /**
192
- * Override parent method to add 'wp_ajax_ab_' prefix
193
- * so current 'execute*' methods look nicer.
194
- *
195
- * @param string $prefix
196
- */
197
- protected function registerWpActions( $prefix = '' )
198
- {
199
- parent::registerWpActions( 'wp_ajax_ab_' );
200
- }
201
-
202
  }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Appearance;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Controller
8
+ * @package BooklyLite\Backend\Modules\Appearance
9
+ */
10
+ class Controller extends Lib\Base\Controller
11
+ {
12
+ const page_slug = 'bookly-appearance';
13
+
14
+ /**
15
+ * Default Action
16
+ */
17
+ public function index()
18
+ {
19
+ /** @var \WP_Locale $wp_locale */
20
+ global $wp_locale;
21
+
22
+ $this->enqueueStyles( array(
23
+ 'frontend' => array_merge(
24
+ ( get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
25
+ ? array()
26
+ : array( 'css/intlTelInput.css' ) ),
27
+ array(
28
+ 'css/ladda.min.css',
29
+ 'css/picker.classic.css',
30
+ 'css/picker.classic.date.css',
31
+ 'css/bookly-main.css',
32
+ )
33
+ ),
34
+ 'backend' => array(
35
+ 'bootstrap/css/bootstrap-theme.min.css',
36
+ 'css/bootstrap-editable.css',
37
+ ),
38
+ 'wp' => array( 'wp-color-picker' ),
39
+ ) );
40
+
41
+ $this->enqueueScripts( array(
42
+ 'backend' => array(
43
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
44
+ 'js/bootstrap-editable.min.js' => array( 'jquery' ),
45
+ 'js/alert.js' => array( 'jquery' ),
46
+ ),
47
+ 'frontend' => array_merge(
48
+ array(
49
+ 'js/picker.js' => array( 'jquery' ),
50
+ 'js/picker.date.js' => array( 'jquery' ),
51
+ 'js/spin.min.js' => array( 'jquery' ),
52
+ 'js/ladda.min.js' => array( 'jquery' ),
53
+ ),
54
+ get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
55
+ ? array()
56
+ : array( 'js/intlTelInput.min.js' => array( 'jquery' ) )
57
+ ),
58
+ 'wp' => array( 'wp-color-picker' ),
59
+ 'module' => array( 'js/appearance.js' => array( 'jquery' ) )
60
+ ) );
61
+
62
+ wp_localize_script( 'bookly-picker.date.js', 'BooklyL10n', array(
63
+ 'today' => __( 'Today', 'bookly' ),
64
+ 'months' => array_values( $wp_locale->month ),
65
+ 'days' => array_values( $wp_locale->weekday_abbrev ),
66
+ 'nextMonth' => __( 'Next month', 'bookly' ),
67
+ 'prevMonth' => __( 'Previous month', 'bookly' ),
68
+ 'date_format' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_PICKADATE ),
69
+ 'start_of_week' => (int) get_option( 'start_of_week' ),
70
+ 'saved' => __( 'Settings saved.', 'bookly' ),
71
+ 'intlTelInput' => array(
72
+ 'enabled' => get_option( 'bookly_cst_phone_default_country' ) != 'disabled',
73
+ 'utils' => plugins_url( 'intlTelInput.utils.js', Lib\Plugin::getDirectory() . '/frontend/resources/js/intlTelInput.utils.js' ),
74
+ 'country' => get_option( 'bookly_cst_phone_default_country' ),
75
+ )
76
+ ) );
77
+
78
+ // Initialize steps (tabs).
79
+ $this->steps = array(
80
+ 1 => get_option( 'bookly_l10n_step_service' ),
81
+ get_option( 'bookly_l10n_step_extras' ),
82
+ get_option( 'bookly_l10n_step_time' ),
83
+ get_option( 'bookly_l10n_step_repeat' ),
84
+ get_option( 'bookly_l10n_step_cart' ),
85
+ get_option( 'bookly_l10n_step_details' ),
86
+ get_option( 'bookly_l10n_step_payment' ),
87
+ get_option( 'bookly_l10n_step_done' )
88
+ );
89
+
90
+ // Render general layout.
91
+ $this->render( 'index' );
92
+ }
93
+
94
+ /**
95
+ * Update options
96
+ */
97
+ public function executeUpdateAppearanceOptions()
98
+ {
99
+ if ( $this->hasParameter( 'options' ) ) {
100
+ $get_option = $this->getParameter( 'options' );
101
+ $options = array(
102
+ // Info text.
103
+ 'bookly_l10n_info_cart_step' => $get_option['text_info_cart_step'],
104
+ 'bookly_l10n_info_complete_step' => $get_option['text_info_complete_step'],
105
+ 'bookly_l10n_info_coupon' => $get_option['text_info_coupon'],
106
+ 'bookly_l10n_info_details_step' => $get_option['text_info_details_step'],
107
+ 'bookly_l10n_info_details_step_guest' => $get_option['text_info_details_step_guest'],
108
+ 'bookly_l10n_info_payment_step' => $get_option['text_info_payment_step'],
109
+ 'bookly_l10n_info_service_step' => $get_option['text_info_service_step'],
110
+ 'bookly_l10n_info_time_step' => $get_option['text_info_time_step'],
111
+ // Step, label and option texts.
112
+ 'bookly_l10n_button_apply' => $get_option['text_button_apply'],
113
+ 'bookly_l10n_button_back' => $get_option['text_button_back'],
114
+ 'bookly_l10n_button_book_more' => $get_option['text_button_book_more'],
115
+ 'bookly_l10n_button_next' => $get_option['text_button_next'],
116
+ 'bookly_l10n_label_category' => $get_option['text_label_category'],
117
+ 'bookly_l10n_label_ccard_code' => $get_option['text_label_ccard_code'],
118
+ 'bookly_l10n_label_ccard_expire' => $get_option['text_label_ccard_expire'],
119
+ 'bookly_l10n_label_ccard_number' => $get_option['text_label_ccard_number'],
120
+ 'bookly_l10n_label_coupon' => $get_option['text_label_coupon'],
121
+ 'bookly_l10n_label_email' => $get_option['text_label_email'],
122
+ 'bookly_l10n_label_employee' => $get_option['text_label_employee'],
123
+ 'bookly_l10n_label_finish_by' => $get_option['text_label_finish_by'],
124
+ 'bookly_l10n_label_name' => $get_option['text_label_name'],
125
+ 'bookly_l10n_label_number_of_persons' => $get_option['text_label_number_of_persons'],
126
+ 'bookly_l10n_label_pay_ccard' => $get_option['text_label_pay_ccard'],
127
+ 'bookly_l10n_label_pay_locally' => $get_option['text_label_pay_locally'],
128
+ 'bookly_l10n_label_pay_mollie' => $get_option['text_label_pay_mollie'],
129
+ 'bookly_l10n_label_pay_paypal' => $get_option['text_label_pay_paypal'],
130
+ 'bookly_l10n_label_phone' => $get_option['text_label_phone'],
131
+ 'bookly_l10n_label_select_date' => $get_option['text_label_select_date'],
132
+ 'bookly_l10n_label_service' => $get_option['text_label_service'],
133
+ 'bookly_l10n_label_start_from' => $get_option['text_label_start_from'],
134
+ 'bookly_l10n_option_category' => $get_option['text_option_category'],
135
+ 'bookly_l10n_option_employee' => $get_option['text_option_employee'],
136
+ 'bookly_l10n_option_service' => $get_option['text_option_service'],
137
+ 'bookly_l10n_step_cart' => $get_option['text_step_cart'],
138
+ 'bookly_l10n_step_details' => $get_option['text_step_details'],
139
+ 'bookly_l10n_step_done' => $get_option['text_step_done'],
140
+ 'bookly_l10n_step_payment' => $get_option['text_step_payment'],
141
+ 'bookly_l10n_step_service' => $get_option['text_step_service'],
142
+ 'bookly_l10n_step_time' => $get_option['text_step_time'],
143
+ // Validator errors.
144
+ 'bookly_l10n_required_email' => $get_option['text_required_email'],
145
+ 'bookly_l10n_required_employee' => $get_option['text_required_employee'],
146
+ 'bookly_l10n_required_name' => $get_option['text_required_name'],
147
+ 'bookly_l10n_required_phone' => $get_option['text_required_phone'],
148
+ 'bookly_l10n_required_service' => $get_option['text_required_service'],
149
+ // Color.
150
+ 'bookly_app_color' => $get_option['color'],
151
+ // Checkboxes.
152
+ 'bookly_app_required_employee' => $get_option['required_employee'],
153
+ 'bookly_app_show_blocked_timeslots' => $get_option['blocked_timeslots'],
154
+ 'bookly_app_show_calendar' => $get_option['show_calendar'],
155
+ 'bookly_app_show_day_one_column' => $get_option['day_one_column'],
156
+ 'bookly_app_show_progress_tracker' => $get_option['progress_tracker'],
157
+ 'bookly_app_staff_name_with_price' => $get_option['staff_name_with_price'],
158
+ );
159
+
160
+ $options = apply_filters( 'bookly_prepare_appearance_settings', $options, $get_option );
161
+
162
+ // Save options.
163
+ foreach ( $options as $option_name => $option_value ) {
164
+ update_option( $option_name, $option_value );
165
+ // Register string for translate in WPML.
166
+ if ( strpos( $option_name, 'bookly_l10n_' ) === 0 ) {
167
+ do_action( 'wpml_register_single_string', 'bookly', $option_name, $option_value );
168
+ }
169
+ }
170
+ }
171
+
172
+ wp_send_json_success();
173
+ }
174
+
175
+ /**
176
+ * Ajax request to dismiss appearance notice for current user.
177
+ */
178
+ public function executeDismissAppearanceNotice()
179
+ {
180
+ update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_appearance_notice', 1 );
181
+ }
182
+
183
+ /**
184
+ * Override parent method to add 'wp_ajax_bookly_' prefix
185
+ * so current 'execute*' methods look nicer.
186
+ *
187
+ * @param string $prefix
188
+ */
189
+ protected function registerWpActions( $prefix = '' )
190
+ {
191
+ parent::registerWpActions( 'wp_ajax_bookly_' );
192
+ }
193
+
 
 
 
 
 
 
 
 
194
  }
backend/modules/appearance/lib/Helper.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Appearance\Lib;
3
+
4
+ class Helper
5
+ {
6
+ /**
7
+ * Render ui for editing frontend messages. <span>
8
+ *
9
+ * @param array $data l10n option name
10
+ * @param null $mirror_class the updated value will be displayed also for the objects with css class
11
+ */
12
+ public static function renderSpan( array $data, $mirror_class = null )
13
+ {
14
+ $id = $data[0];
15
+ $options = array();
16
+ foreach ( $data as $option_name ) {
17
+ $options[ $option_name ] = get_option( $option_name );
18
+ }
19
+ if ( count( $options ) > 1 ) {
20
+ printf( '<span id="%s" data-options-default=\'%s\' data-type="multiple"%s%s>%s</span>',
21
+ $id, json_encode( $options ), $mirror_class ? ' class="' . $mirror_class . '"' : '',
22
+ $mirror_class ? ' data-mirror="' . $mirror_class . '"' : '', esc_html( $options[ $id ] )
23
+ );
24
+ } else {
25
+ printf( '<span id="%s" data-option-default=\'%s\' data-type="text" class="bookly-editable%s"%s>%s</span>',
26
+ $id, esc_attr( $options[ $id ] ), $mirror_class ? ' ' . $mirror_class : '',
27
+ $mirror_class ? ' data-mirror="' . $mirror_class . '"' : '', esc_html( $options[ $id ] )
28
+ );
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Render ui for editing frontend messages. <label>
34
+ *
35
+ * @param array $data l10n option name
36
+ * @param null $mirror_class the updated value will be displayed also for the objects with css class
37
+ */
38
+ public static function renderLabel( array $data, $mirror_class = null )
39
+ {
40
+ $id = $data[0];
41
+ $options = array();
42
+ foreach ( $data as $option_name ) {
43
+ $options[ $option_name ] = get_option( $option_name );
44
+ }
45
+ if ( count( $options ) > 1 ) {
46
+ printf( '<label id="%s" data-type="multiple" data-options-default=\'%s\'%s%s>%s</label>',
47
+ $id, json_encode( $options ), $mirror_class ? ' class="' . $mirror_class . '"' : '',
48
+ $mirror_class ? ' data-mirror="' . $mirror_class . '"' : '', esc_html( $options[ $id ] )
49
+ );
50
+ } else {
51
+ printf( '<label id="%s" data-option-default=\'%s\' data-type="text" class="bookly-editable%s"%s>%s</label>',
52
+ $id, esc_attr( $options[ $id ] ), $mirror_class ? ' ' . $mirror_class : '',
53
+ $mirror_class ? ' data-mirror="' . $mirror_class . '"' : '', esc_html( $options[ $id ] )
54
+ );
55
+ }
56
+ }
57
+
58
+ }
backend/modules/appearance/resources/js/appearance.js CHANGED
@@ -1,583 +1,598 @@
1
- jQuery(function($) {
2
- var // Progress Tracker.
3
- $progress_tracker_option = $('input#ab-progress-tracker-checkbox'),
4
- $staff_name_with_price_option = $('input#ab-staff-name-with-price-checkbox'),
5
- // Time slots setting.
6
- $blocked_timeslots_option = $('input#ab-blocked-timeslots-checkbox'),
7
- $day_one_column_option = $('input#ab-day-one-column-checkbox'),
8
- $show_calendar_option = $('input#ab-show-calendar-checkbox'),
9
- $required_employee_option = $('input#ab-required-employee-checkbox'),
10
- $required_location_option = $('input#ab-required-location-checkbox'),
11
- // Buttons.
12
- $save_button = $('#ajax-send-appearance'),
13
- $reset_button = $('button[type=reset]'),
14
- // Texts.
15
- $text_step_service = $('#ab-text-step-service'),
16
- $text_step_extras = $('#ab-text-step-extras'),
17
- $text_step_time = $('#ab-text-step-time'),
18
- $text_step_cart = $('#ab-text-step-cart'),
19
- $text_step_details = $('#ab-text-step-details'),
20
- $text_step_payment = $('#ab-text-step-payment'),
21
- $text_step_done = $('#ab-text-step-done'),
22
- $text_label_location = $('#ab-text-label-location'),
23
- $text_label_multiply = $('#ab-text-label-multiply'),
24
- $text_label_category = $('#ab-text-label-category'),
25
- $text_option_location = $('#ab-text-option-location'),
26
- $text_option_category = $('#ab-text-option-category'),
27
- $text_option_service = $('#ab-text-option-service'),
28
- $text_option_employee = $('#ab-text-option-employee'),
29
- $text_label_service = $('#ab-text-label-service'),
30
- $text_label_number_of_persons = $('#ab-text-label-number-of-persons'),
31
- $text_label_employee = $('#ab-text-label-employee'),
32
- $text_label_select_date = $('#ab-text-label-select_date'),
33
- $text_label_start_from = $('#ab-text-label-start_from'),
34
- $text_button_next = $('#ab-text-button-next'),
35
- $text_button_back = $('#ab-text-button-back'),
36
- $text_button_book_more = $('#ab-text-button-book-more'),
37
- $text_button_apply = $('#ab-text-button-apply'),
38
- $text_label_finish_by = $('#ab-text-label-finish_by'),
39
- $text_label_name = $('#ab-text-label-name'),
40
- $text_label_phone = $('#ab-text-label-phone'),
41
- $text_label_email = $('#ab-text-label-email'),
42
- $text_label_coupon = $('#ab-text-label-coupon'),
43
- $text_info_service = $('#ab-text-info-service'),
44
- $text_info_extras = $('#ab-text-info-extras'),
45
- $text_info_time = $('#ab-text-info-time'),
46
- $text_info_cart = $('#ab-text-info-cart'),
47
- $text_info_details = $('#ab-text-info-details'),
48
- $text_info_details_guest = $('#ab-text-info-details-guest'),
49
- $text_info_coupon = $('#ab-text-info-coupon'),
50
- $text_info_payment = $('#ab-text-info-payment'),
51
- $text_info_complete = $('#ab-text-info-complete'),
52
- $text_label_pay_paypal = $('#ab-text-label-pay-paypal'),
53
- $text_label_pay_ccard = $('#ab-text-label-pay-ccard'),
54
- $text_label_ccard_number = $('#ab-text-label-ccard-number'),
55
- $text_label_ccard_expire = $('#ab-text-label-ccard-expire'),
56
- $text_label_ccard_code = $('#ab-text-label-ccard-code'),
57
- $color_picker = $('.bookly-js-color-picker'),
58
- $ab_editable = $('.ab_editable'),
59
- $text_label_pay_locally = $('#ab-text-label-pay-locally'),
60
- $text_label_pay_mollie = $('#ab-text-label-pay-mollie'),
61
- // Calendars.
62
- $second_step_calendar = $('.ab-selected-date'),
63
- $second_step_calendar_wrap = $('.ab-slot-calendar'),
64
- // Step settings.
65
- $step_settings = $('#bookly-js-step-settings')
66
- ;
67
-
68
- if (BooklyL10n.intlTelInput.enabled) {
69
- $('.ab-user-phone').intlTelInput({
70
- preferredCountries: [BooklyL10n.intlTelInput.country],
71
- defaultCountry: BooklyL10n.intlTelInput.country,
72
- geoIpLookup: function (callback) {
73
- $.get(ajaxurl, {action: 'ab_ip_info'}, function () {
74
- }, 'json').always(function (resp) {
75
- var countryCode = (resp && resp.country) ? resp.country : '';
76
- callback(countryCode);
77
- });
78
- },
79
- utilsScript: BooklyL10n.intlTelInput.utils
80
- });
81
- }
82
-
83
- $staff_name_with_price_option.on('change', function () {
84
- var staff = $('.ab-select-employee').val();
85
- if (staff) {
86
- $('.ab-select-employee').val(staff * -1);
87
- }
88
- $('.employee-name-price').toggle($staff_name_with_price_option.prop("checked"));
89
- $('.employee-name').toggle(!$staff_name_with_price_option.prop("checked"));
90
- }).trigger('change');
91
-
92
- // menu fix for WP 3.8.1
93
- $('#toplevel_page_ab-system > ul').css('margin-left', '0px');
94
-
95
- // Tabs.
96
- $('li.bookly-nav-item').on('shown.bs.tab', function (e) {
97
- $step_settings.children().hide();
98
- switch (e.target.getAttribute('data-target')) {
99
- case '#ab-step-1': $step_settings.find('#bookly-js-step-service').show(); break;
100
- case '#ab-step-3': $step_settings.find('#bookly-js-step-time').show(); break;
101
- }
102
- });
103
-
104
- function getEditableValue(val) {
105
- return $.trim(val == 'Empty' ? '' : val);
106
- }
107
- // Apply color from color picker.
108
- var applyColor = function() {
109
- var color_important = $color_picker.wpColorPicker('color') + '!important';
110
- $('.ab-progress-tracker').find('.active').css('color', $color_picker.wpColorPicker('color')).find('.step').css('background', $color_picker.wpColorPicker('color'));
111
- $('.ab-mobile-step_1 label').css('color', $color_picker.wpColorPicker('color'));
112
- $('.bookly-js-actions > a').css('background-color', $color_picker.wpColorPicker('color'));
113
- $('.ab-next-step, .ab-mobile-next-step').css('background', $color_picker.wpColorPicker('color'));
114
- $('.ab-week-days label').css('background-color', $color_picker.wpColorPicker('color'));
115
- $('.picker__frame').attr('style', 'background: ' + color_important);
116
- $('.picker__header').attr('style', 'border-bottom: ' + '1px solid ' + color_important);
117
- $('.picker__day').mouseenter(function(){
118
- $(this).attr('style', 'color: ' + color_important);
119
- }).mouseleave(function(){ $(this).attr('style', $(this).hasClass('picker__day--selected') ? 'color: ' + color_important : '') });
120
- $('.picker__day--selected').attr('style', 'color: ' + color_important);
121
- $('.picker__button--clear').attr('style', 'color: ' + color_important);
122
- $('.picker__button--today').attr('style', 'color: ' + color_important);
123
- $('.ab-extra-step .bookly-extras-thumb.bookly-extras-selected').css('border-color', $color_picker.wpColorPicker('color'));
124
- $('.ab-columnizer .ab-day').css({
125
- 'background': $color_picker.wpColorPicker('color'),
126
- 'border-color': $color_picker.wpColorPicker('color')
127
- });
128
- $('.ab-columnizer .ab-hour').off().hover(
129
- function() { // mouse-on
130
- $(this).css({
131
- 'color': $color_picker.wpColorPicker('color'),
132
- 'border': '2px solid ' + $color_picker.wpColorPicker('color')
133
- });
134
- $(this).find('.ab-hour-icon').css({
135
- 'border-color': $color_picker.wpColorPicker('color'),
136
- 'color': $color_picker.wpColorPicker('color')
137
- });
138
- $(this).find('.ab-hour-icon > span').css({
139
- 'background': $color_picker.wpColorPicker('color')
140
- });
141
- },
142
- function() { // mouse-out
143
- $(this).css({
144
- 'color': '#333333',
145
- 'border': '1px solid #cccccc'
146
- });
147
- $(this).find('.ab-hour-icon').css({
148
- 'border-color': '#333333',
149
- 'color': '#cccccc'
150
- });
151
- $(this).find('.ab-hour-icon > span').css({
152
- 'background': '#cccccc'
153
- });
154
- }
155
- );
156
- $('.ab-details-step label').css('color', $color_picker.wpColorPicker('color'));
157
- $('.ab-card-form label').css('color', $color_picker.wpColorPicker('color'));
158
- $('.ab-nav-tabs .ladda-button, .ab-nav-steps .ladda-button, .ab-btn, .bookly-round-button').css('background-color', $color_picker.wpColorPicker('color'));
159
- $('.ab-back-step, .ab-next-step').css('background', $color_picker.wpColorPicker('color'));
160
- var style_arrow = '.picker__nav--next:before { border-left: 6px solid ' + $color_picker.wpColorPicker('color') + '!important; } .picker__nav--prev:before { border-right: 6px solid ' + $color_picker.wpColorPicker('color') + '!important; }';
161
- $('#ab--style-arrow').html(style_arrow);
162
- };
163
- $color_picker.wpColorPicker({
164
- change : applyColor
165
- });
166
- // Init calendars.
167
- $('.ab-date-from').pickadate({
168
- formatSubmit : 'yyyy-mm-dd',
169
- format : BooklyL10n.date_format,
170
- min : true,
171
- clear : false,
172
- close : false,
173
- today : BooklyL10n.today,
174
- weekdaysShort : BooklyL10n.days,
175
- monthsFull : BooklyL10n.months,
176
- labelMonthNext : BooklyL10n.nextMonth,
177
- labelMonthPrev : BooklyL10n.prevMonth,
178
- onRender : applyColor,
179
- firstDay : BooklyL10n.start_of_week == 1
180
- });
181
-
182
- $second_step_calendar.pickadate({
183
- formatSubmit : 'yyyy-mm-dd',
184
- format : BooklyL10n.date_format,
185
- min : true,
186
- weekdaysShort : BooklyL10n.days,
187
- monthsFull : BooklyL10n.months,
188
- labelMonthNext : BooklyL10n.nextMonth,
189
- labelMonthPrev : BooklyL10n.prevMonth,
190
- close : false,
191
- clear : false,
192
- today : false,
193
- closeOnSelect : false,
194
- onRender : applyColor,
195
- firstDay : BooklyL10n.start_of_week == 1,
196
- klass : {
197
- picker: 'picker picker--opened picker--focused'
198
- },
199
- onClose : function() {
200
- this.open(false);
201
- }
202
- });
203
- $second_step_calendar_wrap.find('.picker__holder').css({ top : '0px', left : '0px' });
204
- $second_step_calendar_wrap.toggle($show_calendar_option.prop('checked'));
205
-
206
- // Update options.
207
- $save_button.on('click', function(e) {
208
- e.preventDefault();
209
- var data = {
210
- action: 'ab_update_appearance_options',
211
- options: {
212
- // Color.
213
- 'color' : $color_picker.wpColorPicker('color'),
214
- // Info text.
215
- 'text_info_service_step' : getEditableValue($text_info_service.text()),
216
- 'text_info_extras_step' : getEditableValue($text_info_extras.text()),
217
- 'text_info_time_step' : getEditableValue($text_info_time.text()),
218
- 'text_info_cart_step' : getEditableValue($text_info_cart.text()),
219
- 'text_info_details_step' : getEditableValue($text_info_details.text()),
220
- 'text_info_details_step_guest' : getEditableValue($text_info_details_guest.text()),
221
- 'text_info_payment_step' : getEditableValue($text_info_payment.text()),
222
- 'text_info_complete_step' : getEditableValue($text_info_complete.text()),
223
- 'text_info_coupon' : getEditableValue($text_info_coupon.text()),
224
- // Step and label texts.
225
- 'text_step_service' : getEditableValue($text_step_service.text()),
226
- 'text_step_extras' : getEditableValue($text_step_extras.text()),
227
- 'text_step_time' : getEditableValue($text_step_time.text()),
228
- 'text_step_cart' : getEditableValue($text_step_cart.text()),
229
- 'text_step_details' : getEditableValue($text_step_details.text()),
230
- 'text_step_payment' : getEditableValue($text_step_payment.text()),
231
- 'text_step_done' : getEditableValue($text_step_done.text()),
232
- 'text_label_location' : getEditableValue($text_label_location.text()),
233
- 'text_label_category' : getEditableValue($text_label_category.text()),
234
- 'text_label_service' : getEditableValue($text_label_service.text()),
235
- 'text_label_number_of_persons' : getEditableValue($text_label_number_of_persons.text()),
236
- 'text_label_multiply' : getEditableValue($text_label_multiply.text()),
237
- 'text_label_employee' : getEditableValue($text_label_employee.text()),
238
- 'text_label_select_date' : getEditableValue($text_label_select_date.text()),
239
- 'text_label_start_from' : getEditableValue($text_label_start_from.text()),
240
- 'text_button_next' : getEditableValue($text_button_next.text()),
241
- 'text_button_back' : getEditableValue($text_button_back.text()),
242
- 'text_button_apply' : getEditableValue($text_button_apply.text()),
243
- 'text_button_book_more' : getEditableValue($text_button_book_more.text()),
244
- 'text_label_finish_by' : getEditableValue($text_label_finish_by.text()),
245
- 'text_label_name' : getEditableValue($text_label_name.text()),
246
- 'text_label_phone' : getEditableValue($text_label_phone.text()),
247
- 'text_label_email' : getEditableValue($text_label_email.text()),
248
- 'text_label_coupon' : getEditableValue($text_label_coupon.text()),
249
- 'text_option_location' : getEditableValue($text_option_location.text()),
250
- 'text_option_category' : getEditableValue($text_option_category.text()),
251
- 'text_option_service' : getEditableValue($text_option_service.text()),
252
- 'text_option_employee' : getEditableValue($text_option_employee.text()),
253
- 'text_label_pay_locally' : getEditableValue($text_label_pay_locally.text()),
254
- 'text_label_pay_mollie' : getEditableValue($text_label_pay_mollie.text()),
255
- 'text_label_pay_paypal' : getEditableValue($text_label_pay_paypal.text()),
256
- 'text_label_pay_ccard' : getEditableValue($text_label_pay_ccard.text()),
257
- 'text_label_ccard_number' : getEditableValue($text_label_ccard_number.text()),
258
- 'text_label_ccard_expire' : getEditableValue($text_label_ccard_expire.text()),
259
- 'text_label_ccard_code' : getEditableValue($text_label_ccard_code.text()),
260
- // Validator.
261
- 'text_required_location' : getEditableValue($('#ab_appearance_text_required_location').html()),
262
- 'text_required_service' : getEditableValue($('#ab_appearance_text_required_service').html()),
263
- 'text_required_employee' : getEditableValue($('#ab_appearance_text_required_employee').html()),
264
- 'text_required_name' : getEditableValue($('#ab_appearance_text_required_name').html()),
265
- 'text_required_phone' : getEditableValue($('#ab_appearance_text_required_phone').html()),
266
- 'text_required_email' : getEditableValue($('#ab_appearance_text_required_email').html()),
267
- // Checkboxes.
268
- 'progress_tracker' : Number($progress_tracker_option.prop('checked')),
269
- 'staff_name_with_price': Number($staff_name_with_price_option.prop('checked')),
270
- 'blocked_timeslots' : Number($blocked_timeslots_option.prop('checked')),
271
- 'day_one_column' : Number($day_one_column_option.prop('checked')),
272
- 'show_calendar' : Number($show_calendar_option.prop('checked')),
273
- 'required_employee' : Number($required_employee_option.prop('checked')),
274
- 'required_location' : Number($required_location_option.prop('checked'))
275
- } // options
276
- }; // data
277
-
278
- // update data and show spinner while updating
279
- var ladda = Ladda.create(this);
280
- ladda.start();
281
- $.post(ajaxurl, data, function (response) {
282
- ladda.stop();
283
- booklyAlert({success : [BooklyL10n.saved]});
284
- });
285
- });
286
-
287
- // Reset options to defaults.
288
- $reset_button.on('click', function() {
289
- // Reset color.
290
- $color_picker.wpColorPicker('color', $color_picker.data('selected'));
291
-
292
- // Reset texts.
293
- jQuery.each($('.editable'), function() {
294
- $(this).text($(this).data('default')); //default value for texts
295
- $(this).editable('setValue', $(this).data('default')); // default value for editable inputs
296
- });
297
-
298
- // Reset texts.
299
- jQuery.each($('.ab-service-list, .ab-employee-list'), function() {
300
- $(this).html($(this).data('default')); //default value
301
- });
302
-
303
- // default value for multiple inputs
304
-
305
- $text_label_location.editable('setValue', {
306
- label: $text_label_location.text(),
307
- option: $text_option_location.text(),
308
- id_option: $text_label_location.data('option-id'),
309
- extended: true,
310
- option2: $text_label_location.data('default-error'),
311
- id_option2: $text_label_location.data('error-id')
312
- });
313
-
314
- $text_label_category.editable('setValue', {
315
- label: $text_label_category.text(),
316
- option: $text_option_category.text(),
317
- id_option: $text_label_category.data('option-id')
318
- });
319
-
320
- $text_label_service.editable('setValue', {
321
- label: $text_label_service.text(),
322
- option: $text_option_service.text(),
323
- id_option: $text_label_service.data('option-id'),
324
- extended: true,
325
- option2: $text_label_service.data('default-error'),
326
- id_option2: $text_label_service.data('error-id')
327
- });
328
-
329
- $text_label_employee.editable('setValue', {
330
- label: $text_label_employee.text(),
331
- option: $text_option_employee.text(),
332
- id_option: $text_label_employee.data('option-id'),
333
- extended: true,
334
- option2: $text_label_employee.data('default-error'),
335
- id_option2: $text_label_employee.data('error-id')
336
- });
337
-
338
- $text_label_name.editable('setValue', {
339
- label: $text_label_name.text(),
340
- option: $text_label_name.data('default-error'),
341
- id_option: $text_label_name.data('option-id')
342
- });
343
-
344
- $text_label_phone.editable('setValue', {
345
- label: $text_label_phone.text(),
346
- option: $text_label_phone.data('default-error'),
347
- id_option: $text_label_phone.data('option-id')
348
- });
349
-
350
- $text_label_email.editable('setValue', {
351
- label: $text_label_email.text(),
352
- option: $text_label_email.data('default-error'),
353
- id_option: $text_label_email.data('option-id')
354
- });
355
- });
356
-
357
- $progress_tracker_option.change(function(){
358
- $('.ab-progress-tracker').toggle($(this).is(':checked'));
359
- }).trigger('change');
360
-
361
- var day_one_column = $('.ab-day-one-column'),
362
- day_columns = $('.ab-day-columns');
363
-
364
- if ($show_calendar_option.prop('checked')) {
365
- $second_step_calendar_wrap.show();
366
- day_columns.find('.col3,.col4,.col5,.col6,.col7').hide();
367
- day_columns.find('.col2 button:gt(0)').attr('style', 'display: none !important');
368
- day_one_column.find('.col2,.col3,.col4,.col5,.col6,.col7').hide();
369
- }
370
-
371
- // Change show calendar
372
- $show_calendar_option.change(function() {
373
- if (this.checked) {
374
- $second_step_calendar_wrap.show();
375
- day_columns.find('.col3,.col4,.col5,.col6,.col7').hide();
376
- day_columns.find('.col2 button:gt(0)').attr('style', 'display: none !important');
377
- day_one_column.find('.col2,.col3,.col4,.col5,.col6,.col7').hide();
378
- } else {
379
- $second_step_calendar_wrap.hide();
380
- day_columns.find('.col2 button:gt(0)').attr('style', 'display: block !important');
381
- day_columns.find('.col3,.col4,.col5,.col6,.col7').css('display','inline-block');
382
- day_one_column.find('.col2,.col3,.col4,.col5,.col6,.col7').css('display','inline-block');
383
- }
384
- });
385
-
386
- // Change blocked time slots.
387
- $blocked_timeslots_option.change(function(){
388
- if (this.checked) {
389
- $('.ab-hour.no-booked').removeClass('no-booked').addClass('booked');
390
- } else {
391
- $('.ab-hour.booked').removeClass('booked').addClass('no-booked');
392
- }
393
- });
394
-
395
- // Change day one column.
396
- $day_one_column_option.change(function() {
397
- if (this.checked) {
398
- day_one_column.show();
399
- day_columns.hide();
400
- } else {
401
- day_one_column.hide();
402
- day_columns.show();
403
- }
404
- });
405
-
406
- // Clickable week-days.
407
- $('.ab-week-day').on('change', function () {
408
- var self = $(this);
409
- if (self.is(':checked') && !self.parent().hasClass('active')) {
410
- self.parent().addClass('active');
411
- } else if (self.parent().hasClass('active')) {
412
- self.parent().removeClass('active')
413
- }
414
- });
415
-
416
- var multiple = function (options) {
417
- this.init('multiple', options, multiple.defaults);
418
- };
419
-
420
- // Inherit from Abstract input.
421
- $.fn.editableutils.inherit(multiple, $.fn.editabletypes.abstractinput);
422
-
423
- $.extend(multiple.prototype, {
424
- render: function() {
425
- this.$input = this.$tpl.find('input');
426
- this.$more = jQuery('div.ad--extend', this.tpl);
427
- },
428
-
429
- value2html: function(value, element) {
430
- if(!value) {
431
- $(element).empty();
432
- return;
433
- }
434
- $(element).text(value.label);
435
- $('#' + value.id_option).text(value.option);
436
- $('#' + value.id_option2).text(value.option2);
437
- },
438
-
439
- activate: function () {
440
- this.$input.filter('[name="label"]').focus();
441
- },
442
-
443
- value2input: function(value) {
444
- if(!value) {
445
- return;
446
- }
447
- if (value.extended) {
448
- this.$more.show();
449
- }
450
- this.$input.filter('[name="label"]').val(value.label);
451
- this.$input.filter('[name="option"]').val(value.option);
452
- this.$input.filter('[name="id_option"]').val(value.id_option);
453
- this.$input.filter('[name="option2"]').val(value.option2);
454
- this.$input.filter('[name="id_option2"]').val(value.id_option2);
455
- },
456
-
457
- input2value: function() {
458
- return {
459
- label: this.$input.filter('[name="label"]').val(),
460
- option: this.$input.filter('[name="option"]').val(),
461
- id_option: this.$input.filter('[name="id_option"]').val(),
462
- option2: this.$input.filter('[name="option2"]').val(),
463
- id_option2: this.$input.filter('[name="id_option2"]').val(),
464
- extended: this.$more.is(':visible')
465
- };
466
- }
467
- });
468
-
469
- multiple.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
470
- tpl: '<div class="editable-multiple"><input type="text" name="label" class="form-control input-sm" /></div>'+
471
- '<div style="margin-top:5px;" class="editable-multiple"><input type="text" name="option" class="form-control input-sm" /><input type="hidden" name="id_option" /></div><div class="ad--extend" style="display:none">'+
472
- '<div style="margin-top:5px;" class="editable-multiple"><input type="text" name="option2" class="form-control input-sm" /><input type="hidden" name="id_option2" /></div></div></div>',
473
-
474
- inputclass: ''
475
- });
476
-
477
- $.fn.editabletypes.multiple = multiple;
478
- $text_label_location.editable({
479
- value: {
480
- label: $text_label_location.text(),
481
- option: $text_option_location.text(),
482
- id_option: $text_label_location.data('option-id'),
483
- extended: true,
484
- option2: $text_label_location.data('default-error'),
485
- id_option2: $text_label_location.data('error-id')
486
- }
487
- });
488
- $text_label_category.editable({
489
- value: {
490
- label: $text_label_category.text(),
491
- option: $text_option_category.text(),
492
- id_option: $text_label_category.data('option-id')
493
- }
494
- });
495
- $text_label_service.editable({
496
- value: {
497
- label: $text_label_service.text(),
498
- option: $text_option_service.text(),
499
- id_option: $text_label_service.data('option-id'),
500
- extended: true,
501
- option2: $text_label_service.data('default-error'),
502
- id_option2: $text_label_service.data('error-id')
503
- }
504
- });
505
- $text_label_employee.editable({
506
- value: {
507
- label: $text_label_employee.text(),
508
- option: $text_option_employee.text(),
509
- id_option: $text_label_employee.data('option-id'),
510
- extended: true,
511
- option2: $text_label_employee.data('default-error'),
512
- id_option2: $text_label_employee.data('error-id')
513
- }
514
- });
515
-
516
- $text_label_name.editable({
517
- value: {
518
- label: $text_label_name.text(),
519
- option: $text_label_name.data('default-error'),
520
- id_option: $text_label_name.data('option-id')
521
- }
522
- });
523
-
524
- $text_label_phone.editable({
525
- value: {
526
- label: $text_label_phone.text(),
527
- option: $text_label_phone.data('default-error'),
528
- id_option: $text_label_phone.data('option-id')
529
- }
530
- });
531
-
532
- $text_label_email.editable({
533
- value: {
534
- label: $text_label_email.text(),
535
- option: $text_label_email.data('default-error'),
536
- id_option: $text_label_email.data('option-id')
537
- }
538
- });
539
-
540
- $text_info_service.add('#ab-text-info-time').add('#ab-text-info-details').add('#ab-text-info-payment').add('#ab-text-info-complete').add('#ab-text-info-coupon').editable({placement: 'right'});
541
- $ab_editable.editable();
542
-
543
- $.fn.editableform.template = '<form class="form-inline editableform"> <div class="control-group"> <div> <div class="editable-input"></div><div class="editable-buttons"></div></div><div class="editable-notes"></div><div class="editable-error-block"></div></div> </form>';
544
- $.fn.editableform.buttons = '<div class="btn-group btn-group-sm"><button type="submit" class="btn btn-success editable-submit"><span class="glyphicon glyphicon-ok"></span></button><button type="button" class="btn btn-default editable-cancel"><span class="glyphicon glyphicon-remove"></span></button></div>';
545
-
546
- $ab_editable.on('shown', function(e, editable) {
547
- $('.popover').find('.arrow').removeClass().addClass('popover-arrow');
548
- $('.editable-notes').html($(e.target).data('notes'));
549
- });
550
- $('[data-type="multiple"]').on('shown', function(e, editable) {
551
- $('.popover').find('.arrow').removeClass().addClass('popover-arrow');
552
- });
553
-
554
- $("[data-mirror^='text_']").on('save', function (e, params) {
555
- $("." + $(e.target).data('mirror')).editable('setValue', params.newValue);
556
- switch ($(e.target).data('mirror')){
557
- case 'text_services':
558
- $(".ab-service-list").html(params.newValue.label);
559
- break;
560
- case 'text_locations':
561
- $(".ab-location-list").html(params.newValue.label);
562
- break;
563
- case 'text_employee':
564
- $(".ab-employee-list").html(params.newValue.label);
565
- break;
566
- }
567
- });
568
-
569
- $('input[type=radio]').change(function () {
570
- if ($('.ab-card-payment').is(':checked')) {
571
- $('form.ab-card-form').show();
572
- } else {
573
- $('form.ab-card-form').hide();
574
- }
575
- });
576
-
577
- $('#bookly-js-hint-alert').on('closed.bs.alert', function () {
578
- $.ajax({
579
- url: ajaxurl,
580
- data: { action: 'ab_dismiss_appearance_notice' }
581
- });
582
- })
583
- }); // jQuery
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(function($) {
2
+ var // Progress Tracker.
3
+ $progress_tracker_option = $('input#ab-progress-tracker-checkbox'),
4
+ $staff_name_with_price_option = $('input#ab-staff-name-with-price-checkbox'),
5
+ // Time slots setting.
6
+ $blocked_timeslots_option = $('input#ab-blocked-timeslots-checkbox'),
7
+ $day_one_column_option = $('input#ab-day-one-column-checkbox'),
8
+ $show_calendar_option = $('input#ab-show-calendar-checkbox'),
9
+ $required_employee_option = $('input#ab-required-employee-checkbox'),
10
+ $required_location_option = $('input#ab-required-location-checkbox'),
11
+ // Buttons.
12
+ $save_button = $('#ajax-send-appearance'),
13
+ $reset_button = $('button[type=reset]'),
14
+ // Texts.
15
+ $text_step_service = $('#bookly_l10n_step_service'),
16
+ $text_step_extras = $('#bookly_l10n_step_extras'),
17
+ $text_step_time = $('#bookly_l10n_step_time'),
18
+ $text_step_cart = $('#bookly_l10n_step_cart'),
19
+ $text_step_details = $('#bookly_l10n_step_details'),
20
+ $text_step_payment = $('#bookly_l10n_step_payment'),
21
+ $text_step_done = $('#bookly_l10n_step_done'),
22
+ $text_label_location = $('#bookly_l10n_label_location'),
23
+ $text_label_multiply = $('#bookly_l10n_label_multiply'),
24
+ $text_label_category = $('#bookly_l10n_label_category'),
25
+ $text_option_location = $('#bookly_l10n_option_location'),
26
+ $text_option_category = $('#bookly_l10n_option_category'),
27
+ $text_option_service = $('#bookly_l10n_option_service'),
28
+ $text_option_employee = $('#bookly_l10n_option_employee'),
29
+ $text_label_service = $('#bookly_l10n_label_service'),
30
+ $text_label_number_of_persons = $('#bookly_l10n_label_number_of_persons'),
31
+ $text_label_employee = $('#bookly_l10n_label_employee'),
32
+ $text_label_select_date = $('#bookly_l10n_label_select_date'),
33
+ $text_label_start_from = $('#bookly_l10n_label_start_from'),
34
+ $text_button_next = $('#bookly_l10n_button_next'),
35
+ $text_button_back = $('#bookly_l10n_button_back'),
36
+ $text_button_book_more = $('#bookly_l10n_button_book_more'),
37
+ $text_button_apply = $('#bookly_l10n_button_apply'),
38
+ $text_label_finish_by = $('#bookly_l10n_label_finish_by'),
39
+ $text_label_name = $('#bookly_l10n_label_name'),
40
+ $text_label_phone = $('#bookly_l10n_label_phone'),
41
+ $text_label_email = $('#bookly_l10n_label_email'),
42
+ $text_label_coupon = $('#bookly_l10n_label_coupon'),
43
+ $text_info_service = $('#bookly_l10n_info_service_step'),
44
+ $text_info_extras = $('#bookly_l10n_info_extras_step'),
45
+ $text_info_time = $('#bookly_l10n_info_time_step'),
46
+ $text_info_cart = $('#bookly_l10n_info_cart_step'),
47
+ $text_info_details = $('#bookly_l10n_info_details_step'),
48
+ $text_info_details_guest = $('#bookly_l10n_info_details_step_guest'),
49
+ $text_info_coupon = $('#bookly_l10n_info_coupon'),
50
+ $text_info_payment = $('#bookly_l10n_info_payment_step'),
51
+ $text_info_complete = $('#bookly_l10n_info_complete_step'),
52
+ $text_label_pay_paypal = $('#bookly_l10n_label_pay_paypal'),
53
+ $text_label_pay_ccard = $('#bookly_l10n_label_pay_ccard'),
54
+ $text_label_ccard_number = $('#bookly_l10n_label_ccard_number'),
55
+ $text_label_ccard_expire = $('#bookly_l10n_label_ccard_expire'),
56
+ $text_label_ccard_code = $('#bookly_l10n_label_ccard_code'),
57
+ $color_picker = $('.bookly-js-color-picker'),
58
+ $ab_editable = $('.bookly-editable'),
59
+ $text_label_pay_locally = $('#bookly_l10n_label_pay_locally'),
60
+ $text_label_pay_mollie = $('#bookly_l10n_label_pay_mollie'),
61
+ // Calendars.
62
+ $second_step_calendar = $('.ab-selected-date'),
63
+ $second_step_calendar_wrap = $('.ab-slot-calendar'),
64
+ // Step settings.
65
+ $step_settings = $('#bookly-js-step-settings'),
66
+ // Step repeat.
67
+ $repeat_ui_step_calendar = $('.bookly-repeat-until'),
68
+ $repeat_ui_variants = $('[class^="bookly-variant"]'),
69
+ $repeat_ui_variant = $('.bookly-repeat-variant'),
70
+ $repeat_ui_variant_monthly = $('.variant-monthly'),
71
+ $repeat_ui_weekly_week_day = $('.bookly-week-day'),
72
+ $repeat_ui_monthly_specific_day = $('.bookly-monthly-specific-day'),
73
+ $repeat_ui_monthly_week_day = $('.bookly-monthly-week-day'),
74
+ // Step repeat l10n
75
+ $text_info_repeat_step = $('#bookly_l10n_info_repeat_step'),
76
+ $text_label_repeat = $('#bookly_l10n_label_repeat'),
77
+ $text_repeat = $('#bookly_l10n_repeat'),
78
+ $text_repeat_another_time = $('#bookly_l10n_repeat_another_time'),
79
+ $text_repeat_biweekly = $('#bookly_l10n_repeat_biweekly'),
80
+ $text_repeat_daily = $('#bookly_l10n_repeat_daily'),
81
+ $text_repeat_day = $('#bookly_l10n_repeat_day'),
82
+ $text_repeat_days = $('#bookly_l10n_repeat_days'),
83
+ $text_repeat_deleted = $('#bookly_l10n_repeat_deleted'),
84
+ $text_repeat_every = $('#bookly_l10n_repeat_every'),
85
+ $text_repeat_first = $('#bookly_l10n_repeat_first'),
86
+ $text_repeat_first_in_cart_info = $('#bookly_l10n_repeat_first_in_cart_info').first(),
87
+ $text_repeat_fourth = $('#bookly_l10n_repeat_fourth'),
88
+ $text_repeat_last = $('#bookly_l10n_repeat_last'),
89
+ $text_repeat_monthly = $('#bookly_l10n_repeat_monthly'),
90
+ $text_repeat_on = $('#bookly_l10n_repeat_on'),
91
+ $text_repeat_on_week = $('#bookly_l10n_repeat_on_week'),
92
+ $text_repeat_required_week_days = $('#bookly_l10n_repeat_required_week_days'),
93
+ $text_repeat_schedule = $('#bookly_l10n_repeat_schedule'),
94
+ $text_repeat_schedule_help = $('#bookly_l10n_repeat_schedule_help'),
95
+ $text_repeat_schedule_info = $('#bookly_l10n_repeat_schedule_info'),
96
+ $text_repeat_second = $('#bookly_l10n_repeat_second'),
97
+ $text_repeat_specific = $('#bookly_l10n_repeat_specific'),
98
+ $text_repeat_third = $('#bookly_l10n_repeat_third'),
99
+ $text_repeat_this_appointment = $('#bookly_l10n_repeat_this_appointment'),
100
+ $text_repeat_until = $('#bookly_l10n_repeat_until'),
101
+ $text_repeat_weekly = $('#bookly_l10n_repeat_weekly'),
102
+ $text_step_repeat = $('#bookly_l10n_step_repeat')
103
+ ;
104
+
105
+ if (BooklyL10n.intlTelInput.enabled) {
106
+ $('.ab-user-phone').intlTelInput({
107
+ preferredCountries: [BooklyL10n.intlTelInput.country],
108
+ defaultCountry: BooklyL10n.intlTelInput.country,
109
+ geoIpLookup: function (callback) {
110
+ $.get(ajaxurl, {action: 'bookly_ip_info'}, function () {
111
+ }, 'json').always(function (resp) {
112
+ var countryCode = (resp && resp.country) ? resp.country : '';
113
+ callback(countryCode);
114
+ });
115
+ },
116
+ utilsScript: BooklyL10n.intlTelInput.utils
117
+ });
118
+ }
119
+
120
+ $staff_name_with_price_option.on('change', function () {
121
+ var staff = $('.ab-select-employee').val();
122
+ if (staff) {
123
+ $('.ab-select-employee').val(staff * -1);
124
+ }
125
+ $('.employee-name-price').toggle($staff_name_with_price_option.prop("checked"));
126
+ $('.employee-name').toggle(!$staff_name_with_price_option.prop("checked"));
127
+ }).trigger('change');
128
+
129
+ // menu fix for WP 3.8.1
130
+ $('#toplevel_page_ab-system > ul').css('margin-left', '0px');
131
+
132
+ // Tabs.
133
+ $('li.bookly-nav-item').on('shown.bs.tab', function (e) {
134
+ $step_settings.children().hide();
135
+ switch (e.target.getAttribute('data-target')) {
136
+ case '#ab-step-1': $step_settings.find('#bookly-js-step-service').show(); break;
137
+ case '#ab-step-3': $step_settings.find('#bookly-js-step-time').show(); break;
138
+ }
139
+ });
140
+
141
+ function getEditableValue(val) {
142
+ return $.trim(val == 'Empty' ? '' : val);
143
+ }
144
+ // Apply color from color picker.
145
+ var applyColor = function() {
146
+ var color_important = $color_picker.wpColorPicker('color') + '!important';
147
+ $('.ab-progress-tracker').find('.active').css('color', $color_picker.wpColorPicker('color')).find('.step').css('background', $color_picker.wpColorPicker('color'));
148
+ $('.ab-mobile-step_1 label').css('color', $color_picker.wpColorPicker('color'));
149
+ $('.ab-label-error').css('color', $color_picker.wpColorPicker('color'));
150
+ $('.bookly-js-actions > a').css('background-color', $color_picker.wpColorPicker('color'));
151
+ $('.bookly-next-step, .ab-mobile-next-step').css('background', $color_picker.wpColorPicker('color'));
152
+ $('.bookly-week-days label').css('background-color', $color_picker.wpColorPicker('color'));
153
+ $('.picker__frame').attr('style', 'background: ' + color_important);
154
+ $('.picker__header').attr('style', 'border-bottom: ' + '1px solid ' + color_important);
155
+ $('.picker__day').mouseenter(function(){
156
+ $(this).attr('style', 'color: ' + color_important);
157
+ }).mouseleave(function(){ $(this).attr('style', $(this).hasClass('picker__day--selected') ? 'color: ' + color_important : '') });
158
+ $('.picker__day--selected').attr('style', 'color: ' + color_important);
159
+ $('.picker__button--clear').attr('style', 'color: ' + color_important);
160
+ $('.picker__button--today').attr('style', 'color: ' + color_important);
161
+ $('.bookly-extra-step .bookly-extras-thumb.bookly-extras-selected').css('border-color', $color_picker.wpColorPicker('color'));
162
+ $('.ab-columnizer .ab-day, .bookly-schedule-date,.bookly-pagination li.active').css({
163
+ 'background': $color_picker.wpColorPicker('color'),
164
+ 'border-color': $color_picker.wpColorPicker('color')
165
+ });
166
+ $('.ab-columnizer .ab-hour').off().hover(
167
+ function() { // mouse-on
168
+ $(this).css({
169
+ 'color': $color_picker.wpColorPicker('color'),
170
+ 'border': '2px solid ' + $color_picker.wpColorPicker('color')
171
+ });
172
+ $(this).find('.ab-hour-icon').css({
173
+ 'border-color': $color_picker.wpColorPicker('color'),
174
+ 'color': $color_picker.wpColorPicker('color')
175
+ });
176
+ $(this).find('.ab-hour-icon > span').css({
177
+ 'background': $color_picker.wpColorPicker('color')
178
+ });
179
+ },
180
+ function() { // mouse-out
181
+ $(this).css({
182
+ 'color': '#333333',
183
+ 'border': '1px solid #cccccc'
184
+ });
185
+ $(this).find('.ab-hour-icon').css({
186
+ 'border-color': '#333333',
187
+ 'color': '#cccccc'
188
+ });
189
+ $(this).find('.ab-hour-icon > span').css({
190
+ 'background': '#cccccc'
191
+ });
192
+ }
193
+ );
194
+ $('.ab-details-step label').css('color', $color_picker.wpColorPicker('color'));
195
+ $('.ab-card-form label').css('color', $color_picker.wpColorPicker('color'));
196
+ $('.ab-nav-tabs .ladda-button, .bookly-nav-steps .ladda-button, .ab-btn, .bookly-round, .bookly-square').css('background-color', $color_picker.wpColorPicker('color'));
197
+ $('.bookly-triangle').css('border-bottom-color', $color_picker.wpColorPicker('color'));
198
+ $('.bookly-back-step, .bookly-next-step').css('background', $color_picker.wpColorPicker('color'));
199
+ var style_arrow = '.picker__nav--next:before { border-left: 6px solid ' + $color_picker.wpColorPicker('color') + '!important; } .picker__nav--prev:before { border-right: 6px solid ' + $color_picker.wpColorPicker('color') + '!important; }';
200
+ $('#ab--style-arrow').html(style_arrow);
201
+ };
202
+ $color_picker.wpColorPicker({
203
+ change : applyColor
204
+ });
205
+ // Init calendars.
206
+ $('.ab-date-from').pickadate({
207
+ formatSubmit : 'yyyy-mm-dd',
208
+ format : BooklyL10n.date_format,
209
+ min : true,
210
+ clear : false,
211
+ close : false,
212
+ today : BooklyL10n.today,
213
+ weekdaysShort : BooklyL10n.days,
214
+ monthsFull : BooklyL10n.months,
215
+ labelMonthNext : BooklyL10n.nextMonth,
216
+ labelMonthPrev : BooklyL10n.prevMonth,
217
+ onRender : applyColor,
218
+ firstDay : BooklyL10n.start_of_week == 1
219
+ });
220
+
221
+ $second_step_calendar.pickadate({
222
+ formatSubmit : 'yyyy-mm-dd',
223
+ format : BooklyL10n.date_format,
224
+ min : true,
225
+ weekdaysShort : BooklyL10n.days,
226
+ monthsFull : BooklyL10n.months,
227
+ labelMonthNext : BooklyL10n.nextMonth,
228
+ labelMonthPrev : BooklyL10n.prevMonth,
229
+ close : false,
230
+ clear : false,
231
+ today : false,
232
+ closeOnSelect : false,
233
+ onRender : applyColor,
234
+ firstDay : BooklyL10n.start_of_week == 1,
235
+ klass : {
236
+ picker: 'picker picker--opened picker--focused'
237
+ },
238
+ onClose : function() {
239
+ this.open(false);
240
+ }
241
+ });
242
+ $second_step_calendar_wrap.find('.picker__holder').css({ top : '0px', left : '0px' });
243
+ $second_step_calendar_wrap.toggle($show_calendar_option.prop('checked'));
244
+
245
+ // Update options.
246
+ $save_button.on('click', function(e) {
247
+ e.preventDefault();
248
+ var data = {
249
+ action: 'bookly_update_appearance_options',
250
+ options: {
251
+ // Color.
252
+ 'color' : $color_picker.wpColorPicker('color'),
253
+ // Info text.
254
+ 'text_info_service_step' : getEditableValue($text_info_service.text()),
255
+ 'text_info_extras_step' : getEditableValue($text_info_extras.text()),
256
+ 'text_info_time_step' : getEditableValue($text_info_time.text()),
257
+ 'text_info_cart_step' : getEditableValue($text_info_cart.text()),
258
+ 'text_info_details_step' : getEditableValue($text_info_details.text()),
259
+ 'text_info_details_step_guest' : getEditableValue($text_info_details_guest.text()),
260
+ 'text_info_payment_step' : getEditableValue($text_info_payment.text()),
261
+ 'text_info_complete_step' : getEditableValue($text_info_complete.text()),
262
+ 'text_info_coupon' : getEditableValue($text_info_coupon.text()),
263
+ // Step and label texts.
264
+ 'text_step_service' : getEditableValue($text_step_service.text()),
265
+ 'text_step_extras' : getEditableValue($text_step_extras.text()),
266
+ 'text_step_time' : getEditableValue($text_step_time.text()),
267
+ 'text_step_cart' : getEditableValue($text_step_cart.text()),
268
+ 'text_step_details' : getEditableValue($text_step_details.text()),
269
+ 'text_step_payment' : getEditableValue($text_step_payment.text()),
270
+ 'text_step_done' : getEditableValue($text_step_done.text()),
271
+ 'text_label_location' : getEditableValue($text_label_location.text()),
272
+ 'text_label_category' : getEditableValue($text_label_category.text()),
273
+ 'text_label_service' : getEditableValue($text_label_service.text()),
274
+ 'text_label_number_of_persons' : getEditableValue($text_label_number_of_persons.text()),
275
+ 'text_label_multiply' : getEditableValue($text_label_multiply.text()),
276
+ 'text_label_employee' : getEditableValue($text_label_employee.text()),
277
+ 'text_label_select_date' : getEditableValue($text_label_select_date.text()),
278
+ 'text_label_start_from' : getEditableValue($text_label_start_from.text()),
279
+ 'text_button_next' : getEditableValue($text_button_next.text()),
280
+ 'text_button_back' : getEditableValue($text_button_back.text()),
281
+ 'text_button_apply' : getEditableValue($text_button_apply.text()),
282
+ 'text_button_book_more' : getEditableValue($text_button_book_more.text()),
283
+ 'text_label_finish_by' : getEditableValue($text_label_finish_by.text()),
284
+ 'text_label_name' : getEditableValue($text_label_name.text()),
285
+ 'text_label_phone' : getEditableValue($text_label_phone.text()),
286
+ 'text_label_email' : getEditableValue($text_label_email.text()),
287
+ 'text_label_coupon' : getEditableValue($text_label_coupon.text()),
288
+ 'text_option_location' : getEditableValue($text_option_location.text()),
289
+ 'text_option_category' : getEditableValue($text_option_category.text()),
290
+ 'text_option_service' : getEditableValue($text_option_service.text()),
291
+ 'text_option_employee' : getEditableValue($text_option_employee.text()),
292
+ 'text_label_pay_locally' : getEditableValue($text_label_pay_locally.text()),
293
+ 'text_label_pay_mollie' : getEditableValue($text_label_pay_mollie.text()),
294
+ 'text_label_pay_paypal' : getEditableValue($text_label_pay_paypal.text()),
295
+ 'text_label_pay_ccard' : getEditableValue($text_label_pay_ccard.text()),
296
+ 'text_label_ccard_number' : getEditableValue($text_label_ccard_number.text()),
297
+ 'text_label_ccard_expire' : getEditableValue($text_label_ccard_expire.text()),
298
+ 'text_label_ccard_code' : getEditableValue($text_label_ccard_code.text()),
299
+ // Repeat
300
+ 'text_info_repeat_step' : getEditableValue($text_info_repeat_step.text()),
301
+ 'text_label_repeat' : getEditableValue($text_label_repeat.text()),
302
+ 'text_repeat' : getEditableValue($text_repeat.text()),
303
+ 'text_repeat_another_time' : getEditableValue($text_repeat_another_time.text()),
304
+ 'text_repeat_biweekly' : getEditableValue($text_repeat_biweekly.text()),
305
+ 'text_repeat_daily' : getEditableValue($text_repeat_daily.text()),
306
+ 'text_repeat_day' : getEditableValue($text_repeat_day.text()),
307
+ 'text_repeat_days' : getEditableValue($text_repeat_days.text()),
308
+ 'text_repeat_deleted' : getEditableValue($text_repeat_deleted.text()),
309
+ 'text_repeat_every' : getEditableValue($text_repeat_every.text()),
310
+ 'text_repeat_first' : getEditableValue($text_repeat_first.text()),
311
+ 'text_repeat_first_in_cart_info' : getEditableValue($text_repeat_first_in_cart_info.text()),
312
+ 'text_repeat_fourth' : getEditableValue($text_repeat_fourth.text()),
313
+ 'text_repeat_last' : getEditableValue($text_repeat_last.text()),
314
+ 'text_repeat_monthly' : getEditableValue($text_repeat_monthly.text()),
315
+ 'text_repeat_on' : getEditableValue($text_repeat_on.text()),
316
+ 'text_repeat_on_week' : getEditableValue($text_repeat_on_week.text()),
317
+ 'text_repeat_required_week_days' : getEditableValue($text_repeat_required_week_days.text()),
318
+ 'text_repeat_schedule' : getEditableValue($text_repeat_schedule.text()),
319
+ 'text_repeat_schedule_help' : getEditableValue($text_repeat_schedule_help.text()),
320
+ 'text_repeat_schedule_info' : getEditableValue($text_repeat_schedule_info.text()),
321
+ 'text_repeat_second' : getEditableValue($text_repeat_second.text()),
322
+ 'text_repeat_specific' : getEditableValue($text_repeat_specific.text()),
323
+ 'text_repeat_third' : getEditableValue($text_repeat_third.text()),
324
+ 'text_repeat_this_appointment' : getEditableValue($text_repeat_this_appointment.text()),
325
+ 'text_repeat_until' : getEditableValue($text_repeat_until.text()),
326
+ 'text_repeat_weekly' : getEditableValue($text_repeat_weekly.text()),
327
+ 'text_step_repeat' : getEditableValue($text_step_repeat.text()),
328
+ // Validator.
329
+ 'text_required_location' : getEditableValue($('#bookly_l10n_required_location').html()),
330
+ 'text_required_service' : getEditableValue($('#bookly_l10n_required_service').html()),
331
+ 'text_required_employee' : getEditableValue($('#bookly_l10n_required_employee').html()),
332
+ 'text_required_name' : getEditableValue($('#bookly_l10n_required_name').html()),
333
+ 'text_required_phone' : getEditableValue($('#bookly_l10n_required_phone').html()),
334
+ 'text_required_email' : getEditableValue($('#bookly_l10n_required_email').html()),
335
+ // Checkboxes.
336
+ 'progress_tracker' : Number($progress_tracker_option.prop('checked')),
337
+ 'staff_name_with_price': Number($staff_name_with_price_option.prop('checked')),
338
+ 'blocked_timeslots' : Number($blocked_timeslots_option.prop('checked')),
339
+ 'day_one_column' : Number($day_one_column_option.prop('checked')),
340
+ 'show_calendar' : Number($show_calendar_option.prop('checked')),
341
+ 'required_employee' : Number($required_employee_option.prop('checked')),
342
+ 'required_location' : Number($required_location_option.prop('checked'))
343
+ } // options
344
+ }; // data
345
+
346
+ // update data and show spinner while updating
347
+ var ladda = Ladda.create(this);
348
+ ladda.start();
349
+ $.post(ajaxurl, data, function (response) {
350
+ ladda.stop();
351
+ booklyAlert({success : [BooklyL10n.saved]});
352
+ });
353
+ });
354
+
355
+ // Reset options to defaults.
356
+ $reset_button.on('click', function() {
357
+ // Reset color.
358
+ $color_picker.wpColorPicker('color', $color_picker.data('selected'));
359
+
360
+ // Reset texts.
361
+ jQuery.each($('.editable:not([data-type=multiple])'), function() {
362
+ $(this).text($(this).data('option-default')); //default value for texts
363
+ $(this).editable('setValue', $(this).data('option-default')); // default value for editable inputs
364
+ });
365
+
366
+ // Reset texts.
367
+ jQuery.each($('.ab-service-list, .ab-employee-list'), function() {
368
+ $(this).html($(this).data('default')); //default value
369
+ });
370
+
371
+ // default value for multiple inputs
372
+ $('[data-type=multiple]').each(function () {
373
+ var $elem = $(this),
374
+ options = $elem.data('options-default'),
375
+ $target;
376
+ $elem.data('options',options);
377
+ $.each(options, function (name, value) {
378
+ $target = $('#' + name);
379
+ if ($target.is(':text')) {
380
+ $target.val(value);
381
+ } else {
382
+ $target.text(value);
383
+ }
384
+ $target = $('[name=' + name + ']:text');
385
+ if ($target.is(':text')) {
386
+ $target.val(value);
387
+ } else {
388
+ $target.text(value);
389
+ }
390
+ });
391
+ });
392
+ });
393
+
394
+ $progress_tracker_option.change(function(){
395
+ $('.ab-progress-tracker').toggle($(this).is(':checked'));
396
+ }).trigger('change');
397
+
398
+ var day_one_column = $('.ab-day-one-column'),
399
+ day_columns = $('.ab-day-columns');
400
+
401
+ if ($show_calendar_option.prop('checked')) {
402
+ $second_step_calendar_wrap.show();
403
+ day_columns.find('.col3,.col4,.col5,.col6,.col7').hide();
404
+ day_columns.find('.col2 button:gt(0)').attr('style', 'display: none !important');
405
+ day_one_column.find('.col2,.col3,.col4,.col5,.col6,.col7').hide();
406
+ }
407
+
408
+ // Change show calendar
409
+ $show_calendar_option.change(function() {
410
+ if (this.checked) {
411
+ $second_step_calendar_wrap.show();
412
+ day_columns.find('.col3,.col4,.col5,.col6,.col7').hide();
413
+ day_columns.find('.col2 button:gt(0)').attr('style', 'display: none !important');
414
+ day_one_column.find('.col2,.col3,.col4,.col5,.col6,.col7').hide();
415
+ } else {
416
+ $second_step_calendar_wrap.hide();
417
+ day_columns.find('.col2 button:gt(0)').attr('style', 'display: block !important');
418
+ day_columns.find('.col2 button.ab-first-child').attr('style', 'background: ' + $color_picker.wpColorPicker('color') + '!important;display: block !important');
419
+ day_columns.find('.col3,.col4,.col5,.col6,.col7').css('display','inline-block');
420
+ day_one_column.find('.col2,.col3,.col4,.col5,.col6,.col7').css('display','inline-block');
421
+ }
422
+ });
423
+
424
+ // Change blocked time slots.
425
+ $blocked_timeslots_option.change(function(){
426
+ if (this.checked) {
427
+ $('.ab-hour.no-booked').removeClass('no-booked').addClass('booked');
428
+ } else {
429
+ $('.ab-hour.booked').removeClass('booked').addClass('no-booked');
430
+ }
431
+ });
432
+
433
+ // Change day one column.
434
+ $day_one_column_option.change(function() {
435
+ if (this.checked) {
436
+ day_one_column.show();
437
+ day_columns.hide();
438
+ } else {
439
+ day_one_column.hide();
440
+ day_columns.show();
441
+ }
442
+ });
443
+
444
+ // Clickable week-days.
445
+ $('.bookly-week-day').on('change', function () {
446
+ var self = $(this);
447
+ if (self.is(':checked') && !self.parent().hasClass('active')) {
448
+ self.parent().addClass('active');
449
+ } else if (self.parent().hasClass('active')) {
450
+ self.parent().removeClass('active')
451
+ }
452
+ });
453
+
454
+ var multiple = function (options) {
455
+ this.init('multiple', options, multiple.defaults);
456
+ };
457
+
458
+ // Step repeat.
459
+ $repeat_ui_step_calendar.pickadate({
460
+ formatSubmit : 'yyyy-mm-dd',
461
+ format : BooklyL10n.date_format,
462
+ min : true,
463
+ clear : false,
464
+ close : false,
465
+ today : BooklyL10n.today,
466
+ weekdaysShort : BooklyL10n.days,
467
+ monthsFull : BooklyL10n.months,
468
+ labelMonthNext : BooklyL10n.nextMonth,
469
+ labelMonthPrev : BooklyL10n.prevMonth,
470
+ onRender : applyColor,
471
+ firstDay : BooklyL10n.start_of_week == 1
472
+ });
473
+ $repeat_ui_variant.on('change', function () {
474
+ $repeat_ui_variants.hide();
475
+ $('.bookly-variant-' + this.value).show()
476
+ }).trigger('change');
477
+
478
+ $repeat_ui_variant_monthly.on('change', function () {
479
+ $repeat_ui_monthly_week_day.toggle(this.value != 'specific');
480
+ $repeat_ui_monthly_specific_day.toggle(this.value == 'specific');
481
+ }).trigger('change');
482
+
483
+ $repeat_ui_weekly_week_day.on('change', function () {
484
+ var $this = $(this);
485
+ if ($this.is(':checked')) {
486
+ $this.parent().not("[class*='active']").addClass('active');
487
+ } else {
488
+ $this.parent().removeClass('active');
489
+ }
490
+ });
491
+
492
+ // Inherit from Abstract input.
493
+ $.fn.editableutils.inherit(multiple, $.fn.editabletypes.abstractinput);
494
+
495
+ $.extend(multiple.prototype, {
496
+ container: null,
497
+ $elem : null,
498
+ render : function() {
499
+ this.container = jQuery('div.bookly-js-container', this.tpl);
500
+ },
501
+
502
+ value2html: function (value, element) { },
503
+
504
+ activate: function () {
505
+ this.container.find(':text:eq(0)').focus();
506
+ },
507
+
508
+ value2input: function(value) {
509
+ if(!value) {
510
+ return;
511
+ }
512
+ var container = this.container;
513
+ this.$elem = value.elem;
514
+ if (!this.$elem.data('options')) {
515
+ this.$elem.data('options', this.$elem.data('options-default'));
516
+ }
517
+ container.html('');
518
+ $.each(this.$elem.data('options'), function (id, value) {
519
+ $('<input/>', {
520
+ type : 'text',
521
+ class: 'form-control input-sm',
522
+ name : id,
523
+ value: value || ''
524
+ }).appendTo(container);
525
+ });
526
+ },
527
+
528
+ input2value: function() {
529
+ var options = {};
530
+ $.each(this.container.find(':text'), function () {
531
+ var name = $(this).attr('name'),
532
+ $target = $('#' + name);
533
+ options[name] = this.value;
534
+ if($target.is(':text')){
535
+ $target.val(this.value);
536
+ } else {
537
+ $target.text(this.value);
538
+ }
539
+ });
540
+ this.$elem.data('options', options);
541
+ return {elem: this.$elem};
542
+ }
543
+ });
544
+ multiple.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
545
+ tpl: '<div class="bookly-editable-multiple"><div class="bookly-js-container">',
546
+ inputclass: ''
547
+ });
548
+
549
+ $.fn.editabletypes.multiple = multiple;
550
+ $('[data-type=multiple]').each(function () {
551
+ var $elem = $(this);
552
+ $elem.editable({ value: { elem: $elem}});
553
+ });
554
+
555
+ $text_info_service.add('#bookly_l10n_info_time_step').add('#bookly_l10n_info_details_step').add('#bookly_l10n_info_payment_step').add('#bookly_l10n_info_complete_step').add('#bookly_l10n_info_coupon').editable({placement: 'right'});
556
+ $ab_editable.editable();
557
+
558
+ $.fn.editableform.template = '<form class="form-inline editableform"> <div class="control-group"> <div> <div class="editable-input"></div><div class="editable-buttons"></div></div><div class="editable-notes"></div><div class="editable-error-block"></div></div> </form>';
559
+ $.fn.editableform.buttons = '<div class="btn-group btn-group-sm"><button type="submit" class="btn btn-success editable-submit"><span class="glyphicon glyphicon-ok"></span></button><button type="button" class="btn btn-default editable-cancel"><span class="glyphicon glyphicon-remove"></span></button></div>';
560
+
561
+ $ab_editable.on('shown', function(e, editable) {
562
+ $('.popover').find('.arrow').removeClass().addClass('popover-arrow');
563
+ $('.editable-notes').html($(e.target).data('notes'));
564
+ });
565
+ $('[data-type="multiple"]').on('shown', function(e, editable) {
566
+ $('.popover').find('.arrow').removeClass().addClass('popover-arrow');
567
+ });
568
+
569
+ $("[data-mirror]").on('save', function (e, params) {
570
+ $("." + $(e.target).data('mirror')).editable('setValue', params.newValue);
571
+ switch ($(e.target).data('mirror')){
572
+ case 'text_services':
573
+ $(".ab-service-list").html(params.newValue.label);
574
+ break;
575
+ case 'text_locations':
576
+ $(".ab-location-list").html(params.newValue.label);
577
+ break;
578
+ case 'text_employee':
579
+ $(".ab-employee-list").html(params.newValue.label);
580
+ break;
581
+ }
582
+ });
583
+
584
+ $('input[type=radio]').change(function () {
585
+ if ($('.ab-card-payment').is(':checked')) {
586
+ $('form.ab-card-form').show();
587
+ } else {
588
+ $('form.ab-card-form').hide();
589
+ }
590
+ });
591
+
592
+ $('#bookly-js-hint-alert').on('closed.bs.alert', function () {
593
+ $.ajax({
594
+ url: ajaxurl,
595
+ data: { action: 'bookly_dismiss_appearance_notice' }
596
+ });
597
+ })
598
+ }); // jQuery
backend/modules/appearance/templates/_1_service.php CHANGED
@@ -1,195 +1,179 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- /** @var WP_Locale $wp_locale */
3
- global $wp_locale;
4
- ?>
5
- <div class="bookly-form">
6
- <?php include '_progress_tracker.php' ?>
7
-
8
- <div class="ab-service-step">
9
- <div class="ab-row">
10
- <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_service_step' ) ) ?>"
11
- class="ab_editable ab-bold ab-desc" id="ab-text-info-service"
12
- data-rows="7" data-type="textarea"><?php echo esc_html( get_option( 'ab_appearance_text_info_service_step' ) ) ?></span>
13
- </div>
14
- <div class="ab-mobile-step_1 ab-row">
15
- <div class="bookly-js-chain-item bookly-table ab-row">
16
- <?php if ( \BooklyLite\Lib\Config::locationsEnabled() ) : ?>
17
- <div class="ab-formGroup">
18
- <?php do_action( 'bookly_locations_render_appearance' ) ?>
19
- </div>
20
- <?php endif ?>
21
- <div class="ab-formGroup">
22
- <label data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_category' ) ) ?>"
23
- class="text_category_label"
24
- id="ab-text-label-category" data-type="multiple"
25
- data-option-id="ab-text-option-category"
26
- ><?php echo esc_html( get_option( 'ab_appearance_text_label_category' ) ) ?></label>
27
- <div>
28
- <select class="ab-select-mobile ab-select-category">
29
- <option value="" class="editable" id="ab-text-option-category" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_option_category' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_option_category' ) ) ?></option>
30
- <option value="1">Cosmetic Dentistry</option>
31
- <option value="2">Invisalign</option>
32
- <option value="3">Orthodontics</option>
33
- <option value="4">Dentures</option>
34
- </select>
35
- </div>
36
- </div>
37
- <div class="ab-formGroup">
38
- <label>
39
- <span
40
- data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_service' ) ) ?>"
41
- data-default-error="<?php echo esc_attr( get_option( 'ab_appearance_text_required_service' ) ) ?>"
42
- data-error-id="ab_appearance_text_required_service"
43
- id="ab-text-label-service"
44
- data-mirror="text_services"
45
- data-type="multiple"
46
- data-option-id="ab-text-option-service"
47
- ><?php echo esc_html( get_option( 'ab_appearance_text_label_service' ) ) ?></span>
48
- </label>
49
- <div>
50
- <select class="ab-select-mobile ab-select-service">
51
- <option class="editable" id="ab-text-option-service" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_option_service' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_option_service' ) ) ?></option>
52
- <option>Crown and Bridge</option>
53
- <option>Teeth Whitening</option>
54
- <option>Veneers</option>
55
- <option>Invisalign (invisable braces)</option>
56
- <option>Orthodontics (braces)</option>
57
- <option>Wisdom tooth Removal</option>
58
- <option>Root Canal Treatment</option>
59
- <option>Dentures</option>
60
- </select>
61
- </div>
62
- </div>
63
- <div class="ab-formGroup">
64
- <label class="text_employee_label">
65
- <span
66
- data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_employee' ) ) ?>"
67
- data-default-error="<?php echo esc_attr( get_option( 'ab_appearance_text_required_employee' ) ) ?>"
68
- data-error-id="ab_appearance_text_required_employee"
69
- id="ab-text-label-employee"
70
- data-mirror="text_employee"
71
- data-type="multiple"
72
- data-option-id="ab-text-option-employee"
73
- ><?php echo esc_html( get_option( 'ab_appearance_text_label_employee' ) ) ?></span>
74
- </label>
75
- <div>
76
- <select class="ab-select-mobile ab-select-employee">
77
- <option value="0" class="editable" id="ab-text-option-employee" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_option_employee' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_option_employee' ) ) ?></option>
78
- <option value="1" class="employee-name-price">Nick Knight (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 350 ) ?>)</option>
79
- <option value="-1" class="employee-name">Nick Knight</option>
80
- <option value="2" class="employee-name-price">Jane Howard (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 375 ) ?>)</option>
81
- <option value="-2" class="employee-name">Jane Howard</option>
82
- <option value="3" class="employee-name-price">Ashley Stamp (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 300 ) ?>)</option>
83
- <option value="-3" class="employee-name">Ashley Stamp</option>
84
- <option value="4" class="employee-name-price">Bradley Tannen (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 400 ) ?>)</option>
85
- <option value="-4" class="employee-name">Bradley Tannen</option>
86
- <option value="5" class="employee-name-price">Wayne Turner (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 350 ) ?>)</option>
87
- <option value="-5" class="employee-name">Wayne Turner</option>
88
- <option value="6" class="employee-name-price">Emily Taylor (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 350 ) ?>)</option>
89
- <option value="-6" class="employee-name">Emily Taylor</option>
90
- <option value="7" class="employee-name-price">Hugh Canberg (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 380 ) ?>)</option>
91
- <option value="-7" class="employee-name">Hugh Canberg</option>
92
- <option value="8" class="employee-name-price">Jim Gonzalez (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 390 ) ?>)</option>
93
- <option value="-8" class="employee-name">Jim Gonzalez</option>
94
- <option value="9" class="employee-name-price">Nancy Stinson (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 360 ) ?>)</option>
95
- <option value="-9" class="employee-name">Nancy Stinson</option>
96
- <option value="10" class="employee-name-price">Marry Murphy (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 350 ) ?>)</option>
97
- <option value="-10" class="employee-name">Marry Murphy</option>
98
- </select>
99
- </div>
100
- </div>
101
- <div class="ab-formGroup">
102
- <label data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_number_of_persons' ) ) ?>" class="ab_editable" data-type="text" id="ab-text-label-number-of-persons"><?php echo esc_html( get_option( 'ab_appearance_text_label_number_of_persons' ) ) ?></label>
103
- <div>
104
- <select class="ab-select-mobile ab-select-number-of-persons">
105
- <option>1</option>
106
- <option>2</option>
107
- <option>3</option>
108
- </select>
109
- </div>
110
- </div>
111
- <?php if ( \BooklyLite\Lib\Config::multiplyAppointmentsEnabled() ) : ?>
112
- <div class="ab-formGroup">
113
- <?php do_action( 'bookly_multiply_render_appearance' ) ?>
114
- </div>
115
- <?php endif ?>
116
- <?php if ( \BooklyLite\Lib\Config::chainAppointmentsEnabled() ) : ?>
117
- <div class="ab-formGroup">
118
- <label></label>
119
- <div class="bookly-js-actions">
120
- <button type="button" class="bookly-round-button" data-action="plus"><img src="<?php echo plugins_url( 'appointment-booking/frontend/resources/images/plus.png' ) ?>" /></button>
121
- </div>
122
- </div>
123
- <?php endif ?>
124
- </div>
125
-
126
- <div class="ab-right ab-mobile-next-step ab-btn ab-none" onclick="return false">
127
- <span class="text_next ab_editable" id="ab-text-button-next" data-mirror="text_next" data-type="text" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_button_next' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_button_next' ) ) ?></span>
128
- </div>
129
- </div>
130
- <div class="ab-mobile-step_2">
131
- <div class="ab-row">
132
- <div class="ab-left">
133
- <div class="ab-available-date ab-left">
134
- <div class="ab-formGroup">
135
- <label data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_select_date' ) ) ?>" class="ab_editable" id="ab-text-label-select_date" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_label_select_date' ) ) ?></label>
136
- <div>
137
- <input class="ab-date-from" style="background-color: #fff;" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
138
- </div>
139
- </div>
140
- </div>
141
- <div class="ab-week-days bookly-table ab-left">
142
- <?php foreach ( $wp_locale->weekday_abbrev as $weekday_abbrev ) : ?>
143
- <div>
144
- <div class="bookly-font-bold"><?php echo $weekday_abbrev ?></div>
145
- <label class="active">
146
- <input class="ab-week-day" value="1" checked="checked" type="checkbox">
147
- </label>
148
- </div>
149
- <?php endforeach ?>
150
- </div>
151
- </div>
152
- <div class="ab-time-range ab-left">
153
- <div class="ab-formGroup ab-time-from ab-left">
154
- <label data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_start_from' ) ) ?>" class="ab_editable" id="ab-text-label-start_from" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_label_start_from' ) ) ?></label>
155
- <div>
156
- <select class="ab-select-time-from">
157
- <?php for ( $i = 28800; $i <= 64800; $i += 3600 ) : ?>
158
- <option><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?></option>
159
- <?php endfor ?>
160
- </select>
161
- </div>
162
- </div>
163
- <div class="ab-formGroup ab-time-to ab-left">
164
- <label data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_finish_by' ) ) ?>" class="ab_editable" id="ab-text-label-finish_by" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_label_finish_by' ) ) ?></label>
165
- <div>
166
- <select class="ab-select-time-to">
167
- <?php for ( $i = 28800; $i <= 64800; $i += 3600 ) : ?>
168
- <option<?php selected( $i == 64800 ) ?>><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?></option>
169
- <?php endfor ?>
170
- </select>
171
- </div>
172
- </div>
173
- </div>
174
- </div>
175
- <div class="ab-row ab-nav-steps">
176
- <div class="ab-right ab-mobile-prev-step ab-btn ab-none">
177
- <span class="text_back ab_editable" id="ab-text-button-back" data-mirror="text_back" data-type="text" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_button_back' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_button_back' ) ) ?></span>
178
- </div>
179
- <div class="ab-right ab-next-step ab-btn">
180
- <span class="text_next ab_editable" id="ab-text-button-next" data-mirror="text_next" data-type="text" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_button_next' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_button_next' ) ) ?></span>
181
- </div>
182
- <button class="ab-left ab-goto-cart bookly-round-button ladda-button"><span><img src="<?php echo plugins_url( 'appointment-booking/frontend/resources/images/cart.png' ) ?>" /></span></button>
183
- </div>
184
- </div>
185
- </div>
186
- </div>
187
- <div style="display: none">
188
- <?php foreach ( array( 'ab_appearance_text_required_service', 'ab_appearance_text_required_name', 'ab_appearance_text_required_phone', 'ab_appearance_text_required_email', 'ab_appearance_text_required_employee', 'ab_appearance_text_required_location' ) as $validator ) : ?>
189
- <div id="<?php echo $validator ?>"><?php echo get_option( $validator ) ?></div>
190
- <?php endforeach ?>
191
- </div>
192
- <style id="ab--style-arrow">
193
- .picker__nav--next:before { border-left: 6px solid <?php echo get_option( 'ab_appearance_color' ) ?>!important; }
194
- .picker__nav--prev:before { border-right: 6px solid <?php echo get_option( 'ab_appearance_color' ) ?>!important; }
195
  </style>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ /** @var WP_Locale $wp_locale */
3
+ global $wp_locale;
4
+ ?>
5
+ <div class="bookly-form">
6
+ <?php include '_progress_tracker.php' ?>
7
+
8
+ <div class="bookly-service-step">
9
+ <div class="bookly-box">
10
+ <span data-option-default="<?php form_option( 'bookly_l10n_info_service_step' ) ?>"
11
+ class="bookly-editable ab-bold ab-desc" id="bookly_l10n_info_service_step"
12
+ data-rows="7" data-type="textarea"><?php echo esc_html( get_option( 'bookly_l10n_info_service_step' ) ) ?></span>
13
+ </div>
14
+ <div class="ab-mobile-step_1 bookly-box">
15
+ <div class="bookly-js-chain-item bookly-table bookly-box">
16
+ <?php if ( \BooklyLite\Lib\Config::isLocationsEnabled() ) : ?>
17
+ <div class="ab-formGroup">
18
+ <?php do_action( 'bookly_locations_render_appearance' ) ?>
19
+ </div>
20
+ <?php endif ?>
21
+ <div class="ab-formGroup">
22
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderLabel( array( 'bookly_l10n_label_category', 'bookly_l10n_option_category', ) ) ?>
23
+ <div>
24
+ <select class="ab-select-mobile ab-select-category">
25
+ <option value="" id="bookly_l10n_option_category"><?php echo esc_html( get_option( 'bookly_l10n_option_category' ) ) ?></option>
26
+ <option value="1">Cosmetic Dentistry</option>
27
+ <option value="2">Invisalign</option>
28
+ <option value="3">Orthodontics</option>
29
+ <option value="4">Dentures</option>
30
+ </select>
31
+ </div>
32
+ </div>
33
+ <div class="ab-formGroup">
34
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderLabel( array(
35
+ 'bookly_l10n_label_service',
36
+ 'bookly_l10n_option_service',
37
+ 'bookly_l10n_required_service',
38
+ ) ) ?>
39
+ <div>
40
+ <select class="ab-select-mobile ab-select-service">
41
+ <option id="bookly_l10n_option_service"><?php echo esc_html( get_option( 'bookly_l10n_option_service' ) ) ?></option>
42
+ <option>Crown and Bridge</option>
43
+ <option>Teeth Whitening</option>
44
+ <option>Veneers</option>
45
+ <option>Invisalign (invisable braces)</option>
46
+ <option>Orthodontics (braces)</option>
47
+ <option>Wisdom tooth Removal</option>
48
+ <option>Root Canal Treatment</option>
49
+ <option>Dentures</option>
50
+ </select>
51
+ </div>
52
+ </div>
53
+ <div class="ab-formGroup">
54
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderLabel( array(
55
+ 'bookly_l10n_label_employee',
56
+ 'bookly_l10n_option_employee',
57
+ 'bookly_l10n_required_employee',
58
+ ) ) ?>
59
+ <div>
60
+ <select class="ab-select-mobile ab-select-employee">
61
+ <option value="0" id="bookly_l10n_option_employee"><?php echo esc_html( get_option( 'bookly_l10n_option_employee' ) ) ?></option>
62
+ <option value="1" class="employee-name-price">Nick Knight (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 350 ) ?>)</option>
63
+ <option value="-1" class="employee-name">Nick Knight</option>
64
+ <option value="2" class="employee-name-price">Jane Howard (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 375 ) ?>)</option>
65
+ <option value="-2" class="employee-name">Jane Howard</option>
66
+ <option value="3" class="employee-name-price">Ashley Stamp (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 300 ) ?>)</option>
67
+ <option value="-3" class="employee-name">Ashley Stamp</option>
68
+ <option value="4" class="employee-name-price">Bradley Tannen (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 400 ) ?>)</option>
69
+ <option value="-4" class="employee-name">Bradley Tannen</option>
70
+ <option value="5" class="employee-name-price">Wayne Turner (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 350 ) ?>)</option>
71
+ <option value="-5" class="employee-name">Wayne Turner</option>
72
+ <option value="6" class="employee-name-price">Emily Taylor (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 350 ) ?>)</option>
73
+ <option value="-6" class="employee-name">Emily Taylor</option>
74
+ <option value="7" class="employee-name-price">Hugh Canberg (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 380 ) ?>)</option>
75
+ <option value="-7" class="employee-name">Hugh Canberg</option>
76
+ <option value="8" class="employee-name-price">Jim Gonzalez (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 390 ) ?>)</option>
77
+ <option value="-8" class="employee-name">Jim Gonzalez</option>
78
+ <option value="9" class="employee-name-price">Nancy Stinson (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 360 ) ?>)</option>
79
+ <option value="-9" class="employee-name">Nancy Stinson</option>
80
+ <option value="10" class="employee-name-price">Marry Murphy (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 350 ) ?>)</option>
81
+ <option value="-10" class="employee-name">Marry Murphy</option>
82
+ </select>
83
+ </div>
84
+ </div>
85
+ <div class="ab-formGroup">
86
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderLabel( array( 'bookly_l10n_label_number_of_persons', ) ) ?>
87
+ <div>
88
+ <select class="ab-select-mobile ab-select-number-of-persons">
89
+ <option>1</option>
90
+ <option>2</option>
91
+ <option>3</option>
92
+ </select>
93
+ </div>
94
+ </div>
95
+ <?php if ( \BooklyLite\Lib\Config::isMultiplyAppointmentsEnabled() ) : ?>
96
+ <div class="ab-formGroup">
97
+ <?php do_action( 'bookly_multiply_appointments_render_appearance' ) ?>
98
+ </div>
99
+ <?php endif ?>
100
+ <?php if ( \BooklyLite\Lib\Config::isChainAppointmentsEnabled() ) : ?>
101
+ <div class="ab-formGroup">
102
+ <label></label>
103
+ <div>
104
+ <button class="bookly-round" ><i class="bookly-icon-sm bookly-icon-plus"></i></button>
105
+ </div>
106
+ </div>
107
+ <?php endif ?>
108
+ </div>
109
+
110
+ <div class="ab-right ab-mobile-next-step ab-btn ab-none" onclick="return false">
111
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_button_next' ), 'bookly-js-text-next' ) ?>
112
+ </div>
113
+ </div>
114
+ <div class="ab-mobile-step_2">
115
+ <div class="bookly-box">
116
+ <div class="ab-left">
117
+ <div class="ab-available-date ab-left">
118
+ <div class="ab-formGroup">
119
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderLabel( array( 'bookly_l10n_label_select_date', ) ) ?>
120
+ <div>
121
+ <input class="ab-date-from" style="background-color: #fff;" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
122
+ </div>
123
+ </div>
124
+ </div>
125
+ <div class="bookly-week-days bookly-table ab-left">
126
+ <?php foreach ( $wp_locale->weekday_abbrev as $weekday_abbrev ) : ?>
127
+ <div>
128
+ <div class="bookly-font-bold"><?php echo $weekday_abbrev ?></div>
129
+ <label class="active">
130
+ <input class="bookly-week-day" value="1" checked="checked" type="checkbox">
131
+ </label>
132
+ </div>
133
+ <?php endforeach ?>
134
+ </div>
135
+ </div>
136
+ <div class="ab-time-range ab-left">
137
+ <div class="ab-formGroup ab-time-from ab-left">
138
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderLabel( array( 'bookly_l10n_label_start_from', ) ) ?>
139
+ <div>
140
+ <select class="ab-select-time-from">
141
+ <?php for ( $i = 28800; $i <= 64800; $i += 3600 ) : ?>
142
+ <option><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?></option>
143
+ <?php endfor ?>
144
+ </select>
145
+ </div>
146
+ </div>
147
+ <div class="ab-formGroup ab-time-to ab-left">
148
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderLabel( array( 'bookly_l10n_label_finish_by', ) ) ?>
149
+ <div>
150
+ <select class="ab-select-time-to">
151
+ <?php for ( $i = 28800; $i <= 64800; $i += 3600 ) : ?>
152
+ <option<?php selected( $i == 64800 ) ?>><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?></option>
153
+ <?php endfor ?>
154
+ </select>
155
+ </div>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ <div class="bookly-box bookly-nav-steps">
160
+ <div class="ab-right ab-mobile-prev-step ab-btn ab-none">
161
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_button_back' ), 'bookly-js-text-back' ) ?>
162
+ </div>
163
+ <div class="bookly-next-step ab-btn">
164
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_button_next' ), 'bookly-js-text-next' ) ?>
165
+ </div>
166
+ <button class="bookly-go-to-cart bookly-round bookly-round-md ladda-button"><span><img src="<?php echo plugins_url( 'bookly-responsive-appointment-booking-tool/frontend/resources/images/cart.png' ) ?>" /></span></button>
167
+ </div>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ <div style="display: none">
172
+ <?php foreach ( array( 'bookly_l10n_required_service', 'bookly_l10n_required_name', 'bookly_l10n_required_phone', 'bookly_l10n_required_email', 'bookly_l10n_required_employee', 'bookly_l10n_required_location' ) as $validator ) : ?>
173
+ <div id="<?php echo $validator ?>"><?php echo get_option( $validator ) ?></div>
174
+ <?php endforeach ?>
175
+ </div>
176
+ <style id="ab--style-arrow">
177
+ .picker__nav--next:before { border-left: 6px solid <?php echo get_option( 'bookly_app_color' ) ?>!important; }
178
+ .picker__nav--prev:before { border-right: 6px solid <?php echo get_option( 'bookly_app_color' ) ?>!important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  </style>
backend/modules/appearance/templates/_3_time.php CHANGED
@@ -1,165 +1,165 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="bookly-form">
3
- <?php include '_progress_tracker.php' ?>
4
-
5
- <div class="ab-row">
6
- <span data-inputclass="input-xxlarge" data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 3 ), false ) ) ?>" data-placement="bottom" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_time_step' ) ) ?>" class="ab_editable" id="ab-text-info-time" data-type="textarea"><?php echo esc_html( get_option( 'ab_appearance_text_info_time_step' ) ) ?></span>
7
- </div>
8
- <!-- timeslots -->
9
- <div class="ab-time-step">
10
- <div class="ab-columnizer-wrap">
11
- <div class="ab-columnizer">
12
- <div class="ab-time-screen ab-day-columns" style="display: <?php echo get_option( 'ab_appearance_show_day_one_column' ) == 1 ? ' none' : 'block' ?>">
13
- <div class="ab-input-wrap ab-slot-calendar">
14
- <span class="ab-date-wrap">
15
- <input style="display: none" class="ab-selected-date ab-formElement" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
16
- </span>
17
- </div>
18
- <div class="ab-column col1">
19
- <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d' ) ?></button>
20
- <?php for ( $i = 28800; $i <= 57600; $i += 3600 ) : ?>
21
- <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
22
- <span class="ladda-label">
23
- <i class="ab-hour-icon"><span></span></i>
24
- <?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
25
- </span>
26
- </button>
27
- <?php endfor ?>
28
- </div>
29
- <div class="ab-column col2">
30
- <button class="ab-hour ladda-button ab-last-child">
31
- <span class="ladda-label">
32
- <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( 61200 ) ?>
33
- </span>
34
- </button>
35
- <button class="ab-day ab-first-child" style="display: <?php echo get_option( 'ab_appearance_show_calendar' ) == 1 ? ' none' : 'block' ?>"><?php echo date_i18n( 'D, M d', strtotime( '+1 day' ) ) ?></button>
36
- <?php for ( $i = 28800; $i <= 54000; $i += 3600 ) : ?>
37
- <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>" style="display: <?php echo get_option( 'ab_appearance_show_calendar' ) == 1 ? ' none' : 'block' ?>">
38
- <span class="ladda-label">
39
- <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
40
- </span>
41
- </button>
42
- <?php endfor ?>
43
- </div>
44
- <div class="ab-column col3" style="display: <?php echo get_option( 'ab_appearance_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
45
- <?php for ( $i = 57600; $i <= 61200; $i += 3600 ) : ?>
46
- <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
47
- <span class="ladda-label">
48
- <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
49
- </span>
50
- </button>
51
- <?php endfor ?>
52
- <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+2 day' ) ) ?></button>
53
- <?php for ( $i = 28800; $i <= 50400; $i += 3600 ) : ?>
54
- <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
55
- <span class="ladda-label">
56
- <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
57
- </span>
58
- </button>
59
- <?php endfor ?>
60
- </div>
61
- <div class="ab-column col4" style="display: <?php echo get_option( 'ab_appearance_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
62
- <?php for ( $i = 54000; $i <= 61200; $i += 3600 ) : ?>
63
- <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
64
- <span class="ladda-label">
65
- <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
66
- </span>
67
- </button>
68
- <?php endfor ?>
69
- <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+3 day' ) ) ?></button>
70
- <?php for ( $i = 28800; $i <= 46800; $i += 3600 ) : ?>
71
- <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
72
- <span class="ladda-label">
73
- <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
74
- </span>
75
- </button>
76
- <?php endfor ?>
77
- </div>
78
- <div class="ab-column col5" style="display:<?php echo get_option( 'ab_appearance_show_calendar' ) == 1 ? ' none' : ' inline-block' ?>">
79
- <?php for ( $i = 50400; $i <= 61200; $i += 3600 ) : ?>
80
- <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
81
- <span class="ladda-label">
82
- <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
83
- </span>
84
- </button>
85
- <?php endfor ?>
86
- <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+4 day' ) ) ?></button>
87
- <?php for ( $i = 28800; $i <= 43200; $i += 3600 ) : ?>
88
- <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
89
- <span class="ladda-label">
90
- <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
91
- </span>
92
- </button>
93
- <?php endfor ?>
94
- </div>
95
- <div class="ab-column col6" style="display: <?php echo get_option( 'ab_appearance_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
96
- <?php for ( $i = 46800; $i <= 61200; $i += 3600 ) : ?>
97
- <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
98
- <span class="ladda-label">
99
- <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
100
- </span>
101
- </button>
102
- <?php endfor ?>
103
- <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+5 day' ) ) ?></button>
104
- <?php for ( $i = 28800; $i <= 39600; $i += 3600 ) : ?>
105
- <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
106
- <span class="ladda-label">
107
- <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
108
- </span>
109
- </button>
110
- <?php endfor ?>
111
- </div>
112
- <div class="ab-column col7" style="display:<?php echo get_option( 'ab_appearance_show_calendar' ) == 1 ? ' none' : ' inline-block' ?>">
113
- <?php for ( $i = 43200; $i <= 61200; $i += 3600 ) : ?>
114
- <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
115
- <span class="ladda-label">
116
- <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
117
- </span>
118
- </button>
119
- <?php endfor ?>
120
- <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+6 day' ) ) ?></button>
121
- <?php for ( $i = 28800; $i <= 36000; $i += 3600 ) : ?>
122
- <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
123
- <span class="ladda-label">
124
- <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
125
- </span>
126
- </button>
127
- <?php endfor ?>
128
- </div>
129
- </div>
130
-
131
- <div class="ab-time-screen ab-day-one-column" style="display: <?php echo get_option( 'ab_appearance_show_day_one_column' ) == 1 ? ' block' : 'none' ?>">
132
- <div class="ab-input-wrap ab-slot-calendar">
133
- <span class="ab-date-wrap">
134
- <input style="display: none" class="ab-selected-date ab-formElement" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
135
- </span>
136
- </div>
137
- <?php for ( $i = 1; $i <= 7; ++ $i ) : ?>
138
- <div class="ab-column col<?php echo $i ?>">
139
- <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+' . ( $i - 1 ) . ' day' ) ) ?></button>
140
- <?php for ( $j = 28800; $j <= 61200; $j += 3600 ) : ?>
141
- <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
142
- <span class="ladda-label">
143
- <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $j ) ?>
144
- </span>
145
- </button>
146
- <?php endfor ?>
147
- </div>
148
- <?php endfor ?>
149
- </div>
150
- </div>
151
- </div>
152
- </div>
153
- <div class="ab-row ab-nav-steps">
154
- <button class="ab-time-next ab-btn ab-right ladda-button">
155
- <span class="ab_label">&gt;</span>
156
- </button>
157
- <button class="ab-time-prev ab-btn ab-right ladda-button">
158
- <span class="ab_label">&lt;</span>
159
- </button>
160
- <div class="ab-left ab-back-step ab-btn">
161
- <span class="text_back ab_editable" id="ab-text-button-back" data-mirror="text_back" data-type="text" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_button_back' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_button_back' ) ) ?></span>
162
- </div>
163
- <button class="ab-left ab-goto-cart bookly-round-button ladda-button" data-style="zoom-in" data-spinner-size="30"><span class="ladda-label"><img src="<?php echo plugins_url( 'appointment-booking/frontend/resources/images/cart.png' ) ?>" /></span></button>
164
- </div>
165
- </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div class="bookly-form">
3
+ <?php include '_progress_tracker.php' ?>
4
+
5
+ <div class="bookly-box">
6
+ <span data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 3 ), false ) ) ?>" data-placement="bottom" data-option-default="<?php form_option( 'bookly_l10n_info_time_step' ) ?>" class="bookly-editable" id="bookly_l10n_info_time_step" data-type="textarea"><?php echo esc_html( get_option( 'bookly_l10n_info_time_step' ) ) ?></span>
7
+ </div>
8
+ <!-- timeslots -->
9
+ <div class="ab-time-step">
10
+ <div class="ab-columnizer-wrap">
11
+ <div class="ab-columnizer">
12
+ <div class="ab-time-screen ab-day-columns" style="display: <?php echo get_option( 'bookly_app_show_day_one_column' ) == 1 ? ' none' : 'block' ?>">
13
+ <div class="ab-input-wrap ab-slot-calendar">
14
+ <span class="ab-date-wrap">
15
+ <input style="display: none" class="ab-selected-date ab-formElement" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
16
+ </span>
17
+ </div>
18
+ <div class="ab-column col1">
19
+ <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d', current_time( 'timestamp' ) ) ?></button>
20
+ <?php for ( $i = 28800; $i <= 57600; $i += 3600 ) : ?>
21
+ <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
22
+ <span class="ladda-label">
23
+ <i class="ab-hour-icon"><span></span></i>
24
+ <?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
25
+ </span>
26
+ </button>
27
+ <?php endfor ?>
28
+ </div>
29
+ <div class="ab-column col2">
30
+ <button class="ab-hour ladda-button ab-last-child">
31
+ <span class="ladda-label">
32
+ <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( 61200 ) ?>
33
+ </span>
34
+ </button>
35
+ <button class="ab-day ab-first-child" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'block' ?>"><?php echo date_i18n( 'D, M d', strtotime( '+1 day', current_time( 'timestamp' ) ) ) ?></button>
36
+ <?php for ( $i = 28800; $i <= 54000; $i += 3600 ) : ?>
37
+ <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'block' ?>">
38
+ <span class="ladda-label">
39
+ <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
40
+ </span>
41
+ </button>
42
+ <?php endfor ?>
43
+ </div>
44
+ <div class="ab-column col3" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
45
+ <?php for ( $i = 57600; $i <= 61200; $i += 3600 ) : ?>
46
+ <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
47
+ <span class="ladda-label">
48
+ <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
49
+ </span>
50
+ </button>
51
+ <?php endfor ?>
52
+ <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+2 days', current_time('timestamp') ) ) ?></button>
53
+ <?php for ( $i = 28800; $i <= 50400; $i += 3600 ) : ?>
54
+ <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
55
+ <span class="ladda-label">
56
+ <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
57
+ </span>
58
+ </button>
59
+ <?php endfor ?>
60
+ </div>
61
+ <div class="ab-column col4" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
62
+ <?php for ( $i = 54000; $i <= 61200; $i += 3600 ) : ?>
63
+ <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
64
+ <span class="ladda-label">
65
+ <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
66
+ </span>
67
+ </button>
68
+ <?php endfor ?>
69
+ <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+3 days', current_time( 'timestamp' ) ) ) ?></button>
70
+ <?php for ( $i = 28800; $i <= 46800; $i += 3600 ) : ?>
71
+ <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
72
+ <span class="ladda-label">
73
+ <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
74
+ </span>
75
+ </button>
76
+ <?php endfor ?>
77
+ </div>
78
+ <div class="ab-column col5" style="display:<?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : ' inline-block' ?>">
79
+ <?php for ( $i = 50400; $i <= 61200; $i += 3600 ) : ?>
80
+ <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
81
+ <span class="ladda-label">
82
+ <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
83
+ </span>
84
+ </button>
85
+ <?php endfor ?>
86
+ <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+4 days', current_time( 'timestamp' ) ) ) ?></button>
87
+ <?php for ( $i = 28800; $i <= 43200; $i += 3600 ) : ?>
88
+ <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
89
+ <span class="ladda-label">
90
+ <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
91
+ </span>
92
+ </button>
93
+ <?php endfor ?>
94
+ </div>
95
+ <div class="ab-column col6" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
96
+ <?php for ( $i = 46800; $i <= 61200; $i += 3600 ) : ?>
97
+ <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
98
+ <span class="ladda-label">
99
+ <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
100
+ </span>
101
+ </button>
102
+ <?php endfor ?>
103
+ <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+5 days', current_time( 'timestamp' ) ) ) ?></button>
104
+ <?php for ( $i = 28800; $i <= 39600; $i += 3600 ) : ?>
105
+ <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
106
+ <span class="ladda-label">
107
+ <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
108
+ </span>
109
+ </button>
110
+ <?php endfor ?>
111
+ </div>
112
+ <div class="ab-column col7" style="display:<?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : ' inline-block' ?>">
113
+ <?php for ( $i = 43200; $i <= 61200; $i += 3600 ) : ?>
114
+ <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
115
+ <span class="ladda-label">
116
+ <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
117
+ </span>
118
+ </button>
119
+ <?php endfor ?>
120
+ <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+6 days', current_time( 'timestamp' ) ) ) ?></button>
121
+ <?php for ( $i = 28800; $i <= 36000; $i += 3600 ) : ?>
122
+ <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
123
+ <span class="ladda-label">
124
+ <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
125
+ </span>
126
+ </button>
127
+ <?php endfor ?>
128
+ </div>
129
+ </div>
130
+
131
+ <div class="ab-time-screen ab-day-one-column" style="display: <?php echo get_option( 'bookly_app_show_day_one_column' ) == 1 ? ' block' : 'none' ?>">
132
+ <div class="ab-input-wrap ab-slot-calendar">
133
+ <span class="ab-date-wrap">
134
+ <input style="display: none" class="ab-selected-date ab-formElement" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
135
+ </span>
136
+ </div>
137
+ <?php for ( $i = 1; $i <= 7; ++ $i ) : ?>
138
+ <div class="ab-column col<?php echo $i ?>">
139
+ <button class="ab-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+' . ( $i - 1 ) . ' days', current_time( 'timestamp' ) ) ) ?></button>
140
+ <?php for ( $j = 28800; $j <= 61200; $j += 3600 ) : ?>
141
+ <button class="ab-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
142
+ <span class="ladda-label">
143
+ <i class="ab-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $j ) ?>
144
+ </span>
145
+ </button>
146
+ <?php endfor ?>
147
+ </div>
148
+ <?php endfor ?>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ </div>
153
+ <div class="bookly-box bookly-nav-steps">
154
+ <button class="ab-time-next ab-btn ab-right ladda-button">
155
+ <span class="ab_label">&gt;</span>
156
+ </button>
157
+ <button class="ab-time-prev ab-btn ab-right ladda-button">
158
+ <span class="ab_label">&lt;</span>
159
+ </button>
160
+ <div class="bookly-back-step ab-btn">
161
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_button_back' ), 'bookly-js-text-back' ) ?>
162
+ </div>
163
+ <button class="bookly-go-to-cart bookly-round bookly-round-md ladda-button" data-style="zoom-in" data-spinner-size="30"><span class="ladda-label"><img src="<?php echo plugins_url( 'bookly-responsive-appointment-booking-tool/frontend/resources/images/cart.png' ) ?>" /></span></button>
164
+ </div>
165
+ </div>
backend/modules/appearance/templates/_4_cart.php DELETED
@@ -1,132 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="bookly-form">
3
- <?php include '_progress_tracker.php' ?>
4
-
5
- <div class="ab-row">
6
- <span data-inputclass="input-xxlarge" data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 4 ), false ) ) ?>" data-placement="bottom" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_cart_step' ) ) ?>" class="ab_editable" id="ab-text-info-cart" data-type="textarea"><?php echo esc_html( get_option( 'ab_appearance_text_info_cart_step' ) ) ?></span>
7
- </div>
8
-
9
- <div class="ab-row">
10
- <div class="ab-btn ab-add-item ab-inline-block">
11
- <span class="ab_editable" id="ab-text-button-book-more" data-type="text" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_button_book_more' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_button_book_more' ) ) ?></span>
12
- </div>
13
- </div>
14
-
15
- <div class="ab-cart-step">
16
- <div class="ab-cart ab-row">
17
- <table>
18
- <thead class="ab-desktop-version">
19
- <tr>
20
- <th data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_service' ) ) ?>" class="ab-service-list"><?php echo esc_html( get_option( 'ab_appearance_text_label_service' ) ) ?></th>
21
- <th><?php _e( 'Date', 'bookly' ) ?></th>
22
- <th><?php _e( 'Time', 'bookly' ) ?></th>
23
- <th data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_employee' ) ) ?>" class="ab-employee-list"><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_employee' ) ?></th>
24
- <th><?php _e( 'Price', 'bookly' ) ?></th>
25
- <th></th>
26
- </tr>
27
- </thead>
28
- <tbody class="ab-desktop-version">
29
- <tr>
30
- <td>Crown and Bridge</td>
31
- <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( strtotime( '+2 days' ) ) ?></td>
32
- <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( 28800 ) ?></td>
33
- <td>Nick Knight</td>
34
- <td><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 350 ) ?></td>
35
- <td>
36
- <button class="bookly-round-button ladda-button" title="<?php esc_attr_e( 'Edit', 'bookly' ) ?>"><img src="<?php echo plugins_url( 'appointment-booking/frontend/resources/images/edit.png' ) ?>" /></button>
37
- <button class="bookly-round-button ladda-button" title="<?php esc_attr_e( 'Remove', 'bookly' ) ?>"><img src="<?php echo plugins_url( 'appointment-booking/frontend/resources/images/delete.png' ) ?>" /></button>
38
- </td>
39
- </tr>
40
- <tr>
41
- <td>Teeth Whitening</td>
42
- <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( strtotime( '+3 days' ) ) ?></td>
43
- <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( 57600 ) ?></td>
44
- <td>Wayne Turner</td>
45
- <td><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 400 ) ?></td>
46
- <td>
47
- <button class="bookly-round-button ladda-button" title="<?php esc_attr_e( 'Edit', 'bookly' ) ?>"><img src="<?php echo plugins_url( 'appointment-booking/frontend/resources/images/edit.png' ) ?>" /></button>
48
- <button class="bookly-round-button ladda-button" title="<?php esc_attr_e( 'Remove', 'bookly' ) ?>"><img src="<?php echo plugins_url( 'appointment-booking/frontend/resources/images/delete.png' ) ?>" /></button>
49
- </td>
50
- </tr>
51
- </tbody>
52
- <tbody class="ab-mobile-version">
53
- <tr>
54
- <th data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_service' ) ) ?>" class="ab-service-list"><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_service' ) ?></th>
55
- <td>Crown and Bridge</td>
56
- </tr>
57
- <tr>
58
- <th><?php _e( 'Date', 'bookly' ) ?></th>
59
- <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( strtotime( '+2 days' ) ) ?></td>
60
- </tr>
61
- <tr>
62
- <th><?php _e( 'Time', 'bookly' ) ?></th>
63
- <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( 28800 ) ?></td>
64
- </tr>
65
- <tr>
66
- <th data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_employee' ) ) ?>" class="ab-employee-list"><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_employee' ) ?></th>
67
- <td>Nick Knight</td>
68
- </tr>
69
- <tr>
70
- <th><?php _e( 'Price', 'bookly' ) ?></th>
71
- <td><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 350 ) ?></td>
72
- </tr>
73
- <tr>
74
- <th></th>
75
- <td>
76
- <button class="bookly-round-button ladda-button" title="<?php esc_attr_e( 'Edit', 'bookly' ) ?>"><img src="<?php echo plugins_url( 'appointment-booking/frontend/resources/images/edit.png' ) ?>" /></button>
77
- <button class="bookly-round-button ladda-button" title="<?php esc_attr_e( 'Remove', 'bookly' ) ?>"><img src="<?php echo plugins_url( 'appointment-booking/frontend/resources/images/delete.png' ) ?>" /></button>
78
- </td>
79
- </tr>
80
- <tr>
81
- <th data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_service' ) ) ?>" class="ab-service-list"><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_service' ) ?></th>
82
- <td>Teeth Whitening</td>
83
- </tr>
84
- <tr>
85
- <th><?php _e( 'Date', 'bookly' ) ?></th>
86
- <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( strtotime( '+3 days' ) ) ?></td>
87
- </tr>
88
- <tr>
89
- <th><?php _e( 'Time', 'bookly' ) ?></th>
90
- <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( 57600 ) ?></td>
91
- </tr>
92
- <tr>
93
- <th data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_employee' ) ) ?>" class="ab-employee-list"><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_employee' ) ?></th>
94
- <td>Wayne Turner</td>
95
- </tr>
96
- <tr>
97
- <th><?php _e( 'Price', 'bookly' ) ?></th>
98
- <td><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 400 ) ?></td>
99
- </tr>
100
- <tr>
101
- <th></th>
102
- <td>
103
- <button class="bookly-round-button ladda-button" title="<?php esc_attr_e( 'Edit', 'bookly' ) ?>"><img src="<?php echo plugins_url( 'appointment-booking/frontend/resources/images/edit.png' ) ?>" /></button>
104
- <button class="bookly-round-button ladda-button" title="<?php esc_attr_e( 'Remove', 'bookly' ) ?>"><img src="<?php echo plugins_url( 'appointment-booking/frontend/resources/images/delete.png' ) ?>" /></button>
105
- </td>
106
- </tr>
107
- </tbody>
108
- <tfoot class="ab-desktop-version">
109
- <tr>
110
- <td colspan="4"><strong><?php _e( 'Total', 'bookly' ) ?>:</strong></td>
111
- <td><strong class="bookly-js-total-price"><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 750 ) ?></strong></td>
112
- <td></td>
113
- </tr>
114
- </tfoot>
115
- <tfoot class="ab-mobile-version">
116
- <tr>
117
- <th><strong><?php _e( 'Total', 'bookly' ) ?>:</strong></th>
118
- <td><strong class="bookly-js-total-price"><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 750 ) ?></strong></td>
119
- </tr>
120
- </tfoot>
121
- </table>
122
- </div>
123
- </div>
124
- <div class="ab-row ab-nav-steps">
125
- <div class="ab-left ab-back-step ab-btn">
126
- <span class="text_back ab_editable" id="ab-text-button-back" data-mirror="text_back" data-type="text" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_button_back' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_button_back' ) ) ?></span>
127
- </div>
128
- <div class="ab-right ab-next-step ab-btn">
129
- <span class="text_next ab_editable" id="ab-text-button-next" data-mirror="text_next" data-type="text" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_button_next' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_button_next' ) ) ?></span>
130
- </div>
131
- </div>
132
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appearance/templates/_5_cart.php ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div class="bookly-form">
3
+ <?php include '_progress_tracker.php' ?>
4
+
5
+ <div class="bookly-box">
6
+ <span data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 5 ), false ) ) ?>" data-placement="bottom" data-default="<?php form_option( 'bookly_l10n_info_cart_step' ) ?>" class="bookly-editable" id="bookly_l10n_info_cart_step" data-type="textarea"><?php echo esc_html( get_option( 'bookly_l10n_info_cart_step' ) ) ?></span>
7
+ </div>
8
+
9
+ <div class="bookly-box">
10
+ <div class="ab-btn ab-add-item ab-inline-block">
11
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_button_book_more', ) ) ?>
12
+ </div>
13
+ </div>
14
+
15
+ <div class="ab-cart-step">
16
+ <div class="ab-cart bookly-box">
17
+ <table>
18
+ <thead class="ab-desktop-version">
19
+ <tr>
20
+ <th data-default="<?php form_option( 'bookly_l10n_label_service' ) ?>" class="ab-service-list"><?php echo esc_html( get_option( 'bookly_l10n_label_service' ) ) ?></th>
21
+ <th><?php _e( 'Date', 'bookly' ) ?></th>
22
+ <th><?php _e( 'Time', 'bookly' ) ?></th>
23
+ <th data-default="<?php form_option( 'bookly_l10n_label_employee' ) ?>" class="ab-employee-list"><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_employee' ) ?></th>
24
+ <th><?php _e( 'Price', 'bookly' ) ?></th>
25
+ <th></th>
26
+ </tr>
27
+ </thead>
28
+ <tbody class="ab-desktop-version">
29
+ <tr>
30
+ <td>Crown and Bridge</td>
31
+ <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( '+2 days' ) ?></td>
32
+ <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( 28800 ) ?></td>
33
+ <td>Nick Knight</td>
34
+ <td><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 350 ) ?></td>
35
+ <td>
36
+ <button class="bookly-round" title="<?php esc_attr_e( 'Edit', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-edit"></i></button>
37
+ <button class="bookly-round" title="<?php esc_attr_e( 'Remove', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-drop"></i></button>
38
+ </td>
39
+ </tr>
40
+ <tr>
41
+ <td>Teeth Whitening</td>
42
+ <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( '+3 days' ) ?></td>
43
+ <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( 57600 ) ?></td>
44
+ <td>Wayne Turner</td>
45
+ <td><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 400 ) ?></td>
46
+ <td>
47
+ <button class="bookly-round" title="<?php esc_attr_e( 'Edit', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-edit"></i></button>
48
+ <button class="bookly-round" title="<?php esc_attr_e( 'Remove', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-drop"></i></button>
49
+ </td>
50
+ </tr>
51
+ </tbody>
52
+ <tbody class="ab-mobile-version">
53
+ <tr>
54
+ <th data-default="<?php form_option( 'bookly_l10n_label_service' ) ?>" class="ab-service-list"><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_service' ) ?></th>
55
+ <td>Crown and Bridge</td>
56
+ </tr>
57
+ <tr>
58
+ <th><?php _e( 'Date', 'bookly' ) ?></th>
59
+ <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( '+2 days' ) ?></td>
60
+ </tr>
61
+ <tr>
62
+ <th><?php _e( 'Time', 'bookly' ) ?></th>
63
+ <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( 28800 ) ?></td>
64
+ </tr>
65
+ <tr>
66
+ <th data-default="<?php form_option( 'bookly_l10n_label_employee' ) ?>" class="ab-employee-list"><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_employee' ) ?></th>
67
+ <td>Nick Knight</td>
68
+ </tr>
69
+ <tr>
70
+ <th><?php _e( 'Price', 'bookly' ) ?></th>
71
+ <td><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 350 ) ?></td>
72
+ </tr>
73
+ <tr>
74
+ <th></th>
75
+ <td>
76
+ <button class="bookly-round" title="<?php esc_attr_e( 'Edit', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-edit"></i></button>
77
+ <button class="bookly-round" title="<?php esc_attr_e( 'Remove', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-drop"></i></button>
78
+ </td>
79
+ </tr>
80
+ <tr>
81
+ <th data-default="<?php form_option( 'bookly_l10n_label_service' ) ?>" class="ab-service-list"><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_service' ) ?></th>
82
+ <td>Teeth Whitening</td>
83
+ </tr>
84
+ <tr>
85
+ <th><?php _e( 'Date', 'bookly' ) ?></th>
86
+ <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( '+3 days' ) ?></td>
87
+ </tr>
88
+ <tr>
89
+ <th><?php _e( 'Time', 'bookly' ) ?></th>
90
+ <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( 57600 ) ?></td>
91
+ </tr>
92
+ <tr>
93
+ <th data-default="<?php form_option( 'bookly_l10n_label_employee' ) ?>" class="ab-employee-list"><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_employee' ) ?></th>
94
+ <td>Wayne Turner</td>
95
+ </tr>
96
+ <tr>
97
+ <th><?php _e( 'Price', 'bookly' ) ?></th>
98
+ <td><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 400 ) ?></td>
99
+ </tr>
100
+ <tr>
101
+ <th></th>
102
+ <td>
103
+ <button class="bookly-round" title="<?php esc_attr_e( 'Edit', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-edit"></i></button>
104
+ <button class="bookly-round" title="<?php esc_attr_e( 'Remove', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-drop"></i></button>
105
+ </td>
106
+ </tr>
107
+ </tbody>
108
+ <tfoot class="ab-desktop-version">
109
+ <tr>
110
+ <td colspan="4"><strong><?php _e( 'Total', 'bookly' ) ?>:</strong></td>
111
+ <td><strong class="bookly-js-total-price"><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 750 ) ?></strong></td>
112
+ <td></td>
113
+ </tr>
114
+ </tfoot>
115
+ <tfoot class="ab-mobile-version">
116
+ <tr>
117
+ <th><strong><?php _e( 'Total', 'bookly' ) ?>:</strong></th>
118
+ <td><strong class="bookly-js-total-price"><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 750 ) ?></strong></td>
119
+ </tr>
120
+ </tfoot>
121
+ </table>
122
+ </div>
123
+ </div>
124
+
125
+ <?php do_action( 'bookly_recurring_appointments_render_editable_message_info' ) ?>
126
+
127
+ <div class="bookly-box bookly-nav-steps">
128
+ <div class="bookly-back-step ab-btn">
129
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_button_back' ), 'bookly-js-text-back' ) ?>
130
+ </div>
131
+ <div class="bookly-next-step ab-btn">
132
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_button_next' ), 'bookly-js-text-next' ) ?>
133
+ </div>
134
+ </div>
135
+ </div>
backend/modules/appearance/templates/_5_details.php DELETED
@@ -1,63 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="bookly-form">
3
- <?php include '_progress_tracker.php' ?>
4
-
5
- <div class="ab-row">
6
- <span data-inputclass="input-xxlarge" data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 5, 'login' => false ), false ) ) ?>" data-placement="bottom" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_details_step' ) ) ?>" class="ab_editable" id="ab-text-info-details" data-type="textarea"><?php echo esc_html( get_option( 'ab_appearance_text_info_details_step' ) ) ?></span>
7
- </div>
8
- <div class="ab-row">
9
- <span data-inputclass="input-xxlarge" data-title="<?php _e( 'Visible to non-logged in customers only', 'bookly' ) ?>" data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 5, 'login' => true ), false ) ) ?>" data-placement="bottom" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_details_step_guest' ) ) ?>" class="ab_editable" id="ab-text-info-details-guest" data-type="textarea"><?php echo esc_html( get_option( 'ab_appearance_text_info_details_step_guest' ) ) ?></span>
10
- </div>
11
- <div class="ab-details-step">
12
- <div class="ab-row bookly-table">
13
- <div class="ab-formGroup">
14
- <label>
15
- <span
16
- data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_name' ) ) ?>"
17
- data-default-error="<?php echo esc_attr( get_option( 'ab_appearance_text_required_name' ) ) ?>"
18
- id="ab-text-label-name"
19
- data-type="multiple"
20
- data-option-id="ab_appearance_text_required_name"><?php echo esc_html( get_option( 'ab_appearance_text_label_name' ) ) ?></span>
21
- </label>
22
-
23
- <div>
24
- <input type="text" value="" maxlength="60" />
25
- </div>
26
- </div>
27
- <div class="ab-formGroup">
28
- <label>
29
- <span
30
- data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_phone' ) ) ?>"
31
- data-default-error="<?php echo esc_attr( get_option( 'ab_appearance_text_required_phone' ) ) ?>"
32
- id="ab-text-label-phone"
33
- data-type="multiple"
34
- data-option-id="ab_appearance_text_required_phone"><?php echo esc_html( get_option( 'ab_appearance_text_label_phone' ) ) ?></span>
35
- </label>
36
- <div>
37
- <input type="text" class="<?php if ( get_option( 'ab_settings_phone_default_country' ) != 'disabled' ) : ?>ab-user-phone<?php endif ?>" value="" />
38
- </div>
39
- </div>
40
- <div class="ab-formGroup">
41
- <label>
42
- <span
43
- data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_email' ) ) ?>"
44
- data-default-error="<?php echo esc_attr( get_option( 'ab_appearance_text_required_email' ) ) ?>"
45
- id="ab-text-label-email"
46
- data-type="multiple"
47
- data-option-id="ab_appearance_text_required_email"><?php echo esc_html( get_option( 'ab_appearance_text_label_email' ) ) ?></span>
48
- </label>
49
- <div>
50
- <input maxlength="40" type="text" value="" />
51
- </div>
52
- </div>
53
- </div>
54
- </div>
55
- <div class="ab-row ab-nav-steps">
56
- <div class="ab-left ab-back-step ab-btn">
57
- <span class="text_back ab_editable" id="ab-text-button-back" data-mirror="text_back" data-type="text" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_button_back' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_button_back' ) ) ?></span>
58
- </div>
59
- <div class="ab-right ab-next-step ab-btn">
60
- <span class="text_next ab_editable" id="ab-text-button-next" data-mirror="text_next" data-type="text" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_button_next' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_button_next' ) ) ?></span>
61
- </div>
62
- </div>
63
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appearance/templates/_6_details.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div class="bookly-form">
3
+ <?php include '_progress_tracker.php' ?>
4
+
5
+ <div class="bookly-box">
6
+ <span data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 6, 'login' => false ), false ) ) ?>" data-placement="bottom" data-option-default="<?php form_option( 'bookly_l10n_info_details_step' ) ?>" class="bookly-editable" id="bookly_l10n_info_details_step" data-type="textarea"><?php echo esc_html( get_option( 'bookly_l10n_info_details_step' ) ) ?></span>
7
+ </div>
8
+ <div class="bookly-box">
9
+ <span data-title="<?php _e( 'Visible to non-logged in customers only', 'bookly' ) ?>" data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 6, 'login' => true ), false ) ) ?>" data-placement="bottom" data-option-default="<?php form_option( 'bookly_l10n_info_details_step_guest' ) ?>" class="bookly-editable" id="bookly_l10n_info_details_step_guest" data-type="textarea"><?php echo esc_html( get_option( 'bookly_l10n_info_details_step_guest' ) ) ?></span>
10
+ </div>
11
+ <div class="ab-details-step">
12
+ <div class="bookly-box bookly-table">
13
+ <div class="ab-formGroup">
14
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderLabel( array( 'bookly_l10n_label_name', 'bookly_l10n_required_name', ) ) ?>
15
+ <div>
16
+ <input type="text" value="" maxlength="60" />
17
+ </div>
18
+ </div>
19
+ <div class="ab-formGroup">
20
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderLabel( array( 'bookly_l10n_label_phone', 'bookly_l10n_required_phone', ) ) ?>
21
+ <div>
22
+ <input type="text" class="<?php if ( get_option( 'bookly_cst_phone_default_country' ) != 'disabled' ) : ?>ab-user-phone<?php endif ?>" value="" />
23
+ </div>
24
+ </div>
25
+ <div class="ab-formGroup">
26
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderLabel( array( 'bookly_l10n_label_email', 'bookly_l10n_required_email', ) ) ?>
27
+ <div>
28
+ <input maxlength="40" type="text" value="" />
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
33
+
34
+ <?php do_action( 'bookly_recurring_appointments_render_editable_message_info' ) ?>
35
+
36
+ <div class="bookly-box bookly-nav-steps">
37
+ <div class="bookly-back-step ab-btn">
38
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_button_back' ), 'bookly-js-text-back' ) ?>
39
+ </div>
40
+ <div class="bookly-next-step ab-btn">
41
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_button_next' ), 'bookly-js-text-next' ) ?>
42
+ </div>
43
+ </div>
44
+ </div>
backend/modules/appearance/templates/_6_payment.php DELETED
@@ -1,67 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="bookly-form">
3
- <?php include '_progress_tracker.php' ?>
4
- <!-- Coupons -->
5
- <div class="ab-row">
6
- <span data-inputclass="input-xxlarge" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_coupon' ) ) ?>" data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 6 ), false ) ) ?>" class="ab_editable" id="ab-text-info-coupon" data-type="textarea"><?php echo esc_html( get_option( 'ab_appearance_text_info_coupon' ) ) ?></span>
7
- </div>
8
-
9
- <div class="ab-row ab-list">
10
- <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_coupon' ) ) ?>" class="ab_editable editable editable-click inline-block" id="ab-text-label-coupon" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_label_coupon' ) ) ?></span>
11
- <div class="ab-inline-block">
12
- <input class="ab-user-coupon" type="text" />
13
- <div class="ab-btn btn-apply-coupon">
14
- <span class="ab_editable" id="ab-text-button-apply" data-type="text" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_button_apply' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_button_apply' ) ) ?></span>
15
- </div>
16
- </div>
17
- </div>
18
- <div class="ab-payment-nav">
19
- <div class="ab-row">
20
- <span data-inputclass="input-xxlarge" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_payment_step' ) ) ?>" data-notes="<?php echo esc_attr( $this->render( '_codes', compact( 'step' ), false ) ) ?>" class="ab_editable" id="ab-text-info-payment" data-type="textarea"><?php echo esc_html( get_option( 'ab_appearance_text_info_payment_step' ) ) ?></span>
21
- </div>
22
-
23
- <div class="ab-row ab-list">
24
- <label>
25
- <input type="radio" name="payment" checked="checked" />
26
- <span id="ab-text-label-pay-locally" class="ab_editable" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_pay_locally' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_label_pay_locally' ) ) ?></span>
27
- </label>
28
- </div>
29
-
30
- <div class="ab-row ab-list">
31
- <label>
32
- <input type="radio" name="payment" />
33
- <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_pay_paypal' ) ) ?>" class="ab_editable editable editable-click inline-block" id="ab-text-label-pay-paypal" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_label_pay_paypal' ) ) ?></span>
34
- <img src="<?php echo plugins_url( 'frontend/resources/images/paypal.png', \BooklyLite\Lib\Plugin::getMainFile() ) ?>" alt="paypal" />
35
- </label>
36
- </div>
37
-
38
- <div class="ab-row ab-list">
39
- <label>
40
- <input type="radio" name="payment" class="ab-card-payment" />
41
- <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_pay_ccard' ) ) ?>" class="ab_editable editable editable-click inline-block" id="ab-text-label-pay-ccard" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_label_pay_ccard' ) ) ?></span>
42
- <img src="<?php echo plugins_url( 'frontend/resources/images/cards.png', \BooklyLite\Lib\Plugin::getMainFile() ) ?>" alt="cards" />
43
- </label>
44
- <form class="ab-card-form ab-clearBottom" style="margin-top:15px;display: none;">
45
- <?php include '_card_payment.php' ?>
46
- </form>
47
- </div>
48
-
49
- <div class="ab-row ab-list">
50
- <label>
51
- <input type="radio" name="payment" />
52
- <span id="ab-text-label-pay-mollie" class="ab_editable" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_pay_mollie' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_label_pay_mollie' ) ) ?></span>
53
- <img src="<?php echo plugins_url( 'frontend/resources/images/mollie.png', \BooklyLite\Lib\Plugin::getMainFile() ) ?>" alt="mollie" />
54
- </label>
55
- </div>
56
- </div>
57
-
58
- <!-- buttons -->
59
- <div class="ab-row ab-nav-steps">
60
- <div class="ab-left ab-back-step ab-btn">
61
- <span class="text_back ab_editable" id="ab-text-button-back" data-mirror="text_back" data-type="text" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_button_back' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_button_back' ) ) ?></span>
62
- </div>
63
- <div class="ab-right ab-next-step ab-btn">
64
- <span class="text_next ab_editable" id="ab-text-button-next" data-mirror="text_next" data-type="text" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_button_next' ) ) ?>"><?php echo esc_html( get_option( 'ab_appearance_text_button_next' ) ) ?></span>
65
- </div>
66
- </div>
67
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appearance/templates/_7_done.php DELETED
@@ -1,7 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="bookly-form">
3
- <?php include '_progress_tracker.php' ?>
4
- <div class="ab-row">
5
- <span data-inputclass="input-xxlarge" class="ab_editable" data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 7 ), false ) ) ?>" data-placement="bottom" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_complete_step' ) ) ?>" id="ab-text-info-complete" data-type="textarea"><?php echo nl2br( esc_html( get_option( 'ab_appearance_text_info_complete_step' ) ) ) ?></span>
6
- </div>
7
- </div>
 
 
 
 
 
 
 
backend/modules/appearance/templates/_7_payment.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div class="bookly-form">
3
+ <?php include '_progress_tracker.php' ?>
4
+ <div class="bookly-box">
5
+ <span data-option-default="<?php form_option( 'bookly_l10n_info_coupon' ) ?>" data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 7 ), false ) ) ?>" class="bookly-editable" id="bookly_l10n_info_coupon" data-type="textarea"><?php echo esc_html( get_option( 'bookly_l10n_info_coupon' ) ) ?></span>
6
+ </div>
7
+
8
+ <div class="bookly-box ab-list">
9
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_label_coupon', ) ) ?>
10
+ <div class="ab-inline-block">
11
+ <input class="ab-user-coupon" type="text" />
12
+ <div class="ab-btn btn-apply-coupon">
13
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_button_apply', ) ) ?>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ <div class="ab-payment-nav">
18
+ <div class="bookly-box">
19
+ <span data-option-default="<?php form_option( 'bookly_l10n_info_payment_step' ) ?>" data-notes="<?php echo esc_attr( $this->render( '_codes', compact( 'step' ), false ) ) ?>" class="bookly-editable" id="bookly_l10n_info_payment_step" data-type="textarea"><?php echo esc_html( get_option( 'bookly_l10n_info_payment_step' ) ) ?></span>
20
+ </div>
21
+
22
+ <div class="bookly-box ab-list">
23
+ <label>
24
+ <input type="radio" name="payment" checked="checked" />
25
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_label_pay_locally', ) ) ?>
26
+ </label>
27
+ </div>
28
+
29
+ <div class="bookly-box ab-list">
30
+ <label>
31
+ <input type="radio" name="payment" />
32
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_label_pay_paypal', ) ) ?>
33
+ <img src="<?php echo plugins_url( 'frontend/resources/images/paypal.png', \BooklyLite\Lib\Plugin::getMainFile() ) ?>" alt="paypal" />
34
+ </label>
35
+ </div>
36
+
37
+ <div class="bookly-box ab-list">
38
+ <label>
39
+ <input type="radio" name="payment" class="ab-card-payment" />
40
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_label_pay_ccard', ) ) ?>
41
+ <img src="<?php echo plugins_url( 'frontend/resources/images/cards.png', \BooklyLite\Lib\Plugin::getMainFile() ) ?>" alt="cards" />
42
+ </label>
43
+ <form class="ab-card-form ab-clearBottom" style="margin-top:15px;display: none;">
44
+ <?php include '_card_payment.php' ?>
45
+ </form>
46
+ </div>
47
+
48
+ <div class="bookly-box ab-list">
49
+ <label>
50
+ <input type="radio" name="payment" />
51
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_label_pay_mollie', ) ) ?>
52
+ <img src="<?php echo plugins_url( 'frontend/resources/images/mollie.png', \BooklyLite\Lib\Plugin::getMainFile() ) ?>" alt="mollie" />
53
+ </label>
54
+ </div>
55
+
56
+ <?php do_action( 'bookly_render_appearance_payment_gateway_selector' ) ?>
57
+ </div>
58
+
59
+ <?php do_action( 'bookly_recurring_appointments_render_editable_message_info' ) ?>
60
+
61
+ <div class="bookly-box bookly-nav-steps">
62
+ <div class="bookly-back-step ab-btn">
63
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_button_back' ), 'bookly-js-text-back' ) ?>
64
+ </div>
65
+ <div class="bookly-next-step ab-btn">
66
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_button_next' ), 'bookly-js-text-next' ) ?>
67
+ </div>
68
+ </div>
69
+ </div>
backend/modules/appearance/templates/_8_complete.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div class="bookly-form">
3
+ <?php include '_progress_tracker.php' ?>
4
+ <div class="bookly-box">
5
+ <span class="bookly-editable" data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 8 ), false ) ) ?>" data-placement="bottom" data-option-default="<?php form_option( 'bookly_l10n_info_complete_step' ) ?>" id="bookly_l10n_info_complete_step" data-type="textarea"><?php echo nl2br( esc_html( get_option( 'bookly_l10n_info_complete_step' ) ) ) ?></span>
6
+ </div>
7
+ </div>
backend/modules/appearance/templates/_card_payment.php CHANGED
@@ -1,38 +1,38 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="ab-row bookly-table">
3
- <div class="ab-formGroup" style="width:200px!important">
4
- <label>
5
- <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_ccard_number' ) ) ?>" class="ab_editable editable editable-click inline-block" id="ab-text-label-ccard-number" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_label_ccard_number' ) ) ?></span>
6
- </label>
7
- <div>
8
- <input type="text" />
9
- </div>
10
- </div>
11
- <div class="ab-formGroup">
12
- <label>
13
- <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_ccard_expire' ) ) ?>" class="ab_editable editable editable-click inline-block" id="ab-text-label-ccard-expire" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_label_ccard_expire' ) ) ?></span>
14
- </label>
15
- <div>
16
- <select class="ab-card-exp">
17
- <?php for ( $i = 1; $i <= 12; ++ $i ) : ?>
18
- <option value="<?php echo $i ?>"><?php printf( '%02d', $i ) ?></option>
19
- <?php endfor ?>
20
- </select>
21
- <select class="ab-card-exp">
22
- <?php for ( $i = date( 'Y' ); $i <= date( 'Y' ) + 10; ++ $i ) : ?>
23
- <option value="<?php echo $i ?>"><?php echo $i ?></option>
24
- <?php endfor ?>
25
- </select>
26
- </div>
27
- </div>
28
- </div>
29
- <div class="ab-row ab-clearBottom">
30
- <div class="ab-formGroup">
31
- <label>
32
- <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_ccard_code' ) ) ?>" class="ab_editable editable editable-click inline-block" id="ab-text-label-ccard-code" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_label_ccard_code' ) ) ?></span>
33
- </label>
34
- <div>
35
- <input class="ab-card-cvc" type="text" />
36
- </div>
37
- </div>
38
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div class="bookly-box bookly-table">
3
+ <div class="ab-formGroup" style="width:200px!important">
4
+ <label>
5
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_label_ccard_number', ) ) ?>
6
+ </label>
7
+ <div>
8
+ <input type="text" />
9
+ </div>
10
+ </div>
11
+ <div class="ab-formGroup">
12
+ <label>
13
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_label_ccard_expire', ) ) ?>
14
+ </label>
15
+ <div>
16
+ <select class="ab-card-exp">
17
+ <?php for ( $i = 1; $i <= 12; ++ $i ) : ?>
18
+ <option value="<?php echo $i ?>"><?php printf( '%02d', $i ) ?></option>
19
+ <?php endfor ?>
20
+ </select>
21
+ <select class="ab-card-exp">
22
+ <?php for ( $i = date( 'Y' ); $i <= date( 'Y' ) + 10; ++ $i ) : ?>
23
+ <option value="<?php echo $i ?>"><?php echo $i ?></option>
24
+ <?php endfor ?>
25
+ </select>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ <div class="bookly-box ab-clearBottom">
30
+ <div class="ab-formGroup">
31
+ <label>
32
+ <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_label_ccard_code', ) ) ?>
33
+ </label>
34
+ <div>
35
+ <input class="ab-card-cvc" type="text" />
36
+ </div>
37
+ </div>
38
  </div>
backend/modules/appearance/templates/_codes.php CHANGED
@@ -1,25 +1,25 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- $codes = array(
3
- array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ), ),
4
- array( 'code' => 'login_form', 'description' => __( 'login form', 'bookly' ),
5
- 'step' => 5,
6
- 'login' => true,
7
- ),
8
- array( 'code' => 'number_of_persons', 'description' => __( 'number of persons', 'bookly' ), ),
9
- array( 'code' => 'booking_number', 'description' => __( 'booking number', 'bookly' ),
10
- 'step' => 7,
11
- ),
12
- array( 'code' => 'service_date', 'description' => __( 'date of service', 'bookly' ),
13
- 'min_step' => 3,
14
- ),
15
- array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ), ),
16
- array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ), ),
17
- array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ), ),
18
- array( 'code' => 'service_time', 'description' => __( 'time of service', 'bookly' ),
19
- 'min_step' => 3,
20
- ),
21
- array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ), ),
22
- array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ), ),
23
- array( 'code' => 'total_price', 'description' => __( 'total price of booking', 'bookly' ), ),
24
- );
25
- \BooklyLite\Lib\Utils\Common::Codes( apply_filters( 'bookly_appearance_short_codes', $codes ), $step, isset( $login ) ? $login : false );
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ $codes = array(
3
+ array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ), ),
4
+ array( 'code' => 'login_form', 'description' => __( 'login form', 'bookly' ),
5
+ 'step' => 6,
6
+ 'login' => true,
7
+ ),
8
+ array( 'code' => 'number_of_persons', 'description' => __( 'number of persons', 'bookly' ), ),
9
+ array( 'code' => 'booking_number', 'description' => __( 'booking number', 'bookly' ),
10
+ 'step' => 8,
11
+ ),
12
+ array( 'code' => 'service_date', 'description' => __( 'date of service', 'bookly' ),
13
+ 'min_step' => 3,
14
+ ),
15
+ array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ), ),
16
+ array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ), ),
17
+ array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ), ),
18
+ array( 'code' => 'service_time', 'description' => __( 'time of service', 'bookly' ),
19
+ 'min_step' => 3,
20
+ ),
21
+ array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ), ),
22
+ array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ), ),
23
+ array( 'code' => 'total_price', 'description' => __( 'total price of booking', 'bookly' ), ),
24
+ );
25
+ \BooklyLite\Lib\Utils\Common::Codes( apply_filters( 'bookly_prepare_appearance_short_codes', $codes ), $step, isset( $login ) ? $login : false );
backend/modules/appearance/templates/_progress_tracker.php CHANGED
@@ -3,33 +3,39 @@
3
  ?>
4
  <div class="ab-progress-tracker bookly-table">
5
  <div class="active">
6
- <?php echo $i ++ ?>. <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_step_service' ) ) ?>" data-mirror="text_service" class="text_service ab_editable" id="ab-text-step-service" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_step_service' ) ) ?></span>
7
  <div class="step"></div>
8
  </div>
9
- <?php if ( \BooklyLite\Lib\Config::extrasEnabled() ) : ?>
10
- <div <?php if ( ( $step >= 2 ) ) : ?>class="active"<?php endif ?>>
11
- <?php echo $i ++ ?>. <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_step_extras' ) ) ?>" data-mirror="text_extras" class="text_extras ab_editable" id="ab-text-step-extras" data-type="text"><?php echo esc_html(get_option( 'ab_appearance_text_step_extras' ) ) ?></span>
12
  <div class="step"></div>
13
  </div>
14
  <?php endif ?>
15
  <div <?php if ( $step >= 3 ) : ?>class="active"<?php endif ?>>
16
- <?php echo $i ++ ?>. <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_step_time' ) ) ?>" data-mirror="text_time" class="text_time ab_editable" id="ab-text-step-time" data-type="text"><?php echo esc_html(get_option( 'ab_appearance_text_step_time' ) ) ?></span>
17
- <div class="step"></div>
18
- </div>
19
- <div <?php if ( $step >= 4 ) : ?>class="active"<?php endif ?>>
20
- <?php echo $i ++ ?>. <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_step_cart' ) ) ?>" data-mirror="text_cart" class="text_cart ab_editable" id="ab-text-step-cart" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_step_cart' ) ) ?></span>
21
  <div class="step"></div>
22
  </div>
 
 
 
 
 
 
23
  <div <?php if ( $step >= 5 ) : ?>class="active"<?php endif ?>>
24
- <?php echo $i ++ ?>. <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_step_details' ) ) ?>" data-mirror="text_details" class="text_details ab_editable" id="ab-text-step-details" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_step_details' ) ) ?></span>
25
  <div class="step"></div>
26
  </div>
27
  <div <?php if ( $step >= 6 ) : ?>class="active"<?php endif ?>>
28
- <?php echo $i ++ ?>. <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_step_payment' ) ) ?>" data-mirror="text_payment" class="text_payment ab_editable" id="ab-text-step-payment" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_step_payment' ) ) ?></span>
29
  <div class="step"></div>
30
  </div>
31
  <div <?php if ( $step >= 7 ) : ?>class="active"<?php endif ?>>
32
- <?php echo $i ++ ?>. <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_step_done' ) ) ?>" data-mirror="text_done" class="text_done ab_editable" id="ab-text-step-done" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_step_done' ) ) ?></span>
 
 
 
 
33
  <div class="step"></div>
34
  </div>
35
  </div>
3
  ?>
4
  <div class="ab-progress-tracker bookly-table">
5
  <div class="active">
6
+ <?php echo $i ++ ?>. <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_step_service' ), 'bookly-js-text-service' ) ?>
7
  <div class="step"></div>
8
  </div>
9
+ <?php if ( \BooklyLite\Lib\Config::isServiceExtrasEnabled() ) : ?>
10
+ <div <?php if ( $step >= 2 ) : ?>class="active"<?php endif ?>>
11
+ <?php echo $i ++ ?>. <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_step_extras' ), 'bookly-js-text-extras' ) ?>
12
  <div class="step"></div>
13
  </div>
14
  <?php endif ?>
15
  <div <?php if ( $step >= 3 ) : ?>class="active"<?php endif ?>>
16
+ <?php echo $i ++ ?>. <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_step_time' ), 'bookly-js-text-time' ) ?>
 
 
 
 
17
  <div class="step"></div>
18
  </div>
19
+ <?php if ( \BooklyLite\Lib\Config::isRecurringAppointmentsEnabled() ) : ?>
20
+ <div <?php if ( $step >= 4 ) : ?>class="active"<?php endif ?>>
21
+ <?php echo $i ++ ?>. <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_step_repeat' ), 'bookly-js-text-repeat' ) ?>
22
+ <div class=step></div>
23
+ </div>
24
+ <?php endif ?>
25
  <div <?php if ( $step >= 5 ) : ?>class="active"<?php endif ?>>
26
+ <?php echo $i ++ ?>. <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_step_cart' ), 'bookly-js-text-cart' ) ?>
27
  <div class="step"></div>
28
  </div>
29
  <div <?php if ( $step >= 6 ) : ?>class="active"<?php endif ?>>
30
+ <?php echo $i ++ ?>. <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_step_details' ), 'bookly-js-text-details' ) ?>
31
  <div class="step"></div>
32
  </div>
33
  <div <?php if ( $step >= 7 ) : ?>class="active"<?php endif ?>>
34
+ <?php echo $i ++ ?>. <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_step_payment' ), 'bookly-js-text-payment' ) ?>
35
+ <div class="step"></div>
36
+ </div>
37
+ <div <?php if ( $step >= 8 ) : ?>class="active"<?php endif ?>>
38
+ <?php echo $i ++ ?>. <?php \BooklyLite\Backend\Modules\Appearance\Lib\Helper::renderSpan( array( 'bookly_l10n_step_done' ), 'bookly-js-text-done' ) ?>
39
  <div class="step"></div>
40
  </div>
41
  </div>
backend/modules/appearance/templates/index.php CHANGED
@@ -1,126 +1,128 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div id="bookly-tbs" class="wrap">
3
- <div class="bookly-tbs-body">
4
- <div class="page-header text-right clearfix">
5
- <div class="bookly-page-title">
6
- <?php _e( 'Appearance', 'bookly' ) ?>
7
- </div>
8
- </div>
9
- <div class="panel panel-default bookly-main">
10
- <div class="panel-body">
11
- <div id="ab-appearance">
12
- <div class="row">
13
- <div class="col-sm-3 col-lg-2 bookly-color-picker-wrapper">
14
- <input type="text" name="color" class="bookly-js-color-picker"
15
- value="<?php echo esc_attr( get_option( 'ab_appearance_color' ) ) ?>"
16
- data-selected="<?php echo esc_attr( get_option( 'ab_appearance_color' ) ) ?>" />
17
- </div>
18
- <div class="col-sm-9 col-lg-10">
19
- <div class="checkbox">
20
- <label>
21
- <input type="checkbox" id=ab-progress-tracker-checkbox <?php checked( get_option( 'ab_appearance_show_progress_tracker' ) ) ?>>
22
- <?php _e( 'Show form progress tracker', 'bookly' ) ?>
23
- </label>
24
- </div>
25
- </div>
26
- </div>
27
-
28
- <ul class="bookly-nav bookly-nav-tabs bookly-margin-top-lg" role="tablist">
29
- <?php $i = 1 ?>
30
- <?php foreach ( $steps as $step => $step_name ) : ?>
31
- <?php if ( $step != 2 || \BooklyLite\Lib\Config::extrasEnabled() ) : ?>
32
- <li class="bookly-nav-item <?php if ( $step == 1 ) : ?>active<?php endif ?>" data-target="#ab-step-<?php echo $step ?>" data-toggle="tab">
33
- <?php echo $i++ ?>. <?php echo esc_html( $step_name ) ?>
34
- </li>
35
- <?php endif ?>
36
- <?php endforeach ?>
37
- </ul>
38
-
39
- <?php if ( ! get_user_meta( get_current_user_id(), \BooklyLite\Lib\Plugin::getPrefix() . 'dismiss_appearance_notice', true ) ): ?>
40
- <div class="alert alert-info alert-dismissible fade in bookly-margin-top-lg bookly-margin-bottom-remove" id="bookly-js-hint-alert" role="alert">
41
- <button type="button" class="close" data-dismiss="alert">&times;</button>
42
- <?php _e( 'Click on the underlined text to edit.', 'bookly' ) ?>
43
- </div>
44
- <?php endif ?>
45
-
46
- <div class="row" id="bookly-js-step-settings">
47
- <div id="bookly-js-step-service" class="bookly-margin-top-lg">
48
- <div class="col-md-12">
49
- <?php do_action( 'bookly_render_appearance_step_service_settings' ) ?>
50
- <div class="col-md-4">
51
- <div class="checkbox">
52
- <label>
53
- <input type="checkbox" id=ab-required-employee-checkbox <?php checked( get_option( 'ab_appearance_required_employee' ) ) ?>>
54
- <?php _e( 'Make selecting employee required', 'bookly' ) ?>
55
- </label>
56
- </div>
57
- </div>
58
- <div class="col-md-4">
59
- <div class="checkbox">
60
- <label>
61
- <input type="checkbox" id=ab-staff-name-with-price-checkbox <?php checked( get_option( 'ab_appearance_staff_name_with_price' ) ) ?>>
62
- <?php _e( 'Show service price next to employee name', 'bookly' ) ?>
63
- </label>
64
- </div>
65
- </div>
66
- </div>
67
- </div>
68
- <div id="bookly-js-step-time" class="bookly-margin-top-lg" style="display:none">
69
- <div class="col-md-4">
70
- <div class="checkbox">
71
- <label>
72
- <input type="checkbox" id="ab-show-calendar-checkbox" <?php checked( get_option( 'ab_appearance_show_calendar' ) ) ?>>
73
- <?php _e( 'Show calendar', 'bookly' ) ?>
74
- </label>
75
- </div>
76
- </div>
77
- <div class="col-md-4">
78
- <div class="checkbox">
79
- <label>
80
- <input type="checkbox" id="ab-blocked-timeslots-checkbox" <?php checked( get_option( 'ab_appearance_show_blocked_timeslots' ) ) ?>>
81
- <?php _e( 'Show blocked timeslots', 'bookly' ) ?>
82
- </label>
83
- </div>
84
- </div>
85
- <div class="col-md-4">
86
- <div class="checkbox">
87
- <label>
88
- <input type="checkbox" id="ab-day-one-column-checkbox" <?php checked( get_option( 'ab_appearance_show_day_one_column' ) ) ?>>
89
- <?php _e( 'Show each day in one column', 'bookly' ) ?>
90
- </label>
91
- </div>
92
- </div>
93
- </div>
94
- </div>
95
-
96
- <div class="panel panel-default bookly-margin-top-lg">
97
- <div class="panel-body">
98
- <div class="tab-content">
99
- <?php foreach ( $steps as $step => $step_name ) : ?>
100
- <div id="ab-step-<?php echo $step ?>" class="tab-pane <?php if ( $step == 1 ) : ?>active<?php endif ?>" data-target="<?php echo $step ?>">
101
- <?php // Render unique data per step
102
- switch ( $step ) :
103
- case 1: include '_1_service.php'; break;
104
- case 2: do_action( 'bookly_extras_render_appearance_tab', $this->render( '_progress_tracker', array( 'step' => $step ), false ) );
105
- break;
106
- case 3: include '_3_time.php'; break;
107
- case 4: include '_4_cart.php'; break;
108
- case 5: include '_5_details.php'; break;
109
- case 6: include '_6_payment.php'; break;
110
- case 7: include '_7_done.php'; break;
111
- endswitch ?>
112
- </div>
113
- <?php endforeach ?>
114
- </div>
115
- </div>
116
- </div>
117
-
118
- </div>
119
- </div>
120
- <div class="panel-footer">
121
- <?php \BooklyLite\Lib\Utils\Common::submitButton( 'ajax-send-appearance' ) ?>
122
- <?php \BooklyLite\Lib\Utils\Common::resetButton() ?>
123
- </div>
124
- </div>
125
- </div>
 
 
126
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div id="bookly-tbs" class="wrap">
3
+ <div class="bookly-tbs-body">
4
+ <div class="page-header text-right clearfix">
5
+ <div class="bookly-page-title">
6
+ <?php _e( 'Appearance', 'bookly' ) ?>
7
+ </div>
8
+ <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
9
+ </div>
10
+ <div class="panel panel-default bookly-main">
11
+ <div class="panel-body">
12
+ <div id="ab-appearance">
13
+ <div class="row">
14
+ <div class="col-sm-3 col-lg-2 bookly-color-picker-wrapper">
15
+ <input type="text" name="color" class="bookly-js-color-picker"
16
+ value="<?php form_option( 'bookly_app_color' ) ?>"
17
+ data-selected="<?php form_option( 'bookly_app_color' ) ?>" />
18
+ </div>
19
+ <div class="col-sm-9 col-lg-10">
20
+ <div class="checkbox">
21
+ <label>
22
+ <input type="checkbox" id=ab-progress-tracker-checkbox <?php checked( get_option( 'bookly_app_show_progress_tracker' ) ) ?>>
23
+ <?php _e( 'Show form progress tracker', 'bookly' ) ?>
24
+ </label>
25
+ </div>
26
+ </div>
27
+ </div>
28
+
29
+ <ul class="bookly-nav bookly-nav-tabs bookly-margin-top-lg" role="tablist">
30
+ <?php $i = 1 ?>
31
+ <?php foreach ( $steps as $step => $step_name ) : ?>
32
+ <?php if ( ( $step != 2 || \BooklyLite\Lib\Config::isServiceExtrasEnabled() )
33
+ && ( $step != 4 || \BooklyLite\Lib\Config::isRecurringAppointmentsEnabled() ) ) : ?>
34
+ <li class="bookly-nav-item <?php if ( $step == 1 ) : ?>active<?php endif ?>" data-target="#ab-step-<?php echo $step ?>" data-toggle="tab">
35
+ <?php echo $i++ ?>. <?php echo esc_html( $step_name ) ?>
36
+ </li>
37
+ <?php endif ?>
38
+ <?php endforeach ?>
39
+ </ul>
40
+
41
+ <?php if ( ! get_user_meta( get_current_user_id(), \BooklyLite\Lib\Plugin::getPrefix() . 'dismiss_appearance_notice', true ) ): ?>
42
+ <div class="alert alert-info alert-dismissible fade in bookly-margin-top-lg bookly-margin-bottom-remove" id="bookly-js-hint-alert" role="alert">
43
+ <button type="button" class="close" data-dismiss="alert"></button>
44
+ <?php _e( 'Click on the underlined text to edit.', 'bookly' ) ?>
45
+ </div>
46
+ <?php endif ?>
47
+
48
+ <div class="row" id="bookly-js-step-settings">
49
+ <div id="bookly-js-step-service" class="bookly-margin-top-lg">
50
+ <?php do_action( 'bookly_render_appearance_step_service_settings' ) ?>
51
+ <div class="col-md-4">
52
+ <div class="checkbox">
53
+ <label>
54
+ <input type="checkbox" id=ab-required-employee-checkbox <?php checked( get_option( 'bookly_app_required_employee' ) ) ?>>
55
+ <?php _e( 'Make selecting employee required', 'bookly' ) ?>
56
+ </label>
57
+ </div>
58
+ </div>
59
+ <div class="col-md-4">
60
+ <div class="checkbox">
61
+ <label>
62
+ <input type="checkbox" id=ab-staff-name-with-price-checkbox <?php checked( get_option( 'bookly_app_staff_name_with_price' ) ) ?>>
63
+ <?php _e( 'Show service price next to employee name', 'bookly' ) ?>
64
+ </label>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ <div id="bookly-js-step-time" class="bookly-margin-top-lg" style="display:none">
69
+ <div class="col-md-4">
70
+ <div class="checkbox">
71
+ <label>
72
+ <input type="checkbox" id="ab-show-calendar-checkbox" <?php checked( get_option( 'bookly_app_show_calendar' ) ) ?>>
73
+ <?php _e( 'Show calendar', 'bookly' ) ?>
74
+ </label>
75
+ </div>
76
+ </div>
77
+ <div class="col-md-4">
78
+ <div class="checkbox">
79
+ <label>
80
+ <input type="checkbox" id="ab-blocked-timeslots-checkbox" <?php checked( get_option( 'bookly_app_show_blocked_timeslots' ) ) ?>>
81
+ <?php _e( 'Show blocked timeslots', 'bookly' ) ?>
82
+ </label>
83
+ </div>
84
+ </div>
85
+ <div class="col-md-4">
86
+ <div class="checkbox">
87
+ <label>
88
+ <input type="checkbox" id="ab-day-one-column-checkbox" <?php checked( get_option( 'bookly_app_show_day_one_column' ) ) ?>>
89
+ <?php _e( 'Show each day in one column', 'bookly' ) ?>
90
+ </label>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+
96
+ <div class="panel panel-default bookly-margin-top-lg">
97
+ <div class="panel-body">
98
+ <div class="tab-content">
99
+ <?php foreach ( $steps as $step => $step_name ) : ?>
100
+ <div id="ab-step-<?php echo $step ?>" class="tab-pane <?php if ( $step == 1 ) : ?>active<?php endif ?>" data-target="<?php echo $step ?>">
101
+ <?php // Render unique data per step
102
+ switch ( $step ) :
103
+ case 1: include '_1_service.php'; break;
104
+ case 2: do_action( 'bookly_service_extras_render_appearance', $this->render( '_progress_tracker', array( 'step' => $step ), false ) );
105
+ break;
106
+ case 3: include '_3_time.php'; break;
107
+ case 4: do_action( 'bookly_recurring_appointments_render_appearance', $this->render( '_progress_tracker', array( 'step' => $step ), false ) );
108
+ break;
109
+ case 5: include '_5_cart.php'; break;
110
+ case 6: include '_6_details.php'; break;
111
+ case 7: include '_7_payment.php'; break;
112
+ case 8: include '_8_complete.php'; break;
113
+ endswitch ?>
114
+ </div>
115
+ <?php endforeach ?>
116
+ </div>
117
+ </div>
118
+ </div>
119
+
120
+ </div>
121
+ </div>
122
+ <div class="panel-footer">
123
+ <?php \BooklyLite\Lib\Utils\Common::submitButton( 'ajax-send-appearance' ) ?>
124
+ <?php \BooklyLite\Lib\Utils\Common::resetButton() ?>
125
+ </div>
126
+ </div>
127
+ </div>
128
  </div>
backend/modules/appointments/Controller.php CHANGED
@@ -1,246 +1,256 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Appointments;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Appointments
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- public function index()
13
- {
14
- /** @var \WP_Locale $wp_locale */
15
- global $wp_locale;
16
-
17
- $this->enqueueStyles( array(
18
- 'frontend' => array( 'css/ladda.min.css', ),
19
- 'backend' => array(
20
- 'bootstrap/css/bootstrap-theme.min.css',
21
- 'css/daterangepicker.css',
22
- ),
23
- ) );
24
-
25
- $this->enqueueScripts( array(
26
- 'backend' => array(
27
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
28
- 'js/datatables.min.js' => array( 'jquery' ),
29
- 'js/moment.min.js',
30
- 'js/daterangepicker.js' => array( 'jquery' ),
31
- 'js/alert.js' => array( 'jquery' ),
32
- ),
33
- 'frontend' => array(
34
- 'js/spin.min.js' => array( 'jquery' ),
35
- 'js/ladda.min.js' => array( 'jquery' ),
36
- ),
37
- 'module' => array( 'js/appointments.js' => array( 'ab-datatables.min.js' ), ),
38
- ) );
39
-
40
- // Custom fields without captcha field.
41
- $custom_fields = array_filter( json_decode( get_option( 'ab_custom_fields' ) ), function( $field ) {
42
- return ! in_array( $field->type, array( 'captcha', 'text-content' ) );
43
- } );
44
-
45
- wp_localize_script( 'ab-appointments.js', 'BooklyL10n', array(
46
- 'tomorrow' => __( 'Tomorrow', 'bookly' ),
47
- 'today' => __( 'Today', 'bookly' ),
48
- 'yesterday' => __( 'Yesterday', 'bookly' ),
49
- 'last_7' => __( 'Last 7 Days', 'bookly' ),
50
- 'last_30' => __( 'Last 30 Days', 'bookly' ),
51
- 'this_month' => __( 'This Month', 'bookly' ),
52
- 'next_month' => __( 'Next Month', 'bookly' ),
53
- 'custom_range' => __( 'Custom Range', 'bookly' ),
54
- 'apply' => __( 'Apply', 'bookly' ),
55
- 'cancel' => __( 'Cancel', 'bookly' ),
56
- 'to' => __( 'To', 'bookly' ),
57
- 'from' => __( 'From', 'bookly' ),
58
- 'calendar' => array(
59
- 'longMonths' => array_values( $wp_locale->month ),
60
- 'shortMonths' => array_values( $wp_locale->month_abbrev ),
61
- 'longDays' => array_values( $wp_locale->weekday ),
62
- 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
63
- ),
64
- 'mjsDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
65
- 'startOfWeek' => (int) get_option( 'start_of_week' ),
66
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
67
- 'zeroRecords' => __( 'No appointments for selected period.', 'bookly' ),
68
- 'processing' => __( 'Processing...', 'bookly' ),
69
- 'edit' => __( 'Edit', 'bookly' ),
70
- 'cf_columns' => array_map( function ( $custom_field ) { return $custom_field->id; }, $custom_fields ),
71
- 'limitations' => __( '<b class="h4">This function is disabled in the Lite version of Bookly.</b><br><br>If you find the plugin useful for your business please consider buying a licence for the full version.<br>It costs just $59 and for this money you will get many useful functions, lifetime free updates and excellent support!<br>More information can be found here', 'bookly' ) . ': <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
72
- ) );
73
-
74
- // Filters data
75
- $staff_members = Lib\Entities\Staff::query( 's' )->select( 's.id, s.full_name' )->fetchArray();
76
- $customers = Lib\Entities\Customer::query( 'c' )->select( 'c.id, c.name' )->fetchArray();
77
- $services = Lib\Entities\Service::query( 's' )->select( 's.id, s.title' )->where( 'type', Lib\Entities\Service::TYPE_SIMPLE )->fetchArray();
78
-
79
- $this->render( 'index', compact( 'custom_fields', 'staff_members', 'customers', 'services' ) );
80
- }
81
-
82
- /**
83
- * Get list of appointments.
84
- */
85
- public function executeGetAppointments()
86
- {
87
- $columns = $this->getParameter( 'columns' );
88
- $order = $this->getParameter( 'order' );
89
- $filter = $this->getParameter( 'filter' );
90
-
91
- $query = Lib\Entities\CustomerAppointment::query( 'ca' )
92
- ->select( 'a.id,
93
- ca.payment_id,
94
- ca.status,
95
- ca.id AS ca_id,
96
- ca.extras,
97
- a.start_date,
98
- a.extras_duration,
99
- c.name AS customer_name,
100
- c.phone AS customer_phone,
101
- c.email AS customer_email,
102
- s.title AS service_title,
103
- s.duration AS service_duration,
104
- st.full_name AS staff_name,
105
- p.paid AS payment,
106
- p.total AS payment_total,
107
- p.type AS payment_type,
108
- p.status AS payment_status' )
109
- ->leftJoin( 'Appointment', 'a', 'a.id = ca.appointment_id' )
110
- ->leftJoin( 'Service', 's', 's.id = a.service_id' )
111
- ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
112
- ->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
113
- ->leftJoin( 'Staff', 'st', 'st.id = a.staff_id' )
114
- ->leftJoin( 'StaffService', 'ss', 'ss.staff_id = st.id AND ss.service_id = s.id' );
115
-
116
- $total = $query->count();
117
-
118
- if ( $filter['id'] != '' ) {
119
- $query->where( 'a.id', $filter['id'] );
120
- }
121
-
122
- list ( $start, $end ) = explode( ' - ', $filter['date'], 2 );
123
- $end = date( 'Y-m-d', strtotime( '+1 day', strtotime( $end ) ) );
124
- $query->whereBetween( 'a.start_date', $start, $end );
125
-
126
- if ( $filter['staff'] != -1 ) {
127
- $query->where( 'a.staff_id', $filter['staff'] );
128
- }
129
-
130
- if ( $filter['customer'] != -1 ) {
131
- $query->where( 'ca.customer_id', $filter['customer'] );
132
- }
133
-
134
- if ( $filter['service'] != -1 ) {
135
- $query->where( 'a.service_id', $filter['service'] );
136
- }
137
-
138
- if ( $filter['status'] != -1 ) {
139
- $query->where( 'ca.status', $filter['status'] );
140
- }
141
-
142
- foreach ( $order as $sort_by ) {
143
- $query->sortBy( str_replace( '.', '_', $columns[ $sort_by['column'] ]['data'] ) )
144
- ->order( $sort_by['dir'] == 'desc' ? Lib\Query::ORDER_DESCENDING : Lib\Query::ORDER_ASCENDING );
145
- }
146
-
147
- $custom_fields = array();
148
- $fields_data = array_filter( json_decode( get_option( 'ab_custom_fields' ) ), function( $field ) {
149
- return ! in_array( $field->type, array( 'captcha', 'text-content' ) );
150
- } );
151
- foreach ( $fields_data as $field_data ) {
152
- $custom_fields[ $field_data->id ] = '';
153
- }
154
-
155
- $data = array();
156
- foreach ( $query->fetchArray() as $row ) {
157
- // Service duration.
158
- $service_duration = Lib\Utils\DateTime::secondsToInterval( $row['service_duration'] );
159
- if ( $row['extras_duration'] > 0 ) {
160
- $service_duration .= ' + ' . Lib\Utils\DateTime::secondsToInterval( $row['extras_duration'] );
161
- }
162
- // Appointment status.
163
- $row['status'] = Lib\Entities\CustomerAppointment::statusToString( $row['status'] );
164
-
165
- // Payment title.
166
- $payment_title = '';
167
- if ( $row['payment'] !== null ) {
168
- $payment_title = Lib\Utils\Common::formatPrice( $row['payment'] );
169
- if ( $row['payment'] != $row['payment_total'] ) {
170
- $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Common::formatPrice( $row['payment_total'] ) );
171
- }
172
- $payment_title .= sprintf(
173
- ' %s <span%s>%s</span>',
174
- Lib\Entities\Payment::typeToString( $row['payment_type'] ),
175
- $row['payment_status'] == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
176
- Lib\Entities\Payment::statusToString( $row['payment_status'] )
177
- );
178
- }
179
- // Custom fields
180
- $customer_appointment = new Lib\Entities\CustomerAppointment();
181
- $customer_appointment->load( $row['ca_id'] );
182
- foreach ( $customer_appointment->getCustomFields() as $custom_field ) {
183
- $custom_fields[ $custom_field['id'] ] = $custom_field['value'];
184
- }
185
-
186
- // Add extras
187
- $row = apply_filters( 'bookly_appointment_data', $row, false );
188
-
189
- $data[] = array(
190
- 'id' => $row['id'],
191
- 'start_date' => Lib\Utils\DateTime::formatDateTime( $row['start_date'] ),
192
- 'staff' => array(
193
- 'name' => $row['staff_name'],
194
- ),
195
- 'customer' => array(
196
- 'name' => $row['customer_name'],
197
- 'phone' => $row['customer_phone'],
198
- 'email' => $row['customer_email'],
199
- ),
200
- 'service' => array(
201
- 'title' => $row['service_title'],
202
- 'duration' => $service_duration,
203
- 'extras' => is_array( $row['extras'] ) ? $row['extras'] : array(),
204
- ),
205
- 'status' => $row['status'],
206
- 'payment' => $payment_title,
207
- 'custom_fields' => $custom_fields,
208
- 'ca_id' => $row['ca_id'],
209
- 'payment_id' => $row['payment_id'],
210
- );
211
-
212
- $custom_fields = array_map( function () { return ''; }, $custom_fields );
213
- }
214
-
215
- wp_send_json( array(
216
- 'draw' => ( int ) $this->getParameter( 'draw' ),
217
- 'recordsTotal' => $total,
218
- 'recordsFiltered' => count( $data ),
219
- 'data' => $data,
220
- ) );
221
- }
222
-
223
- /**
224
- * Delete customer appointments.
225
- */
226
- public function executeDeleteCustomerAppointments()
227
- {
228
- /** @var Lib\Entities\CustomerAppointment $ca */
229
- foreach ( Lib\Entities\CustomerAppointment::query()->whereIn( 'id', $this->getParameter( 'data', array() ) )->find() as $ca ) {
230
- $ca->deleteCascade();
231
- }
232
- wp_send_json_success();
233
- }
234
-
235
- /**
236
- * Override parent method to add 'wp_ajax_ab_' prefix
237
- * so current 'execute*' methods look nicer.
238
- *
239
- * @param string $prefix
240
- */
241
- protected function registerWpActions( $prefix = '' )
242
- {
243
- parent::registerWpActions( 'wp_ajax_ab_' );
244
- }
245
-
 
 
 
 
 
 
 
 
 
 
246
  }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Appointments;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Controller
8
+ * @package BooklyLite\Backend\Modules\Appointments
9
+ */
10
+ class Controller extends Lib\Base\Controller
11
+ {
12
+ const page_slug = 'bookly-appointments';
13
+
14
+ public function index()
15
+ {
16
+ /** @var \WP_Locale $wp_locale */
17
+ global $wp_locale;
18
+
19
+ $this->enqueueStyles( array(
20
+ 'frontend' => array( 'css/ladda.min.css', ),
21
+ 'backend' => array(
22
+ 'bootstrap/css/bootstrap-theme.min.css',
23
+ 'css/daterangepicker.css',
24
+ ),
25
+ ) );
26
+
27
+ $this->enqueueScripts( array(
28
+ 'backend' => array(
29
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
30
+ 'js/datatables.min.js' => array( 'jquery' ),
31
+ 'js/moment.min.js',
32
+ 'js/daterangepicker.js' => array( 'jquery' ),
33
+ ),
34
+ 'frontend' => array(
35
+ 'js/spin.min.js' => array( 'jquery' ),
36
+ 'js/ladda.min.js' => array( 'jquery' ),
37
+ ),
38
+ 'module' => array( 'js/appointments.js' => array( 'bookly-datatables.min.js' ), ),
39
+ ) );
40
+
41
+ // Custom fields without captcha field.
42
+ $custom_fields = array_filter( json_decode( get_option( 'bookly_custom_fields' ) ), function( $field ) {
43
+ return ! in_array( $field->type, array( 'captcha', 'text-content' ) );
44
+ } );
45
+
46
+ wp_localize_script( 'bookly-appointments.js', 'BooklyL10n', array(
47
+ 'tomorrow' => __( 'Tomorrow', 'bookly' ),
48
+ 'today' => __( 'Today', 'bookly' ),
49
+ 'yesterday' => __( 'Yesterday', 'bookly' ),
50
+ 'last_7' => __( 'Last 7 Days', 'bookly' ),
51
+ 'last_30' => __( 'Last 30 Days', 'bookly' ),
52
+ 'this_month' => __( 'This Month', 'bookly' ),
53
+ 'next_month' => __( 'Next Month', 'bookly' ),
54
+ 'custom_range' => __( 'Custom Range', 'bookly' ),
55
+ 'apply' => __( 'Apply', 'bookly' ),
56
+ 'cancel' => __( 'Cancel', 'bookly' ),
57
+ 'to' => __( 'To', 'bookly' ),
58
+ 'from' => __( 'From', 'bookly' ),
59
+ 'calendar' => array(
60
+ 'longMonths' => array_values( $wp_locale->month ),
61
+ 'shortMonths' => array_values( $wp_locale->month_abbrev ),
62
+ 'longDays' => array_values( $wp_locale->weekday ),
63
+ 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
64
+ ),
65
+ 'mjsDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
66
+ 'startOfWeek' => (int) get_option( 'start_of_week' ),
67
+ 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
68
+ 'zeroRecords' => __( 'No appointments for selected period.', 'bookly' ),
69
+ 'processing' => __( 'Processing...', 'bookly' ),
70
+ 'edit' => __( 'Edit', 'bookly' ),
71
+ 'cf_columns' => array_map( function ( $custom_field ) { return $custom_field->id; }, $custom_fields ),
72
+ 'filter' => (array) get_user_meta( get_current_user_id(), 'bookly_filter_appointments_list', true ),
73
+ 'limitations' => __( '<b class="h4">This function is disabled in the Lite version of Bookly.</b><br><br>If you find the plugin useful for your business please consider buying a licence for the full version.<br>It costs just $59 and for this money you will get many useful functions, lifetime free updates and excellent support!<br>More information can be found here', 'bookly' ) . ': <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
74
+ ) );
75
+
76
+ // Filters data
77
+ $staff_members = Lib\Entities\Staff::query( 's' )->select( 's.id, s.full_name' )->fetchArray();
78
+ $customers = Lib\Entities\Customer::query( 'c' )->select( 'c.id, c.name' )->fetchArray();
79
+ $services = Lib\Entities\Service::query( 's' )->select( 's.id, s.title' )->where( 'type', Lib\Entities\Service::TYPE_SIMPLE )->fetchArray();
80
+
81
+ $this->render( 'index', compact( 'custom_fields', 'staff_members', 'customers', 'services' ) );
82
+ }
83
+
84
+ /**
85
+ * Get list of appointments.
86
+ */
87
+ public function executeGetAppointments()
88
+ {
89
+ $columns = $this->getParameter( 'columns' );
90
+ $order = $this->getParameter( 'order' );
91
+ $filter = $this->getParameter( 'filter' );
92
+
93
+ $query = Lib\Entities\CustomerAppointment::query( 'ca' )
94
+ ->select( 'a.id,
95
+ ca.payment_id,
96
+ ca.status,
97
+ ca.id AS ca_id,
98
+ ca.extras,
99
+ a.start_date,
100
+ a.extras_duration,
101
+ c.name AS customer_name,
102
+ c.phone AS customer_phone,
103
+ c.email AS customer_email,
104
+ s.title AS service_title,
105
+ s.duration AS service_duration,
106
+ st.full_name AS staff_name,
107
+ p.paid AS payment,
108
+ p.total AS payment_total,
109
+ p.type AS payment_type,
110
+ p.status AS payment_status' )
111
+ ->leftJoin( 'Appointment', 'a', 'a.id = ca.appointment_id' )
112
+ ->leftJoin( 'Service', 's', 's.id = a.service_id' )
113
+ ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
114
+ ->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
115
+ ->leftJoin( 'Staff', 'st', 'st.id = a.staff_id' )
116
+ ->leftJoin( 'StaffService', 'ss', 'ss.staff_id = st.id AND ss.service_id = s.id' );
117
+
118
+ $total = $query->count();
119
+
120
+ if ( $filter['id'] != '' ) {
121
+ $query->where( 'a.id', $filter['id'] );
122
+ }
123
+
124
+ list ( $start, $end ) = explode( ' - ', $filter['date'], 2 );
125
+ $end = date( 'Y-m-d', strtotime( '+1 day', strtotime( $end ) ) );
126
+ $query->whereBetween( 'a.start_date', $start, $end );
127
+
128
+ if ( $filter['staff'] != -1 ) {
129
+ $query->where( 'a.staff_id', $filter['staff'] );
130
+ }
131
+
132
+ if ( $filter['customer'] != -1 ) {
133
+ $query->where( 'ca.customer_id', $filter['customer'] );
134
+ }
135
+
136
+ if ( $filter['service'] != -1 ) {
137
+ $query->where( 'a.service_id', $filter['service'] );
138
+ }
139
+
140
+ if ( $filter['status'] != -1 ) {
141
+ $query->where( 'ca.status', $filter['status'] );
142
+ }
143
+
144
+ foreach ( $order as $sort_by ) {
145
+ $query->sortBy( str_replace( '.', '_', $columns[ $sort_by['column'] ]['data'] ) )
146
+ ->order( $sort_by['dir'] == 'desc' ? Lib\Query::ORDER_DESCENDING : Lib\Query::ORDER_ASCENDING );
147
+ }
148
+
149
+ $custom_fields = array();
150
+ $fields_data = array_filter( json_decode( get_option( 'bookly_custom_fields' ) ), function( $field ) {
151
+ return ! in_array( $field->type, array( 'captcha', 'text-content' ) );
152
+ } );
153
+ foreach ( $fields_data as $field_data ) {
154
+ $custom_fields[ $field_data->id ] = '';
155
+ }
156
+
157
+ $data = array();
158
+ foreach ( $query->fetchArray() as $row ) {
159
+ // Service duration.
160
+ $service_duration = Lib\Utils\DateTime::secondsToInterval( $row['service_duration'] );
161
+ if ( $row['extras_duration'] > 0 ) {
162
+ $service_duration .= ' + ' . Lib\Utils\DateTime::secondsToInterval( $row['extras_duration'] );
163
+ }
164
+ // Appointment status.
165
+ $row['status'] = Lib\Entities\CustomerAppointment::statusToString( $row['status'] );
166
+
167
+ // Payment title.
168
+ $payment_title = '';
169
+ if ( $row['payment'] !== null ) {
170
+ $payment_title = Lib\Utils\Common::formatPrice( $row['payment'] );
171
+ if ( $row['payment'] != $row['payment_total'] ) {
172
+ $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Common::formatPrice( $row['payment_total'] ) );
173
+ }
174
+ $payment_title .= sprintf(
175
+ ' %s <span%s>%s</span>',
176
+ Lib\Entities\Payment::typeToString( $row['payment_type'] ),
177
+ $row['payment_status'] == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
178
+ Lib\Entities\Payment::statusToString( $row['payment_status'] )
179
+ );
180
+ }
181
+ // Custom fields
182
+ $customer_appointment = new Lib\Entities\CustomerAppointment();
183
+ $customer_appointment->load( $row['ca_id'] );
184
+ foreach ( $customer_appointment->getCustomFields() as $custom_field ) {
185
+ $custom_fields[ $custom_field['id'] ] = $custom_field['value'];
186
+ }
187
+
188
+ $data[] = array(
189
+ 'id' => $row['id'],
190
+ 'start_date' => Lib\Utils\DateTime::formatDateTime( $row['start_date'] ),
191
+ 'staff' => array(
192
+ 'name' => $row['staff_name'],
193
+ ),
194
+ 'customer' => array(
195
+ 'name' => $row['customer_name'],
196
+ 'phone' => $row['customer_phone'],
197
+ 'email' => $row['customer_email'],
198
+ ),
199
+ 'service' => array(
200
+ 'title' => $row['service_title'],
201
+ 'duration' => $service_duration,
202
+ 'extras' => apply_filters( 'bookly_service_extras_get_data_for_appointment', array(), $row['extras'], false ),
203
+ ),
204
+ 'status' => $row['status'],
205
+ 'payment' => $payment_title,
206
+ 'custom_fields' => $custom_fields,
207
+ 'ca_id' => $row['ca_id'],
208
+ 'payment_id' => $row['payment_id'],
209
+ );
210
+
211
+ $custom_fields = array_map( function () { return ''; }, $custom_fields );
212
+ }
213
+
214
+ unset( $filter['date'] );
215
+ update_user_meta( get_current_user_id(), 'bookly_filter_appointments_list', $filter );
216
+
217
+ wp_send_json( array(
218
+ 'draw' => ( int ) $this->getParameter( 'draw' ),
219
+ 'recordsTotal' => $total,
220
+ 'recordsFiltered' => count( $data ),
221
+ 'data' => $data,
222
+ ) );
223
+ }
224
+
225
+ /**
226
+ * Delete customer appointments.
227
+ */
228
+ public function executeDeleteCustomerAppointments()
229
+ {
230
+ /** @var Lib\Entities\CustomerAppointment $ca */
231
+ foreach ( Lib\Entities\CustomerAppointment::query()->whereIn( 'id', $this->getParameter( 'data', array() ) )->find() as $ca ) {
232
+ if ( $this->getParameter( 'notify' ) ) {
233
+ if ( $ca->get('status') === Lib\Entities\CustomerAppointment::STATUS_PENDING ) {
234
+ $ca->set( 'status', Lib\Entities\CustomerAppointment::STATUS_REJECTED );
235
+ } else { // STATUS_APPROVED
236
+ $ca->set( 'status', Lib\Entities\CustomerAppointment::STATUS_CANCELLED );
237
+ }
238
+ \BooklyLite\Lib\NotificationSender::send( $ca, array( 'cancellation_reason' => $this->getParameter( 'reason' ) ) );
239
+ }
240
+ $ca->deleteCascade();
241
+ }
242
+ wp_send_json_success();
243
+ }
244
+
245
+ /**
246
+ * Override parent method to add 'wp_ajax_bookly_' prefix
247
+ * so current 'execute*' methods look nicer.
248
+ *
249
+ * @param string $prefix
250
+ */
251
+ protected function registerWpActions( $prefix = '' )
252
+ {
253
+ parent::registerWpActions( 'wp_ajax_bookly_' );
254
+ }
255
+
256
  }
backend/modules/appointments/resources/js/appointments.js CHANGED
@@ -10,13 +10,25 @@ jQuery(function($) {
10
  $service_filter = $('#bookly-filter-service'),
11
  $status_filter = $('#bookly-filter-status'),
12
  $add_button = $('#bookly-add'),
13
- $print_dialog = $('#bookly-print-dialog'),
14
- $print_button = $('#bookly-print'),
15
  $export_dialog = $('#bookly-export-dialog'),
16
  $export_button = $('#bookly-export'),
17
- $delete_button = $('#bookly-delete')
 
18
  ;
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  /**
21
  * Init DataTables.
22
  */
@@ -24,14 +36,24 @@ jQuery(function($) {
24
  { data: 'id', responsivePriority: 2 },
25
  { data: 'start_date', responsivePriority: 2 },
26
  { data: 'staff.name', responsivePriority: 2 },
27
- { data: 'customer.name', responsivePriority: 2 },
28
- { data: 'customer.phone', responsivePriority: 3 },
29
- { data: 'customer.email', responsivePriority: 3 },
 
 
 
 
 
 
 
 
 
 
30
  {
31
  data: 'service.title',
32
  responsivePriority: 2,
33
  render: function ( data, type, row, meta ) {
34
- if(!$.isEmptyObject(row.service.extras)) {
35
  var extras = '<ul class="bookly-list list-dots">';
36
  $.each(row.service.extras, function (key, item) {
37
  extras += '<li><nobr>' + item.title + '</nobr></li>';
@@ -57,6 +79,7 @@ jQuery(function($) {
57
  $.each(BooklyL10n.cf_columns, function (i, cf_id) {
58
  columns.push({
59
  data: 'custom_fields.' + cf_id,
 
60
  responsivePriority: 4,
61
  orderable: false
62
  });
@@ -73,7 +96,7 @@ jQuery(function($) {
73
  url : ajaxurl,
74
  type: 'POST',
75
  data: function (d) {
76
- return $.extend({action: 'ab_get_appointments'}, {
77
  filter: {
78
  id : $id_filter.val(),
79
  date : $date_filter.data('date'),
@@ -178,34 +201,35 @@ jQuery(function($) {
178
  * Delete appointments.
179
  */
180
  $delete_button.on('click', function () {
181
- if (confirm(BooklyL10n.are_you_sure)) {
182
- var ladda = Ladda.create(this);
183
- ladda.start();
184
 
185
- var data = [];
186
- var $checkboxes = $appointments_list.find('tbody input:checked');
187
- $checkboxes.each(function () {
188
- data.push(this.value);
189
- });
190
 
191
- $.ajax({
192
- url : ajaxurl,
193
- type : 'POST',
194
- data : {
195
- action : 'ab_delete_customer_appointments',
196
- data : data
197
- },
198
- dataType : 'json',
199
- success : function(response) {
200
- ladda.stop();
201
- if (response.success) {
202
- dt.rows($checkboxes.closest('td')).remove().draw();
203
- } else {
204
- alert(response.data.message);
205
- }
 
 
206
  }
207
- });
208
- }
209
  });
210
 
211
  /**
10
  $service_filter = $('#bookly-filter-service'),
11
  $status_filter = $('#bookly-filter-status'),
12
  $add_button = $('#bookly-add'),
 
 
13
  $export_dialog = $('#bookly-export-dialog'),
14
  $export_button = $('#bookly-export'),
15
+ $delete_button = $('#bookly-delete'),
16
+ isMobile = false
17
  ;
18
 
19
+ try {
20
+ document.createEvent("TouchEvent");
21
+ isMobile = true;
22
+ } catch (e) {
23
+
24
+ }
25
+
26
+ $.each(BooklyL10n.filter, function (field, value) {
27
+ if (value != -1) {
28
+ $('#bookly-filter-' + field).val(value);
29
+ }
30
+ });
31
+
32
  /**
33
  * Init DataTables.
34
  */
36
  { data: 'id', responsivePriority: 2 },
37
  { data: 'start_date', responsivePriority: 2 },
38
  { data: 'staff.name', responsivePriority: 2 },
39
+ { data: 'customer.name', render: $.fn.dataTable.render.text(), responsivePriority: 2 },
40
+ {
41
+ data: 'customer.phone',
42
+ responsivePriority: 3,
43
+ render: function (data, type, row, meta) {
44
+ if (isMobile) {
45
+ return '<a href="tel:' + data + '">' + $.fn.dataTable.render.text().display(data) + '</a>';
46
+ } else {
47
+ return $.fn.dataTable.render.text().display(data);
48
+ }
49
+ }
50
+ },
51
+ { data: 'customer.email', render: $.fn.dataTable.render.text(), responsivePriority: 3 },
52
  {
53
  data: 'service.title',
54
  responsivePriority: 2,
55
  render: function ( data, type, row, meta ) {
56
+ if (row.service.extras.length) {
57
  var extras = '<ul class="bookly-list list-dots">';
58
  $.each(row.service.extras, function (key, item) {
59
  extras += '<li><nobr>' + item.title + '</nobr></li>';
79
  $.each(BooklyL10n.cf_columns, function (i, cf_id) {
80
  columns.push({
81
  data: 'custom_fields.' + cf_id,
82
+ render: $.fn.dataTable.render.text(),
83
  responsivePriority: 4,
84
  orderable: false
85
  });
96
  url : ajaxurl,
97
  type: 'POST',
98
  data: function (d) {
99
+ return $.extend({action: 'bookly_get_appointments'}, {
100
  filter: {
101
  id : $id_filter.val(),
102
  date : $date_filter.data('date'),
201
  * Delete appointments.
202
  */
203
  $delete_button.on('click', function () {
204
+ var ladda = Ladda.create(this);
205
+ ladda.start();
 
206
 
207
+ var data = [];
208
+ var $checkboxes = $appointments_list.find('tbody input:checked');
209
+ $checkboxes.each(function () {
210
+ data.push(this.value);
211
+ });
212
 
213
+ $.ajax({
214
+ url : ajaxurl,
215
+ type : 'POST',
216
+ data : {
217
+ action : 'bookly_delete_customer_appointments',
218
+ data : data,
219
+ notify : $('#bookly-delete-notify').prop('checked') ? 1 : 0,
220
+ reason : $('#bookly-delete-reason').val()
221
+ },
222
+ dataType : 'json',
223
+ success : function(response) {
224
+ ladda.stop();
225
+ $('#bookly-delete-dialog').modal('hide');
226
+ if (response.success) {
227
+ dt.draw(false);
228
+ } else {
229
+ alert(response.data.message);
230
  }
231
+ }
232
+ });
233
  });
234
 
235
  /**
backend/modules/appointments/templates/_export_dialog.php CHANGED
@@ -1,40 +1,40 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div id="bookly-export-dialog" class="modal fade" tabindex=-1 role="dialog">
3
- <div class="modal-dialog">
4
- <div class="modal-content">
5
- <div class="modal-header">
6
- <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
7
- <div class="modal-title h2"><?php _e( 'Export to CSV', 'bookly' ) ?></div>
8
- </div>
9
- <div class="modal-body">
10
- <div class="form-group">
11
- <label for="bookly-csv-delimiter"><?php _e( 'Delimiter', 'bookly' ) ?></label>
12
- <select id="bookly-csv-delimiter" class="form-control">
13
- <option value=","><?php _e( 'Comma (,)', 'bookly' ) ?></option>
14
- <option value=";"><?php _e( 'Semicolon (;)', 'bookly' ) ?></option>
15
- </select>
16
- </div>
17
- <div class="form-group">
18
- <div class="checkbox"><label><input checked value="0" type="checkbox" /><?php _e( 'No.', 'bookly' ) ?></label></div>
19
- <div class="checkbox"><label><input checked value="1" type="checkbox" /><?php _e( 'Booking Time', 'bookly' ) ?></label></div>
20
- <div class="checkbox"><label><input checked value="2" type="checkbox" /><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_employee' ) ?></label></div>
21
- <div class="checkbox"><label><input checked value="3" type="checkbox" /><?php _e( 'Customer Name', 'bookly' ) ?></label></div>
22
- <div class="checkbox"><label><input checked value="4" type="checkbox" /><?php _e( 'Customer Phone', 'bookly' ) ?></label></div>
23
- <div class="checkbox"><label><input checked value="5" type="checkbox" /><?php _e( 'Customer Email', 'bookly' ) ?></label></div>
24
- <div class="checkbox"><label><input checked value="6" type="checkbox" /><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_service' ) ?></label></div>
25
- <div class="checkbox"><label><input checked value="7" type="checkbox" /><?php _e( 'Duration', 'bookly' ) ?></label></div>
26
- <div class="checkbox"><label><input checked value="8" type="checkbox" /><?php _e( 'Status', 'bookly' ) ?></label></div>
27
- <div class="checkbox"><label><input checked value="9" type="checkbox" /><?php _e( 'Payment', 'bookly' ) ?></label></div>
28
- <?php $i = 10; foreach ( $custom_fields as $custom_field ) : ?>
29
- <div class="checkbox"><label><input checked value="<?php echo $i ++ ?>" type="checkbox"/><?php echo $custom_field->label ?></label></div>
30
- <?php endforeach ?>
31
- </div>
32
- </div>
33
- <div class="modal-footer">
34
- <button type="submit" class="btn btn-lg btn-success" id="bookly-export" data-dismiss="modal">
35
- <?php _e( 'Export to CSV', 'bookly' ) ?>
36
- </button>
37
- </div>
38
- </div>
39
- </div>
40
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div id="bookly-export-dialog" class="modal fade" tabindex=-1 role="dialog">
3
+ <div class="modal-dialog">
4
+ <div class="modal-content">
5
+ <div class="modal-header">
6
+ <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
7
+ <div class="modal-title h2"><?php _e( 'Export to CSV', 'bookly' ) ?></div>
8
+ </div>
9
+ <div class="modal-body">
10
+ <div class="form-group">
11
+ <label for="bookly-csv-delimiter"><?php _e( 'Delimiter', 'bookly' ) ?></label>
12
+ <select id="bookly-csv-delimiter" class="form-control">
13
+ <option value=","><?php _e( 'Comma (,)', 'bookly' ) ?></option>
14
+ <option value=";"><?php _e( 'Semicolon (;)', 'bookly' ) ?></option>
15
+ </select>
16
+ </div>
17
+ <div class="form-group">
18
+ <div class="checkbox"><label><input checked value="0" type="checkbox" /><?php _e( 'No.', 'bookly' ) ?></label></div>
19
+ <div class="checkbox"><label><input checked value="1" type="checkbox" /><?php _e( 'Booking Time', 'bookly' ) ?></label></div>
20
+ <div class="checkbox"><label><input checked value="2" type="checkbox" /><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_employee' ) ?></label></div>
21
+ <div class="checkbox"><label><input checked value="3" type="checkbox" /><?php _e( 'Customer Name', 'bookly' ) ?></label></div>
22
+ <div class="checkbox"><label><input checked value="4" type="checkbox" /><?php _e( 'Customer Phone', 'bookly' ) ?></label></div>
23
+ <div class="checkbox"><label><input checked value="5" type="checkbox" /><?php _e( 'Customer Email', 'bookly' ) ?></label></div>
24
+ <div class="checkbox"><label><input checked value="6" type="checkbox" /><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_service' ) ?></label></div>
25
+ <div class="checkbox"><label><input checked value="7" type="checkbox" /><?php _e( 'Duration', 'bookly' ) ?></label></div>
26
+ <div class="checkbox"><label><input checked value="8" type="checkbox" /><?php _e( 'Status', 'bookly' ) ?></label></div>
27
+ <div class="checkbox"><label><input checked value="9" type="checkbox" /><?php _e( 'Payment', 'bookly' ) ?></label></div>
28
+ <?php $i = 10; foreach ( $custom_fields as $custom_field ) : ?>
29
+ <div class="checkbox"><label><input checked value="<?php echo $i ++ ?>" type="checkbox"/><?php echo $custom_field->label ?></label></div>
30
+ <?php endforeach ?>
31
+ </div>
32
+ </div>
33
+ <div class="modal-footer">
34
+ <button type="submit" class="btn btn-lg btn-success" id="bookly-export" data-dismiss="modal">
35
+ <?php _e( 'Export to CSV', 'bookly' ) ?>
36
+ </button>
37
+ </div>
38
+ </div>
39
+ </div>
40
  </div>
backend/modules/appointments/templates/_print_dialog.php CHANGED
@@ -1 +1,33 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div id="bookly-print-dialog" class="modal fade" tabindex=-1 role="dialog">
3
+ <div class="modal-dialog">
4
+ <div class="modal-content">
5
+ <div class="modal-header">
6
+ <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
7
+ <h4 class="modal-title"><?php _e( 'Print', 'bookly' ) ?></h4>
8
+ </div>
9
+ <div class="modal-body">
10
+ <div class="form-group">
11
+ <div class="checkbox"><label><input checked value="0" type="checkbox"/><?php _e( 'No.', 'bookly' ) ?></label></div>
12
+ <div class="checkbox"><label><input checked value="1" type="checkbox"/><?php _e( 'Appointment Date', 'bookly' ) ?></label></div>
13
+ <div class="checkbox"><label><input checked value="2" type="checkbox"/><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_employee' ) ?></label></div>
14
+ <div class="checkbox"><label><input checked value="3" type="checkbox"/><?php _e( 'Customer Name', 'bookly' ) ?></label></div>
15
+ <div class="checkbox"><label><input checked value="4" type="checkbox"/><?php _e( 'Customer Phone', 'bookly' ) ?></label></div>
16
+ <div class="checkbox"><label><input checked value="5" type="checkbox"/><?php _e( 'Customer Email', 'bookly' ) ?></label></div>
17
+ <div class="checkbox"><label><input checked value="6" type="checkbox"/><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_service' ) ?></label></div>
18
+ <div class="checkbox"><label><input checked value="7" type="checkbox"/><?php _e( 'Duration', 'bookly' ) ?></label></div>
19
+ <div class="checkbox"><label><input checked value="8" type="checkbox"/><?php _e( 'Status', 'bookly' ) ?></label></div>
20
+ <div class="checkbox"><label><input checked value="9" type="checkbox"/><?php _e( 'Payment', 'bookly' ) ?></label></div>
21
+ <?php $i = 10; foreach ( $custom_fields as $custom_field ) : ?>
22
+ <div class="checkbox"><label><input checked value="<?php echo $i ++ ?>" type="checkbox"/><?php echo $custom_field->label ?></label></div>
23
+ <?php endforeach ?>
24
+ </div>
25
+ </div>
26
+ <div class="modal-footer">
27
+ <button type="button" class="btn btn-lg btn-success" id="bookly-print" data-dismiss="modal">
28
+ <?php _e( 'Print', 'bookly' ) ?>
29
+ </button>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
backend/modules/appointments/templates/index.php CHANGED
@@ -1,118 +1,121 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use \BooklyLite\Lib\Entities\CustomerAppointment;
3
- ?>
4
- <div id="bookly-tbs" class="wrap">
5
- <div class="bookly-tbs-body">
6
- <div class="page-header text-right clearfix">
7
- <div class="bookly-page-title">
8
- <?php _e( 'Appointments', 'bookly' ) ?>
9
- </div>
10
- </div>
11
- <div class="panel panel-default bookly-main">
12
- <div class="panel-body">
13
- <div class="row">
14
- <div class="form-inline bookly-margin-bottom-lg text-right">
15
- <div class="form-group">
16
- <button type="button" class="btn btn-default bookly-btn-block-xs" data-toggle="modal" data-target="#bookly-export-dialog"><i class="glyphicon glyphicon-export"></i> <?php _e( 'Export to CSV', 'bookly' ) ?></button>
17
- </div>
18
- <div class="form-group">
19
- <button type="button" class="btn btn-default bookly-btn-block-xs bookly-limitation"><i class="glyphicon glyphicon-print"></i> <?php _e( 'Print', 'bookly' ) ?></button>
20
- </div>
21
- <div class="form-group">
22
- <button type="button" class="btn btn-success bookly-btn-block-xs" id="bookly-add"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'New appointment', 'bookly' ) ?></button>
23
- </div>
24
- </div>
25
-
26
- <div class="col-md-4 col-lg-1">
27
- <div class="form-group">
28
- <input class="form-control" type="text" id="bookly-filter-id" placeholder="<?php esc_attr_e( 'No.', 'bookly' ) ?>" />
29
- </div>
30
- </div>
31
- <div class="col-md-4 col-lg-3">
32
- <div class="bookly-margin-bottom-lg bookly-relative">
33
- <button type="button" class="btn btn-block btn-default" id="bookly-filter-date" data-date="<?php echo date( 'Y-m-d', strtotime( 'first day of' ) ) ?> - <?php echo date( 'Y-m-d', strtotime( 'last day of' ) ) ?>">
34
- <i class="dashicons dashicons-calendar-alt"></i>
35
- <span>
36
- <?php echo date_i18n( get_option( 'date_format' ), strtotime( 'first day of' ) ) ?> - <?php echo date_i18n( get_option( 'date_format' ), strtotime( 'last day of' ) ) ?>
37
- </span>
38
- </button>
39
- </div>
40
- </div>
41
- <div class="col-md-4 col-lg-2">
42
- <div class="form-group">
43
- <select class="form-control bookly-js-chosen-select" id="bookly-filter-staff" data-placeholder="<?php echo esc_attr( \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_employee' ) ) ?>">
44
- <option value="-1"></option>
45
- <?php foreach ( $staff_members as $staff ) : ?>
46
- <option value="<?php echo $staff['id'] ?>"><?php esc_html_e( $staff['full_name'] ) ?></option>
47
- <?php endforeach ?>
48
- </select>
49
- </div>
50
- </div>
51
- <div class="clearfix visible-md-block"></div>
52
- <div class="col-md-4 col-lg-2">
53
- <div class="form-group">
54
- <select class="form-control bookly-js-chosen-select" id="bookly-filter-customer" data-placeholder="<?php esc_attr_e( 'Customer', 'bookly' ) ?>">
55
- <option value="-1"></option>
56
- <?php foreach ( $customers as $customer ) : ?>
57
- <option value="<?php echo $customer['id'] ?>"><?php esc_html_e( $customer['name'] ) ?></option>
58
- <?php endforeach ?>
59
- </select>
60
- </div>
61
- </div>
62
- <div class="col-md-4 col-lg-2">
63
- <div class="form-group">
64
- <select class="form-control bookly-js-chosen-select" id="bookly-filter-service" data-placeholder="<?php echo esc_attr( \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_service' ) ) ?>">
65
- <option value="-1"></option>
66
- <?php foreach ( $services as $service ) : ?>
67
- <option value="<?php echo $service['id'] ?>"><?php esc_html_e( $service['title'] ) ?></option>
68
- <?php endforeach ?>
69
- </select>
70
- </div>
71
- </div>
72
- <div class="col-md-4 col-lg-2">
73
- <div class="form-group">
74
- <select class="form-control bookly-js-chosen-select" id="bookly-filter-status" data-placeholder="<?php esc_attr_e( 'Status', 'bookly' ) ?>">
75
- <option value="-1"></option>
76
- <option value="<?php echo CustomerAppointment::STATUS_PENDING ?>"><?php echo CustomerAppointment::statusToString( CustomerAppointment::STATUS_PENDING ) ?></option>
77
- <option value="<?php echo CustomerAppointment::STATUS_APPROVED ?>"><?php echo CustomerAppointment::statusToString( CustomerAppointment::STATUS_APPROVED ) ?></option>
78
- <option value="<?php echo CustomerAppointment::STATUS_CANCELLED ?>"><?php echo CustomerAppointment::statusToString( CustomerAppointment::STATUS_CANCELLED ) ?></option>
79
- </select>
80
- </div>
81
- </div>
82
- </div>
83
-
84
- <table id="bookly-appointments-list" class="table table-striped" width="100%">
85
- <thead>
86
- <tr>
87
- <th><?php _e( 'No.', 'bookly' ) ?></th>
88
- <th><?php _e( 'Appointment Date', 'bookly' ) ?></th>
89
- <th><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_employee' ) ?></th>
90
- <th><?php _e( 'Customer Name', 'bookly' ) ?></th>
91
- <th><?php _e( 'Customer Phone', 'bookly' ) ?></th>
92
- <th><?php _e( 'Customer Email', 'bookly' ) ?></th>
93
- <th><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_service' ) ?></th>
94
- <th><?php _e( 'Duration', 'bookly' ) ?></th>
95
- <th><?php _e( 'Status', 'bookly' ) ?></th>
96
- <th><?php _e( 'Payment', 'bookly' ) ?></th>
97
- <?php foreach ( $custom_fields as $custom_field ) : ?>
98
- <th><?php echo $custom_field->label ?></th>
99
- <?php endforeach ?>
100
- <th></th>
101
- <th width="16"><input type="checkbox" id="bookly-check-all" /></th>
102
- </tr>
103
- </thead>
104
- </table>
105
-
106
- <div class="text-right bookly-margin-top-lg">
107
- <?php \BooklyLite\Lib\Utils\Common::deleteButton() ?>
108
- </div>
109
- </div>
110
- </div>
111
-
112
- <?php include '_export_dialog.php' ?>
113
- <?php include '_print_dialog.php' ?>
114
-
115
- <?php \BooklyLite\Backend\Modules\Calendar\Components::getInstance()->renderAppointmentDialog() ?>
116
- <?php do_action( 'bookly_render_component_appointments' ) ?>
117
- </div>
118
- </div>
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use BooklyLite\Lib\Entities\CustomerAppointment;
3
+ ?>
4
+ <div id="bookly-tbs" class="wrap">
5
+ <div class="bookly-tbs-body">
6
+ <div class="page-header text-right clearfix">
7
+ <div class="bookly-page-title">
8
+ <?php _e( 'Appointments', 'bookly' ) ?>
9
+ </div>
10
+ <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
11
+ </div>
12
+ <div class="panel panel-default bookly-main">
13
+ <div class="panel-body">
14
+ <div class="row">
15
+ <div class="form-inline bookly-margin-bottom-lg text-right">
16
+ <div class="form-group">
17
+ <button type="button" class="btn btn-default bookly-btn-block-xs" data-toggle="modal" data-target="#bookly-export-dialog"><i class="glyphicon glyphicon-export"></i> <?php _e( 'Export to CSV', 'bookly' ) ?></button>
18
+ </div>
19
+ <div class="form-group">
20
+ <button type="button" class="btn btn-default bookly-btn-block-xs bookly-limitation"><i class="glyphicon glyphicon-print"></i> <?php _e( 'Print', 'bookly' ) ?></button>
21
+ </div>
22
+ <div class="form-group">
23
+ <button type="button" class="btn btn-success bookly-btn-block-xs" id="bookly-add"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'New appointment', 'bookly' ) ?></button>
24
+ </div>
25
+ </div>
26
+
27
+ <div class="col-md-4 col-lg-1">
28
+ <div class="form-group">
29
+ <input class="form-control" type="text" id="bookly-filter-id" placeholder="<?php esc_attr_e( 'No.', 'bookly' ) ?>" />
30
+ </div>
31
+ </div>
32
+ <div class="col-md-4 col-lg-3">
33
+ <div class="bookly-margin-bottom-lg bookly-relative">
34
+ <button type="button" class="btn btn-block btn-default" id="bookly-filter-date" data-date="<?php echo date( 'Y-m-d', strtotime( 'first day of' ) ) ?> - <?php echo date( 'Y-m-d', strtotime( 'last day of' ) ) ?>">
35
+ <i class="dashicons dashicons-calendar-alt"></i>
36
+ <span>
37
+ <?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( 'first day of this month' ) ?> - <?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( 'last day of this month' ) ?>
38
+ </span>
39
+ </button>
40
+ </div>
41
+ </div>
42
+ <div class="col-md-4 col-lg-2">
43
+ <div class="form-group">
44
+ <select class="form-control bookly-js-chosen-select" id="bookly-filter-staff" data-placeholder="<?php echo esc_attr( \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_employee' ) ) ?>">
45
+ <option value="-1"></option>
46
+ <?php foreach ( $staff_members as $staff ) : ?>
47
+ <option value="<?php echo $staff['id'] ?>"><?php esc_html_e( $staff['full_name'] ) ?></option>
48
+ <?php endforeach ?>
49
+ </select>
50
+ </div>
51
+ </div>
52
+ <div class="clearfix visible-md-block"></div>
53
+ <div class="col-md-4 col-lg-2">
54
+ <div class="form-group">
55
+ <select class="form-control bookly-js-chosen-select" id="bookly-filter-customer" data-placeholder="<?php esc_attr_e( 'Customer', 'bookly' ) ?>">
56
+ <option value="-1"></option>
57
+ <?php foreach ( $customers as $customer ) : ?>
58
+ <option value="<?php echo $customer['id'] ?>"><?php esc_html_e( $customer['name'] ) ?></option>
59
+ <?php endforeach ?>
60
+ </select>
61
+ </div>
62
+ </div>
63
+ <div class="col-md-4 col-lg-2">
64
+ <div class="form-group">
65
+ <select class="form-control bookly-js-chosen-select" id="bookly-filter-service" data-placeholder="<?php echo esc_attr( \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_service' ) ) ?>">
66
+ <option value="-1"></option>
67
+ <?php foreach ( $services as $service ) : ?>
68
+ <option value="<?php echo $service['id'] ?>"><?php esc_html_e( $service['title'] ) ?></option>
69
+ <?php endforeach ?>
70
+ </select>
71
+ </div>
72
+ </div>
73
+ <div class="col-md-4 col-lg-2">
74
+ <div class="form-group">
75
+ <select class="form-control bookly-js-chosen-select" id="bookly-filter-status" data-placeholder="<?php esc_attr_e( 'Status', 'bookly' ) ?>">
76
+ <option value="-1"></option>
77
+ <option value="<?php echo CustomerAppointment::STATUS_PENDING ?>"><?php echo CustomerAppointment::statusToString( CustomerAppointment::STATUS_PENDING ) ?></option>
78
+ <option value="<?php echo CustomerAppointment::STATUS_APPROVED ?>"><?php echo CustomerAppointment::statusToString( CustomerAppointment::STATUS_APPROVED ) ?></option>
79
+ <option value="<?php echo CustomerAppointment::STATUS_CANCELLED ?>"><?php echo CustomerAppointment::statusToString( CustomerAppointment::STATUS_CANCELLED ) ?></option>
80
+ <option value="<?php echo CustomerAppointment::STATUS_REJECTED ?>"><?php echo CustomerAppointment::statusToString( CustomerAppointment::STATUS_REJECTED ) ?></option>
81
+ </select>
82
+ </div>
83
+ </div>
84
+ </div>
85
+
86
+ <table id="bookly-appointments-list" class="table table-striped" width="100%">
87
+ <thead>
88
+ <tr>
89
+ <th><?php _e( 'No.', 'bookly' ) ?></th>
90
+ <th><?php _e( 'Appointment Date', 'bookly' ) ?></th>
91
+ <th><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_employee' ) ?></th>
92
+ <th><?php _e( 'Customer Name', 'bookly' ) ?></th>
93
+ <th><?php _e( 'Customer Phone', 'bookly' ) ?></th>
94
+ <th><?php _e( 'Customer Email', 'bookly' ) ?></th>
95
+ <th><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_service' ) ?></th>
96
+ <th><?php _e( 'Duration', 'bookly' ) ?></th>
97
+ <th><?php _e( 'Status', 'bookly' ) ?></th>
98
+ <th><?php _e( 'Payment', 'bookly' ) ?></th>
99
+ <?php foreach ( $custom_fields as $custom_field ) : ?>
100
+ <th><?php echo $custom_field->label ?></th>
101
+ <?php endforeach ?>
102
+ <th></th>
103
+ <th width="16"><input type="checkbox" id="bookly-check-all" /></th>
104
+ </tr>
105
+ </thead>
106
+ </table>
107
+
108
+ <div class="text-right bookly-margin-top-lg">
109
+ <?php \BooklyLite\Lib\Utils\Common::deleteButton( '', '', '#bookly-delete-dialog' ) ?>
110
+ </div>
111
+ </div>
112
+ </div>
113
+
114
+ <?php \BooklyLite\Backend\Modules\Calendar\Components::getInstance()->renderDeleteDialog(); ?>
115
+ <?php include '_export_dialog.php' ?>
116
+ <?php include '_print_dialog.php' ?>
117
+
118
+ <?php \BooklyLite\Backend\Modules\Calendar\Components::getInstance()->renderAppointmentDialog() ?>
119
+ <?php do_action( 'bookly_render_component_appointments' ) ?>
120
+ </div>
121
+ </div>
backend/modules/calendar/Components.php CHANGED
@@ -19,22 +19,27 @@ class Components extends Lib\Base\Components
19
 
20
  $this->enqueueStyles( array(
21
  'backend' => array( 'css/jquery-ui-theme/jquery-ui.min.css', ),
 
22
  ) );
23
 
24
  $this->enqueueScripts( array(
25
  'backend' => array(
26
  'js/angular.min.js' => array( 'jquery' ),
27
- 'js/angular-ui-date-0.0.8.js' => array( 'ab-angular.min.js' ),
28
  'js/moment.min.js' => array( 'jquery' ),
29
  'js/chosen.jquery.min.js' => array( 'jquery' ),
30
  'js/help.js' => array( 'jquery' ),
31
  ),
 
 
 
 
32
  'module' => array(
33
- 'js/ng-appointment_dialog.js' => array( 'ab-angular-ui-date-0.0.8.js', 'jquery-ui-datepicker' ),
34
  )
35
  ) );
36
 
37
- wp_localize_script( 'ab-ng-appointment_dialog.js', 'BooklyL10nAppDialog', array(
38
  'calendar' => array(
39
  'shortMonths' => array_values( $wp_locale->month_abbrev ),
40
  'longMonths' => array_values( $wp_locale->month ),
@@ -52,11 +57,32 @@ class Components extends Lib\Base\Components
52
 
53
  // Custom fields without captcha field.
54
  $custom_fields = array_filter(
55
- json_decode( get_option( 'ab_custom_fields' ) ),
56
  function( $field ) { return ! in_array( $field->type, array( 'captcha', 'text-content' ) ); }
57
  );
58
 
59
  $this->render( '_appointment_dialog', compact( 'custom_fields' ) );
60
  }
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  }
19
 
20
  $this->enqueueStyles( array(
21
  'backend' => array( 'css/jquery-ui-theme/jquery-ui.min.css', ),
22
+ 'frontend' => array( 'css/ladda.min.css', ),
23
  ) );
24
 
25
  $this->enqueueScripts( array(
26
  'backend' => array(
27
  'js/angular.min.js' => array( 'jquery' ),
28
+ 'js/angular-ui-date-0.0.8.js' => array( 'bookly-angular.min.js' ),
29
  'js/moment.min.js' => array( 'jquery' ),
30
  'js/chosen.jquery.min.js' => array( 'jquery' ),
31
  'js/help.js' => array( 'jquery' ),
32
  ),
33
+ 'frontend' => array(
34
+ 'js/spin.min.js' => array( 'jquery' ),
35
+ 'js/ladda.min.js' => array( 'jquery' ),
36
+ ),
37
  'module' => array(
38
+ 'js/ng-appointment_dialog.js' => array( 'bookly-angular-ui-date-0.0.8.js', 'jquery-ui-datepicker' ),
39
  )
40
  ) );
41
 
42
+ wp_localize_script( 'bookly-ng-appointment_dialog.js', 'BooklyL10nAppDialog', array(
43
  'calendar' => array(
44
  'shortMonths' => array_values( $wp_locale->month_abbrev ),
45
  'longMonths' => array_values( $wp_locale->month ),
57
 
58
  // Custom fields without captcha field.
59
  $custom_fields = array_filter(
60
+ json_decode( get_option( 'bookly_custom_fields' ) ),
61
  function( $field ) { return ! in_array( $field->type, array( 'captcha', 'text-content' ) ); }
62
  );
63
 
64
  $this->render( '_appointment_dialog', compact( 'custom_fields' ) );
65
  }
66
 
67
+ /**
68
+ * Render delete appointment dialog
69
+ */
70
+ public function renderDeleteDialog()
71
+ {
72
+ $this->enqueueStyles( array(
73
+ 'frontend' => array( 'css/ladda.min.css', ),
74
+ ) );
75
+
76
+ $this->enqueueScripts( array(
77
+ 'frontend' => array(
78
+ 'js/spin.min.js' => array( 'jquery' ),
79
+ 'js/ladda.min.js' => array( 'jquery' ),
80
+ ),
81
+ 'module' => array(
82
+ 'js/delete_dialog.js' => array( 'jquery' ),
83
+ ),
84
+ ) );
85
+ $this->render( '_delete_dialog' );
86
+ }
87
+
88
  }
backend/modules/calendar/Controller.php CHANGED
@@ -1,584 +1,748 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Calendar;
3
-
4
- use \BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Calendar
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- protected function getPermissions()
13
- {
14
- return array( '_this' => 'user' );
15
- }
16
-
17
- public function index()
18
- {
19
- /** @var \WP_Locale $wp_locale */
20
- global $wp_locale;
21
-
22
- $this->enqueueStyles( array(
23
- 'module' => array( 'css/fullcalendar.min.css', ),
24
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
25
- ) );
26
-
27
- $this->enqueueScripts( array(
28
- 'backend' => array( 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ), ),
29
- 'module' => array(
30
- 'js/fullcalendar.min.js' => array( 'ab-moment.min.js' ),
31
- 'js/fc-multistaff-view.js' => array( 'ab-fullcalendar.min.js' ),
32
- 'js/calendar.js' => array( 'ab-fc-multistaff-view.js' ),
33
- ),
34
- ) );
35
-
36
- $slot_length_minutes = get_option( 'ab_settings_time_slot_length', '15' );
37
- $slot = new \DateInterval( 'PT' . $slot_length_minutes . 'M' );
38
-
39
- $staff_members = Lib\Utils\Common::isCurrentUserAdmin()
40
- ? Lib\Entities\Staff::query()->sortBy( 'position' )->find()
41
- : Lib\Entities\Staff::query()->where( 'wp_user_id', get_current_user_id() )->find();
42
-
43
- wp_localize_script( 'ab-calendar.js', 'BooklyL10n', array(
44
- 'slotDuration' => $slot->format( '%H:%I:%S' ),
45
- 'calendar' => array(
46
- 'shortMonths' => array_values( $wp_locale->month_abbrev ),
47
- 'longMonths' => array_values( $wp_locale->month ),
48
- 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
49
- 'longDays' => array_values( $wp_locale->weekday ),
50
- ),
51
- 'dpDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_JQUERY_DATEPICKER ),
52
- 'mjsDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
53
- 'mjsTimeFormat' => Lib\Utils\DateTime::convertFormat( 'time', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
54
- 'today' => __( 'Today', 'bookly' ),
55
- 'week' => __( 'Week', 'bookly' ),
56
- 'day' => __( 'Day', 'bookly' ),
57
- 'month' => __( 'Month', 'bookly' ),
58
- 'allDay' => __( 'All Day', 'bookly' ),
59
- 'delete' => __( 'Delete', 'bookly' ),
60
- 'noStaffSelected' => __( 'No staff selected', 'bookly' ),
61
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
62
- 'startOfWeek' => (int) get_option( 'start_of_week' ),
63
- ) );
64
-
65
- $this->render( 'calendar', compact( 'staff_members' ) );
66
- }
67
-
68
- /**
69
- * Get data for FullCalendar.
70
- *
71
- * return string json
72
- */
73
- public function executeGetStaffAppointments()
74
- {
75
- $result = array();
76
- $staff_members = array();
77
- $one_day = new \DateInterval( 'P1D' );
78
- $start_date = new \DateTime( $this->getParameter( 'start' ) );
79
- $end_date = new \DateTime( $this->getParameter( 'end' ) );
80
- // FullCalendar sends end date as 1 day further.
81
- $end_date->sub( $one_day );
82
-
83
- if ( Lib\Utils\Common::isCurrentUserAdmin() ) {
84
- $staff_members = Lib\Entities\Staff::query()
85
- ->where( 'id', 1 )
86
- ->find();
87
- } else {
88
- $staff_members[] = Lib\Entities\Staff::query()
89
- ->where( 'wp_user_id', get_current_user_id() )
90
- ->findOne();
91
- }
92
-
93
- foreach ( $staff_members as $staff ) {
94
- /** @var Lib\Entities\Staff $staff */
95
- $result = array_merge( $result, $staff->getAppointmentsForFC( $start_date, $end_date ) );
96
-
97
- // Schedule.
98
- $items = $staff->getScheduleItems();
99
- $day = clone $start_date;
100
- // Find previous day end time.
101
- $last_end = clone $day;
102
- $last_end->sub( $one_day );
103
- $w = intval( $day->format( 'w' ) );
104
- $end_time = $items[ $w > 0 ? $w : 7 ]->get( 'end_time' );
105
- if ( $end_time !== null ) {
106
- $end_time = explode( ':', $end_time );
107
- $last_end->setTime( $end_time[0], $end_time[1] );
108
- } else {
109
- $last_end->setTime( 24, 0 );
110
- }
111
- // Do the loop.
112
- while ( $day <= $end_date ) {
113
- do {
114
- /** @var Lib\Entities\StaffScheduleItem $item */
115
- $item = $items[ intval( $day->format( 'w' ) ) + 1 ];
116
- if ( $item->get( 'start_time' ) && ! $staff->isOnHoliday( $day ) ) {
117
- $start = $last_end->format( 'Y-m-d H:i:s' );
118
- $end = $day->format( 'Y-m-d ' . $item->get( 'start_time' ) );
119
- if ( $start < $end ) {
120
- $result[] = array(
121
- 'start' => $start,
122
- 'end' => $end,
123
- 'rendering' => 'background',
124
- 'staffId' => $staff->get( 'id' ),
125
- );
126
- }
127
- $last_end = clone $day;
128
- $end_time = explode( ':', $item->get( 'end_time' ) );
129
- $last_end->setTime( $end_time[0], $end_time[1] );
130
-
131
- // Breaks.
132
- foreach ( $item->getBreaksList() as $break ) {
133
- $result[] = array(
134
- 'start' => $day->format( 'Y-m-d ' . $break['start_time'] ),
135
- 'end' => $day->format( 'Y-m-d ' . $break['end_time'] ),
136
- 'rendering' => 'background',
137
- 'staffId' => $staff->get( 'id' ),
138
- );
139
- }
140
-
141
- break;
142
- }
143
-
144
- $result[] = array(
145
- 'start' => $last_end->format( 'Y-m-d H:i:s' ),
146
- 'end' => $day->format( 'Y-m-d 24:00:00' ),
147
- 'rendering' => 'background',
148
- 'staffId' => $staff->get( 'id' ),
149
- );
150
- $last_end = clone $day;
151
- $last_end->setTime( 24, 0 );
152
-
153
- } while ( 0 );
154
-
155
- $day->add( $one_day );
156
- }
157
-
158
- if ( $last_end->format( 'H' ) != 24 ) {
159
- $result[] = array(
160
- 'start' => $last_end->format( 'Y-m-d H:i:s' ),
161
- 'end' => $last_end->format( 'Y-m-d 24:00:00' ),
162
- 'rendering' => 'background',
163
- 'staffId' => $staff->get( 'id' ),
164
- );
165
- }
166
- }
167
-
168
- wp_send_json( $result );
169
- }
170
-
171
- /**
172
- * Get data needed for appointment form initialisation.
173
- */
174
- public function executeGetDataForAppointmentForm()
175
- {
176
- $result = array(
177
- 'staff' => array(),
178
- 'customers' => array(),
179
- 'start_time' => array(),
180
- 'end_time' => array(),
181
- 'time_interval' => Lib\Config::getTimeSlotLength(),
182
- 'status' => array(
183
- 'items' => array(
184
- 'pending' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_PENDING ),
185
- 'approved' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_APPROVED ),
186
- 'cancelled' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_CANCELLED ),
187
- ),
188
- 'default' => get_option( 'ab_settings_default_appointment_status' ),
189
- ),
190
- );
191
-
192
- // Staff list.
193
- $staff_members = Lib\Utils\Common::isCurrentUserAdmin()
194
- ? Lib\Entities\Staff::query()->sortBy( 'position' )->find()
195
- : Lib\Entities\Staff::query()->where( 'wp_user_id', get_current_user_id() )->find();
196
-
197
- /** @var Lib\Entities\Staff $staff_member */
198
- foreach ( $staff_members as $staff_member ) {
199
- $services = array();
200
- foreach ( $staff_member->getStaffServices() as $staff_service ) {
201
- $services[] = array(
202
- 'id' => $staff_service->service->get( 'id' ),
203
- 'title' => sprintf(
204
- '%s (%s)',
205
- $staff_service->service->get( 'title' ),
206
- Lib\Utils\DateTime::secondsToInterval( $staff_service->service->get( 'duration' ) )
207
- ),
208
- 'duration' => $staff_service->service->get( 'duration' ),
209
- 'capacity' => $staff_service->get( 'capacity' )
210
- );
211
- }
212
- $result['staff'][] = array(
213
- 'id' => $staff_member->get( 'id' ),
214
- 'full_name' => $staff_member->get( 'full_name' ),
215
- 'services' => $services
216
- );
217
- }
218
-
219
- // Customers list.
220
- foreach ( Lib\Entities\Customer::query()->sortBy( 'name' )->find() as $customer ) {
221
- $name = $customer->get( 'name' );
222
- if ( $customer->get( 'email' ) != '' || $customer->get( 'phone' ) != '' ) {
223
- $name .= ' (' . trim( $customer->get( 'email' ) . ', ' . $customer->get( 'phone' ) , ', ' ) . ')';
224
- }
225
-
226
- $result['customers'][] = array(
227
- 'id' => $customer->get( 'id' ),
228
- 'name' => $name,
229
- 'custom_fields' => array(),
230
- 'number_of_persons' => 1,
231
- );
232
- }
233
-
234
- // Time list.
235
- $ts_length = Lib\Config::getTimeSlotLength();
236
- $time_start = 0;
237
- $time_end = DAY_IN_SECONDS * 2;
238
-
239
- // Run the loop.
240
- while ( $time_start <= $time_end ) {
241
- $slot = array(
242
- 'value' => Lib\Utils\DateTime::buildTimeString( $time_start, false ),
243
- 'title' => Lib\Utils\DateTime::formatTime( $time_start )
244
- );
245
- if ( $time_start < DAY_IN_SECONDS ) {
246
- $result['start_time'][] = $slot;
247
- }
248
- $result['end_time'][] = $slot;
249
- $time_start += $ts_length;
250
- }
251
-
252
- wp_send_json( $result );
253
- }
254
-
255
- /**
256
- * Get appointment data when editing an appointment.
257
- */
258
- public function executeGetDataForAppointment()
259
- {
260
- $response = array( 'success' => false, 'data' => array( 'customers' => array() ) );
261
-
262
- $appointment = new Lib\Entities\Appointment();
263
- if ( $appointment->load( $this->getParameter( 'id' ) ) ) {
264
- $response['success'] = true;
265
-
266
- $info = Lib\Entities\Appointment::query( 'a' )
267
- ->select( 'ss.capacity AS max_capacity, SUM( ca.number_of_persons ) AS total_number_of_persons, a.staff_id, a.service_id, a.start_date, a.end_date, a.internal_note' )
268
- ->leftJoin( 'CustomerAppointment', 'ca', 'ca.appointment_id = a.id' )
269
- ->leftJoin( 'StaffService', 'ss', 'ss.staff_id = a.staff_id AND ss.service_id = a.service_id' )
270
- ->where( 'a.id', $appointment->get( 'id' ) )
271
- ->fetchRow();
272
-
273
- $response['data']['total_number_of_persons'] = $info['total_number_of_persons'];
274
- $response['data']['max_capacity'] = $info['max_capacity'];
275
- $response['data']['start_date'] = $info['start_date'];
276
- $response['data']['end_date'] = $info['end_date'];
277
- $response['data']['staff_id'] = $info['staff_id'];
278
- $response['data']['service_id'] = $info['service_id'];
279
- $response['data']['internal_note'] = $info['internal_note'];
280
- $customers = Lib\Entities\CustomerAppointment::query( 'ca' )
281
- ->select( 'ca.id,
282
- ca.customer_id,
283
- ca.custom_fields,
284
- ca.extras,
285
- ca.number_of_persons,
286
- ca.status,
287
- ca.payment_id,
288
- ca.compound_service_id,
289
- ca.compound_token,
290
- ca.location_id,
291
- p.paid AS payment,
292
- p.total AS payment_total,
293
- p.type AS payment_type,
294
- p.details AS payment_details,
295
- p.status AS payment_status' )
296
- ->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
297
- ->where( 'ca.appointment_id', $appointment->get( 'id' ) )
298
- ->fetchArray();
299
- foreach ( $customers as $customer ) {
300
- $payment_title = '';
301
- if ( $customer['payment'] !== null ) {
302
- $payment_title = Lib\Utils\Common::formatPrice( $customer['payment'] );
303
- if ( $customer['payment'] != $customer['payment_total'] ) {
304
- $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Common::formatPrice( $customer['payment_total'] ) );
305
- }
306
- $payment_title .= sprintf(
307
- ' %s <span%s>%s</span>',
308
- Lib\Entities\Payment::typeToString( $customer['payment_type'] ),
309
- $customer['payment_status'] == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
310
- Lib\Entities\Payment::statusToString( $customer['payment_status'] )
311
- );
312
- }
313
- $compound_service = '';
314
- if ( $customer['compound_service_id'] !== null ) {
315
- $service = new Lib\Entities\Service();
316
- if ( $service->load( $customer['compound_service_id'] ) ) {
317
- $compound_service = $service->getTitle();
318
- }
319
- }
320
- $response['data']['customers'][] = array(
321
- 'id' => $customer['customer_id'],
322
- 'ca_id' => $customer['id'],
323
- 'compound_service' => $compound_service,
324
- 'compound_token' => $customer['compound_token'],
325
- 'custom_fields' => (array) json_decode( $customer['custom_fields'], true ),
326
- 'extras' => (array) json_decode( $customer['extras'], true ),
327
- 'location_id' => $customer['location_id'],
328
- 'number_of_persons' => $customer['number_of_persons'],
329
- 'payment_id' => $customer['payment_id'],
330
- 'payment_type' => $customer['payment'] != $customer['payment_total'] ? 'partial' : 'full',
331
- 'payment_title' => $payment_title,
332
- 'status' => $customer['status'],
333
- );
334
- }
335
- }
336
- wp_send_json( $response );
337
- }
338
-
339
- /**
340
- * Save appointment form (for both create and edit).
341
- */
342
- public function executeSaveAppointmentForm()
343
- {
344
- $response = array( 'success' => false );
345
-
346
- $internal_note = $this->getParameter( 'internal_note' );
347
- $start_date = $this->getParameter( 'start_date' );
348
- $end_date = $this->getParameter( 'end_date' );
349
- $staff_id = 1;
350
- $service_id = $this->getParameter( 'service_id' );
351
- $appointment_id = $this->getParameter( 'id', 0 );
352
- $customers = json_decode( $this->getParameter( 'customers', '[]' ), true );
353
-
354
- $staff_service = new Lib\Entities\StaffService();
355
- $staff_service->loadBy( array(
356
- 'staff_id' => $staff_id,
357
- 'service_id' => $service_id
358
- ) );
359
-
360
- // Check for errors.
361
- if ( ! $start_date ) {
362
- $response['errors']['time_interval'] = __( 'Start time must not be empty', 'bookly' );
363
- } elseif ( ! $end_date ) {
364
- $response['errors']['time_interval'] = __( 'End time must not be empty', 'bookly' );
365
- } elseif ( $start_date == $end_date ) {
366
- $response['errors']['time_interval'] = __( 'End time must not be equal to start time', 'bookly' );
367
- }
368
- if ( ! $service_id ) {
369
- $response['errors']['service_required'] = true;
370
- }
371
- if ( empty ( $customers ) ) {
372
- $response['errors']['customers_required'] = true;
373
- }
374
- $total_number_of_persons = 0;
375
- $max_extras_duration = 0;
376
- foreach ( $customers as $customer ) {
377
- if ( $customer['status'] != Lib\Entities\CustomerAppointment::STATUS_CANCELLED ) {
378
- $total_number_of_persons += $customer['number_of_persons'];
379
- $extras_duration = apply_filters( 'bookly_extras_get_total_duration', 0, $customer['extras'] );
380
- if ( $extras_duration > $max_extras_duration ) {
381
- $max_extras_duration = $extras_duration;
382
- }
383
- }
384
- }
385
- if ( $total_number_of_persons > $staff_service->get( 'capacity' ) ) {
386
- $response['errors']['overflow_capacity'] = sprintf(
387
- __( 'The number of customers should not be more than %d', 'bookly' ),
388
- $staff_service->get( 'capacity' )
389
- );
390
- }
391
- $total_end_date = $end_date;
392
- if ( $max_extras_duration > 0 ) {
393
- $total_end_date = date_create( $end_date )->modify( '+' . $max_extras_duration . ' sec' )->format( 'Y-m-d H:i:s' );
394
- }
395
- if ( ! $this->dateIntervalIsAvailableForAppointment( $start_date, $total_end_date, $staff_id, $appointment_id ) ) {
396
- $response['errors']['date_interval_not_available'] = true;
397
- }
398
-
399
- // If no errors then try to save the appointment.
400
- if ( ! isset ( $response['errors'] ) ) {
401
- $appointment = new Lib\Entities\Appointment();
402
- if ( $appointment_id ) {
403
- // Edit.
404
- $appointment->load( $appointment_id );
405
- }
406
- $appointment->set( 'internal_note', $internal_note );
407
- $appointment->set( 'start_date', $start_date );
408
- $appointment->set( 'end_date', $end_date );
409
- $appointment->set( 'staff_id', $staff_id );
410
- $appointment->set( 'service_id', $service_id );
411
- $appointment->set( 'extras_duration', $max_extras_duration );
412
-
413
- if ( $appointment->save() !== false ) {
414
- // Save customer appointments.
415
- $ca_status_changed = $appointment->saveCustomerAppointments( $customers );
416
- $customer_appointments = $appointment->getCustomerAppointments( true );
417
-
418
- // Google Calendar.
419
- $appointment->handleGoogleCalendar();
420
-
421
- // Send notifications.
422
- $notification = $this->getParameter( 'notification' );
423
- if ( $notification != 'no' ) {
424
- foreach ( $customer_appointments as $ca ) {
425
- // Send notification.
426
- if ( $notification == 'all' || in_array( $ca->get( 'id' ), $ca_status_changed ) ) {
427
- Lib\NotificationSender::send( $ca );
428
- }
429
- }
430
- }
431
-
432
- // Prepare response.
433
- $desc = '';
434
- // Display customer information only if there is 1 customer.
435
- if ( count( $customer_appointments ) == 1 ) {
436
- // Customer information.
437
- $customer = $customer_appointments[0]->customer;
438
- foreach ( array( 'name', 'phone', 'email' ) as $data_entry ) {
439
- $entry_value = $customer->get( $data_entry );
440
- if ( $entry_value ) {
441
- $desc .= '<div>' . esc_html( $entry_value ) . '</div>';
442
- }
443
- }
444
- $desc = apply_filters( 'bookly_calendar_appointment_description', $desc, $customer_appointments[0] );
445
- // Custom fields.
446
- $custom_fields = $customer_appointments[0]->getCustomFields();
447
- if ( ! empty( $custom_fields ) ) {
448
- $desc .= '<br/>';
449
- foreach ( $custom_fields as $custom_field ) {
450
- $desc .= '<div>' . wp_strip_all_tags( $custom_field['label'] ) . ': ' . esc_html( $custom_field['value'] ) . '</div>';
451
- }
452
- }
453
- // Payment.
454
- $payment = Lib\Entities\Payment::query()->select( 'total, type AS payment_gateway, status AS payment_status' )
455
- ->where( 'id', $customer_appointments[0]->get( 'payment_id' ) )
456
- ->fetchRow();
457
- if ( $payment ) {
458
- $desc .= sprintf(
459
- '<br/><div>%s: %s %s %s</div>',
460
- __( 'Payment', 'bookly' ),
461
- Lib\Utils\Common::formatPrice( $payment['total'] ),
462
- Lib\Entities\Payment::typeToString( $payment['payment_gateway'] ),
463
- Lib\Entities\Payment::statusToString( $payment['payment_status'] )
464
- );
465
- }
466
- // Status.
467
- $desc .= sprintf( '<br/><div>%s: %s</div>', __( 'Status', 'bookly' ), $customer_appointments[0]->getStatusTitle() );
468
- // Signed up & Capacity.
469
- if ( $staff_service->get( 'capacity' ) > 1 ) {
470
- $signed_up = 0;
471
- foreach ( $customer_appointments as $ca ) {
472
- $signed_up += $ca->get( 'number_of_persons' );
473
- }
474
- $desc .= '<br/><div>' . __( 'Signed up', 'bookly' ) . ': ' . $signed_up . '</div>';
475
- $desc .= '<div>' . __( 'Capacity', 'bookly' ) . ': ' . $staff_service->get( 'capacity' ) . '</div>';
476
- }
477
- } else {
478
- if ( $staff_service->get( 'capacity' ) > 1 ) {
479
- $signed_up = 0;
480
- foreach ( $customer_appointments as $ca ) {
481
- $signed_up += $ca->get( 'number_of_persons' );
482
- }
483
- $desc .= '<div>' . __( 'Signed up', 'bookly' ) . ': ' . $signed_up . '</div>';
484
- $desc .= '<div>' . __( 'Capacity', 'bookly' ) . ': ' . $staff_service->get( 'capacity' ) . '</div>';
485
- }
486
- }
487
-
488
- $startDate = new \DateTime( $appointment->get( 'start_date' ) );
489
- $endDate = new \DateTime( $appointment->get( 'end_date' ) );
490
- $endDate->modify( '+' . (int) $appointment->get( 'extras_duration' ) . ' sec' );
491
-
492
- $service = new Lib\Entities\Service();
493
- $service->load( $service_id );
494
-
495
- $response['success'] = true;
496
- $response['data'] = array(
497
- 'id' => (int) $appointment->get( 'id' ),
498
- 'start' => $startDate->format( 'Y-m-d H:i:s' ),
499
- 'end' => $endDate->format( 'Y-m-d H:i:s' ),
500
- 'desc' => $desc,
501
- 'title' => $service->get( 'title' ) ? $service->get( 'title' ) : __( 'Untitled', 'bookly' ),
502
- 'color' => $service->get( 'color' ),
503
- 'staffId' => $appointment->get( 'staff_id' ),
504
- );
505
- } else {
506
- $response['errors'] = array( 'db' => __( 'Could not save appointment in database.', 'bookly' ) );
507
- }
508
- }
509
-
510
- wp_send_json( $response );
511
- }
512
-
513
- public function executeCheckAppointmentDateSelection()
514
- {
515
- $start_date = $this->getParameter( 'start_date' );
516
- $end_date = $this->getParameter( 'end_date' );
517
- $staff_id = 1;
518
- $service_id = $this->getParameter( 'service_id' );
519
- $appointment_id = $this->getParameter( 'appointment_id' );
520
- $timestamp_diff = strtotime( $end_date ) - strtotime( $start_date );
521
-
522
- $result = array(
523
- 'date_interval_not_available' => false,
524
- 'date_interval_warning' => false,
525
- );
526
-
527
- if ( ! $this->dateIntervalIsAvailableForAppointment( $start_date, $end_date, $staff_id, $appointment_id ) ) {
528
- $result['date_interval_not_available'] = true;
529
- }
530
-
531
- if ( $service_id ) {
532
- $service = new Lib\Entities\Service();
533
- $service->load( $service_id );
534
-
535
- $duration = $service->get( 'duration' );
536
-
537
- // Service duration interval is not equal to.
538
- $result['date_interval_warning'] = ( $timestamp_diff != $duration );
539
- }
540
-
541
- wp_send_json( $result );
542
- }
543
-
544
- public function executeDeleteAppointment()
545
- {
546
- $appointment = new Lib\Entities\Appointment();
547
- $appointment->load( $this->getParameter( 'appointment_id' ) );
548
- $appointment->delete();
549
- exit;
550
- }
551
-
552
- /**
553
- * @param $start_date
554
- * @param $end_date
555
- * @param $staff_id
556
- * @param $appointment_id
557
- * @return bool
558
- */
559
- private function dateIntervalIsAvailableForAppointment( $start_date, $end_date, $staff_id, $appointment_id )
560
- {
561
- return Lib\Entities\Appointment::query( 'a' )
562
- ->leftJoin( 'CustomerAppointment', 'ca', 'ca.appointment_id = a.id' )
563
- ->whereNot( 'a.id', $appointment_id )
564
- ->where( 'a.staff_id', $staff_id )
565
- ->whereNot( 'ca.status', Lib\Entities\CustomerAppointment::STATUS_CANCELLED )
566
- ->whereRaw(
567
- '(start_date > %s AND start_date < %s OR (end_date > %s AND end_date < %s) OR (start_date < %s AND end_date > %s) OR (start_date = %s OR end_date = %s) )',
568
- array( $start_date, $end_date, $start_date, $end_date, $start_date, $end_date, $start_date, $end_date )
569
- )
570
- ->count() == 0;
571
- }
572
-
573
- /**
574
- * Override parent method to add 'wp_ajax_ab_' prefix
575
- * so current 'execute*' methods look nicer.
576
- *
577
- * @param string $prefix
578
- */
579
- protected function registerWpActions( $prefix = '' )
580
- {
581
- parent::registerWpActions( 'wp_ajax_ab_' );
582
- }
583
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
584
  }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Calendar;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Controller
8
+ * @package BooklyLite\Backend\Modules\Calendar
9
+ */
10
+ class Controller extends Lib\Base\Controller
11
+ {
12
+ const page_slug = 'bookly-calendar';
13
+
14
+ protected function getPermissions()
15
+ {
16
+ return array( '_this' => 'user' );
17
+ }
18
+
19
+ public function index()
20
+ {
21
+ /** @var \WP_Locale $wp_locale */
22
+ global $wp_locale;
23
+
24
+ $this->enqueueStyles( array(
25
+ 'module' => array( 'css/fullcalendar.min.css', ),
26
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
27
+ ) );
28
+
29
+ $this->enqueueScripts( array(
30
+ 'backend' => array( 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ), ),
31
+ 'module' => array(
32
+ 'js/fullcalendar.min.js' => array( 'bookly-moment.min.js' ),
33
+ 'js/fc-multistaff-view.js' => array( 'bookly-fullcalendar.min.js' ),
34
+ 'js/calendar.js' => array( 'bookly-fc-multistaff-view.js' ),
35
+ ),
36
+ ) );
37
+
38
+ $slot_length_minutes = get_option( 'bookly_gen_time_slot_length', '15' );
39
+ $slot = new \DateInterval( 'PT' . $slot_length_minutes . 'M' );
40
+
41
+ $staff_members = Lib\Utils\Common::isCurrentUserAdmin()
42
+ ? Lib\Entities\Staff::query()->sortBy( 'position' )->find()
43
+ : Lib\Entities\Staff::query()->where( 'wp_user_id', get_current_user_id() )->find();
44
+
45
+ wp_localize_script( 'bookly-calendar.js', 'BooklyL10n', array(
46
+ 'slotDuration' => $slot->format( '%H:%I:%S' ),
47
+ 'calendar' => array(
48
+ 'shortMonths' => array_values( $wp_locale->month_abbrev ),
49
+ 'longMonths' => array_values( $wp_locale->month ),
50
+ 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
51
+ 'longDays' => array_values( $wp_locale->weekday ),
52
+ ),
53
+ 'dpDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_JQUERY_DATEPICKER ),
54
+ 'mjsDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
55
+ 'mjsTimeFormat' => Lib\Utils\DateTime::convertFormat( 'time', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
56
+ 'today' => __( 'Today', 'bookly' ),
57
+ 'week' => __( 'Week', 'bookly' ),
58
+ 'day' => __( 'Day', 'bookly' ),
59
+ 'month' => __( 'Month', 'bookly' ),
60
+ 'allDay' => __( 'All Day', 'bookly' ),
61
+ 'delete' => __( 'Delete', 'bookly' ),
62
+ 'noStaffSelected' => __( 'No staff selected', 'bookly' ),
63
+ 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
64
+ 'startOfWeek' => (int) get_option( 'start_of_week' ),
65
+ 'recurring_appointments_enabled' => (int) Lib\Config::isRecurringAppointmentsEnabled(),
66
+ ) );
67
+
68
+ $this->render( 'calendar', compact( 'staff_members' ) );
69
+ }
70
+
71
+ /**
72
+ * Get data for FullCalendar.
73
+ *
74
+ * return string json
75
+ */
76
+ public function executeGetStaffAppointments()
77
+ {
78
+ $result = array();
79
+ $staff_members = array();
80
+ $one_day = new \DateInterval( 'P1D' );
81
+ $start_date = new \DateTime( $this->getParameter( 'start' ) );
82
+ $end_date = new \DateTime( $this->getParameter( 'end' ) );
83
+ // FullCalendar sends end date as 1 day further.
84
+ $end_date->sub( $one_day );
85
+
86
+ if ( Lib\Utils\Common::isCurrentUserAdmin() ) {
87
+ $staff_members = Lib\Entities\Staff::query()
88
+ ->where( 'id', 1 )
89
+ ->find();
90
+ } else {
91
+ $staff_members[] = Lib\Entities\Staff::query()
92
+ ->where( 'wp_user_id', get_current_user_id() )
93
+ ->findOne();
94
+ $staff_ids = array( 1 );
95
+ }
96
+ // Load special days.
97
+ $special_days = array();
98
+ foreach ( apply_filters( 'bookly_special_days_get_data_for_available_time', array(), $staff_ids, $start_date, $end_date ) as $day ) {
99
+ $special_days[ $day['staff_id'] ][ $day['date'] ][] = $day;
100
+ }
101
+
102
+ foreach ( $staff_members as $staff ) {
103
+ /** @var Lib\Entities\Staff $staff */
104
+ $result = array_merge( $result, $this->_getAppointmentsForFC( $staff->get( 'id' ), $start_date, $end_date ) );
105
+
106
+ // Schedule.
107
+ $items = $staff->getScheduleItems();
108
+ $day = clone $start_date;
109
+ // Find previous day end time.
110
+ $last_end = clone $day;
111
+ $last_end->sub( $one_day );
112
+ $w = (int) $day->format( 'w' );
113
+ $end_time = $items[ $w > 0 ? $w : 7 ]->get( 'end_time' );
114
+ if ( $end_time !== null ) {
115
+ $end_time = explode( ':', $end_time );
116
+ $last_end->setTime( $end_time[0], $end_time[1] );
117
+ } else {
118
+ $last_end->setTime( 24, 0 );
119
+ }
120
+ // Do the loop.
121
+ while ( $day <= $end_date ) {
122
+ $start = $last_end->format( 'Y-m-d H:i:s' );
123
+ // Check if $day is Special Day for current staff.
124
+ if ( isset( $special_days[ $staff->get( 'id' ) ][ $day->format( 'Y-m-d' ) ] ) ) {
125
+ $sp_days = $special_days[ $staff->get( 'id' ) ][ $day->format( 'Y-m-d' ) ];
126
+ $sp_day = array_shift( $sp_days );
127
+ $end = $sp_day['date'] . ' ' . $sp_day['start_time'];
128
+ if ( $start < $end ) {
129
+ $result[] = array(
130
+ 'start' => $start,
131
+ 'end' => $end,
132
+ 'rendering' => 'background',
133
+ 'staffId' => $staff->get( 'id' ),
134
+ );
135
+ // Check if the first break exists.
136
+ if ( isset( $sp_day['break_start'] ) ) {
137
+ $result[] = array(
138
+ 'start' => $sp_day['date'] . ' ' . $sp_day['break_start'],
139
+ 'end' => $sp_day['date'] . ' ' . $sp_day['break_end'],
140
+ 'rendering' => 'background',
141
+ 'staffId' => $staff->get( 'id' ),
142
+ );
143
+ }
144
+ }
145
+ // Breaks.
146
+ foreach ( $sp_days as $sp_day ) {
147
+ $result[] = array(
148
+ 'start' => $sp_day['date'] . ' ' . $sp_day['break_start'],
149
+ 'end' => $sp_day['date'] . ' ' . $sp_day['break_end'],
150
+ 'rendering' => 'background',
151
+ 'staffId' => $staff->get( 'id' ),
152
+ );
153
+ }
154
+ $end_time = explode( ':', $sp_day['end_time'] );
155
+ $last_end = clone $day;
156
+ $last_end->setTime( $end_time[0], $end_time[1] );
157
+ } else {
158
+ /** @var Lib\Entities\StaffScheduleItem $item */
159
+ $item = $items[ (int) $day->format( 'w' ) + 1 ];
160
+ if ( $item->get( 'start_time' ) && ! $staff->isOnHoliday( $day ) ) {
161
+ $end = $day->format( 'Y-m-d ' . $item->get( 'start_time' ) );
162
+ if ( $start < $end ) {
163
+ $result[] = array(
164
+ 'start' => $start,
165
+ 'end' => $end,
166
+ 'rendering' => 'background',
167
+ 'staffId' => $staff->get( 'id' ),
168
+ );
169
+ }
170
+ $last_end = clone $day;
171
+ $end_time = explode( ':', $item->get( 'end_time' ) );
172
+ $last_end->setTime( $end_time[0], $end_time[1] );
173
+
174
+ // Breaks.
175
+ foreach ( $item->getBreaksList() as $break ) {
176
+ $result[] = array(
177
+ 'start' => $day->format( 'Y-m-d ' . $break['start_time'] ),
178
+ 'end' => $day->format( 'Y-m-d ' . $break['end_time'] ),
179
+ 'rendering' => 'background',
180
+ 'staffId' => $staff->get( 'id' ),
181
+ );
182
+ }
183
+ } else {
184
+ $result[] = array(
185
+ 'start' => $last_end->format( 'Y-m-d H:i:s' ),
186
+ 'end' => $day->format( 'Y-m-d 24:00:00' ),
187
+ 'rendering' => 'background',
188
+ 'staffId' => $staff->get( 'id' ),
189
+ );
190
+ $last_end = clone $day;
191
+ $last_end->setTime( 24, 0 );
192
+ }
193
+ }
194
+
195
+ $day->add( $one_day );
196
+ }
197
+
198
+ if ( $last_end->format( 'H' ) != 24 ) {
199
+ $result[] = array(
200
+ 'start' => $last_end->format( 'Y-m-d H:i:s' ),
201
+ 'end' => $last_end->format( 'Y-m-d 24:00:00' ),
202
+ 'rendering' => 'background',
203
+ 'staffId' => $staff->get( 'id' ),
204
+ );
205
+ }
206
+ }
207
+
208
+ wp_send_json( $result );
209
+ }
210
+
211
+ /**
212
+ * Get data needed for appointment form initialisation.
213
+ */
214
+ public function executeGetDataForAppointmentForm()
215
+ {
216
+ $result = array(
217
+ 'staff' => array(),
218
+ 'customers' => array(),
219
+ 'start_time' => array(),
220
+ 'end_time' => array(),
221
+ 'time_interval' => Lib\Config::getTimeSlotLength(),
222
+ 'status' => array(
223
+ 'items' => array(
224
+ 'pending' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_PENDING ),
225
+ 'approved' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_APPROVED ),
226
+ 'cancelled' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_CANCELLED ),
227
+ 'rejected' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_REJECTED ),//@todo I didn't find the place it uses
228
+ ),
229
+ 'default' => get_option( 'bookly_gen_default_appointment_status' ),
230
+ ),
231
+ );
232
+
233
+ // Staff list.
234
+ $staff_members = Lib\Utils\Common::isCurrentUserAdmin()
235
+ ? Lib\Entities\Staff::query()->sortBy( 'position' )->find()
236
+ : Lib\Entities\Staff::query()->where( 'wp_user_id', get_current_user_id() )->find();
237
+
238
+ /** @var Lib\Entities\Staff $staff_member */
239
+ foreach ( $staff_members as $staff_member ) {
240
+ $services = array();
241
+ foreach ( $staff_member->getStaffServices() as $staff_service ) {
242
+ $services[] = array(
243
+ 'id' => $staff_service->service->get( 'id' ),
244
+ 'title' => sprintf(
245
+ '%s (%s)',
246
+ $staff_service->service->get( 'title' ),
247
+ Lib\Utils\DateTime::secondsToInterval( $staff_service->service->get( 'duration' ) )
248
+ ),
249
+ 'duration' => $staff_service->service->get( 'duration' ),
250
+ 'capacity' => $staff_service->get( 'capacity' )
251
+ );
252
+ }
253
+ $result['staff'][] = array(
254
+ 'id' => $staff_member->get( 'id' ),
255
+ 'full_name' => $staff_member->get( 'full_name' ),
256
+ 'services' => $services
257
+ );
258
+ }
259
+
260
+ // Customers list.
261
+ foreach ( Lib\Entities\Customer::query()->sortBy( 'name' )->find() as $customer ) {
262
+ $name = $customer->get( 'name' );
263
+ if ( $customer->get( 'email' ) != '' || $customer->get( 'phone' ) != '' ) {
264
+ $name .= ' (' . trim( $customer->get( 'email' ) . ', ' . $customer->get( 'phone' ) , ', ' ) . ')';
265
+ }
266
+
267
+ $result['customers'][] = array(
268
+ 'id' => $customer->get( 'id' ),
269
+ 'name' => $name,
270
+ 'custom_fields' => array(),
271
+ 'number_of_persons' => 1,
272
+ );
273
+ }
274
+
275
+ // Time list.
276
+ $ts_length = Lib\Config::getTimeSlotLength();
277
+ $time_start = 0;
278
+ $time_end = DAY_IN_SECONDS * 2;
279
+
280
+ // Run the loop.
281
+ while ( $time_start <= $time_end ) {
282
+ $slot = array(
283
+ 'value' => Lib\Utils\DateTime::buildTimeString( $time_start, false ),
284
+ 'title' => Lib\Utils\DateTime::formatTime( $time_start )
285
+ );
286
+ if ( $time_start < DAY_IN_SECONDS ) {
287
+ $result['start_time'][] = $slot;
288
+ }
289
+ $result['end_time'][] = $slot;
290
+ $time_start += $ts_length;
291
+ }
292
+
293
+ wp_send_json( $result );
294
+ }
295
+
296
+ /**
297
+ * Get appointment data when editing an appointment.
298
+ */
299
+ public function executeGetDataForAppointment()
300
+ {
301
+ $response = array( 'success' => false, 'data' => array( 'customers' => array() ) );
302
+
303
+ $appointment = new Lib\Entities\Appointment();
304
+ if ( $appointment->load( $this->getParameter( 'id' ) ) ) {
305
+ $response['success'] = true;
306
+
307
+ $info = Lib\Entities\Appointment::query( 'a' )
308
+ ->select( 'ss.capacity AS max_capacity, SUM( ca.number_of_persons ) AS total_number_of_persons, a.staff_id, a.service_id, a.start_date, a.end_date, a.internal_note' )
309
+ ->leftJoin( 'CustomerAppointment', 'ca', 'ca.appointment_id = a.id' )
310
+ ->leftJoin( 'StaffService', 'ss', 'ss.staff_id = a.staff_id AND ss.service_id = a.service_id' )
311
+ ->where( 'a.id', $appointment->get( 'id' ) )
312
+ ->fetchRow();
313
+
314
+ $response['data']['total_number_of_persons'] = $info['total_number_of_persons'];
315
+ $response['data']['max_capacity'] = $info['max_capacity'];
316
+ $response['data']['start_date'] = $info['start_date'];
317
+ $response['data']['end_date'] = $info['end_date'];
318
+ $response['data']['staff_id'] = $info['staff_id'];
319
+ $response['data']['service_id'] = $info['service_id'];
320
+ $response['data']['internal_note'] = $info['internal_note'];
321
+ $customers = Lib\Entities\CustomerAppointment::query( 'ca' )
322
+ ->select( 'ca.id,
323
+ ca.customer_id,
324
+ ca.custom_fields,
325
+ ca.extras,
326
+ ca.number_of_persons,
327
+ ca.status,
328
+ ca.payment_id,
329
+ ca.compound_service_id,
330
+ ca.compound_token,
331
+ ca.location_id,
332
+ p.paid AS payment,
333
+ p.total AS payment_total,
334
+ p.type AS payment_type,
335
+ p.details AS payment_details,
336
+ p.status AS payment_status' )
337
+ ->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
338
+ ->where( 'ca.appointment_id', $appointment->get( 'id' ) )
339
+ ->fetchArray();
340
+ foreach ( $customers as $customer ) {
341
+ $payment_title = '';
342
+ if ( $customer['payment'] !== null ) {
343
+ $payment_title = Lib\Utils\Common::formatPrice( $customer['payment'] );
344
+ if ( $customer['payment'] != $customer['payment_total'] ) {
345
+ $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Common::formatPrice( $customer['payment_total'] ) );
346
+ }
347
+ $payment_title .= sprintf(
348
+ ' %s <span%s>%s</span>',
349
+ Lib\Entities\Payment::typeToString( $customer['payment_type'] ),
350
+ $customer['payment_status'] == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
351
+ Lib\Entities\Payment::statusToString( $customer['payment_status'] )
352
+ );
353
+ }
354
+ $compound_service = '';
355
+ if ( $customer['compound_service_id'] !== null ) {
356
+ $service = new Lib\Entities\Service();
357
+ if ( $service->load( $customer['compound_service_id'] ) ) {
358
+ $compound_service = $service->getTitle();
359
+ }
360
+ }
361
+ $response['data']['customers'][] = array(
362
+ 'id' => $customer['customer_id'],
363
+ 'ca_id' => $customer['id'],
364
+ 'compound_service' => $compound_service,
365
+ 'compound_token' => $customer['compound_token'],
366
+ 'custom_fields' => (array) json_decode( $customer['custom_fields'], true ),
367
+ 'extras' => (array) json_decode( $customer['extras'], true ),
368
+ 'location_id' => $customer['location_id'],
369
+ 'number_of_persons' => $customer['number_of_persons'],
370
+ 'payment_id' => $customer['payment_id'],
371
+ 'payment_type' => $customer['payment'] != $customer['payment_total'] ? 'partial' : 'full',
372
+ 'payment_title' => $payment_title,
373
+ 'status' => $customer['status'],
374
+ );
375
+ }
376
+ }
377
+ wp_send_json( $response );
378
+ }
379
+
380
+ /**
381
+ * Save appointment form (for both create and edit).
382
+ */
383
+ public function executeSaveAppointmentForm()
384
+ {
385
+ $response = array( 'success' => false );
386
+
387
+ $internal_note = $this->getParameter( 'internal_note' );
388
+ $start_date = $this->getParameter( 'start_date' );
389
+ $end_date = $this->getParameter( 'end_date' );
390
+ $service_id = $this->getParameter( 'service_id' );
391
+ $appointment_id = $this->getParameter( 'id', 0 );
392
+ $repeat = json_decode( $this->getParameter( 'repeat', '[]' ), true );
393
+ $schedule = $this->getParameter( 'schedule', array() );
394
+ $customers = json_decode( $this->getParameter( 'customers', '[]' ), true );
395
+
396
+ $staff_service = new Lib\Entities\StaffService();
397
+ $staff_service->loadBy( array(
398
+ 'staff_id' => 1,
399
+ 'service_id' => $service_id
400
+ ) );
401
+
402
+ // Check for errors.
403
+ if ( ! $start_date ) {
404
+ $response['errors']['time_interval'] = __( 'Start time must not be empty', 'bookly' );
405
+ } elseif ( ! $end_date ) {
406
+ $response['errors']['time_interval'] = __( 'End time must not be empty', 'bookly' );
407
+ } elseif ( $start_date == $end_date ) {
408
+ $response['errors']['time_interval'] = __( 'End time must not be equal to start time', 'bookly' );
409
+ }
410
+ if ( ! $service_id ) {
411
+ $response['errors']['service_required'] = true;
412
+ }
413
+ if ( empty ( $customers ) ) {
414
+ $response['errors']['customers_required'] = true;
415
+ }
416
+ $total_number_of_persons = 0;
417
+ $max_extras_duration = 0;
418
+ foreach ( $customers as $customer ) {
419
+ if (
420
+ ( $customer['status'] != Lib\Entities\CustomerAppointment::STATUS_CANCELLED )
421
+ && ( $customer['status'] != Lib\Entities\CustomerAppointment::STATUS_REJECTED )
422
+ ) {
423
+ $total_number_of_persons += $customer['number_of_persons'];
424
+ $extras_duration = apply_filters( 'bookly_service_extras_get_total_duration', 0, $customer['extras'] );
425
+ if ( $extras_duration > $max_extras_duration ) {
426
+ $max_extras_duration = $extras_duration;
427
+ }
428
+ }
429
+ }
430
+ if ( $total_number_of_persons > $staff_service->get( 'capacity' ) ) {
431
+ $response['errors']['overflow_capacity'] = sprintf(
432
+ __( 'The number of customers should not be more than %d', 'bookly' ),
433
+ $staff_service->get( 'capacity' )
434
+ );
435
+ }
436
+ $total_end_date = $end_date;
437
+ if ( $max_extras_duration > 0 ) {
438
+ $total_end_date = date_create( $end_date )->modify( '+' . $max_extras_duration . ' sec' )->format( 'Y-m-d H:i:s' );
439
+ }
440
+ if ( ! $this->dateIntervalIsAvailableForAppointment( $start_date, $total_end_date, 1, $appointment_id ) ) {
441
+ $response['errors']['date_interval_not_available'] = true;
442
+ }
443
+ $notification = $this->getParameter( 'notification' );
444
+
445
+ // If no errors then try to save the appointment.
446
+ if ( ! isset ( $response['errors'] ) ) {
447
+ if ( $repeat['enabled'] ) {
448
+ if ( ! empty ( $schedule ) ) {
449
+ $recurring_list = array( 'customers' => $customers, 'appointments' => array() );
450
+ // Create new series.
451
+ $series = new Lib\Entities\Series();
452
+ $series
453
+ ->set( 'repeat', $this->getParameter( 'repeat' ) )
454
+ ->set( 'token', Lib\Utils\Common::generateToken( get_class( $series ), 'token' ) )
455
+ ->save();
456
+
457
+ $service = new Lib\Entities\Service();
458
+ $service->load( $service_id );
459
+
460
+ foreach ( $schedule as $slot ) {
461
+ $slot = json_decode( $slot );
462
+ $appointment = new Lib\Entities\Appointment();
463
+ $appointment
464
+ ->set( 'series_id', $series->get( 'id' ) )
465
+ ->set( 'staff_id', 1 )
466
+ ->set( 'service_id', $service_id )
467
+ ->set( 'start_date', date( 'Y-m-d H:i:s', $slot[0][2] ) )
468
+ ->set( 'end_date', date( 'Y-m-d H:i:s', $slot[0][2] + $service->get( 'duration' ) ) )
469
+ ->set( 'internal_note', $internal_note )
470
+ ->set( 'extras_duration', $max_extras_duration )
471
+ ;
472
+
473
+ if ( $appointment->save() !== false ) {
474
+ // Google Calendar.
475
+ $appointment->handleGoogleCalendar();
476
+ $appointment->saveCustomerAppointments( $customers );
477
+
478
+ if ( $notification != 'no' ) {
479
+ // Collect all appointments for sending recurring notification
480
+ $recurring_list['appointments'][] = $appointment ;
481
+ }
482
+ }
483
+ }
484
+ Lib\NotificationSender::sendRecurring( $recurring_list );
485
+ }
486
+ $response['success'] = true;
487
+ $response['data'] = array( 'staffId' => 1 ); // make FullCalendar refetch events
488
+ } else {
489
+ $appointment = new Lib\Entities\Appointment();
490
+ if ( $appointment_id ) {
491
+ // Edit.
492
+ $appointment->load( $appointment_id );
493
+ }
494
+ $appointment->set( 'start_date', $start_date );
495
+ $appointment->set( 'end_date', $end_date );
496
+ $appointment->set( 'staff_id', 1 );
497
+ $appointment->set( 'service_id', $service_id );
498
+ $appointment->set( 'internal_note', $internal_note );
499
+ $appointment->set( 'extras_duration', $max_extras_duration );
500
+
501
+ if ( $appointment->save() !== false ) {
502
+ // Save customer appointments.
503
+ $ca_status_changed = $appointment->saveCustomerAppointments( $customers );
504
+ $customer_appointments = $appointment->getCustomerAppointments( true );
505
+
506
+ // Google Calendar.
507
+ $appointment->handleGoogleCalendar();
508
+
509
+ // Send notifications.
510
+ if ( $notification != 'no' ) {
511
+ foreach ( $customer_appointments as $ca ) {
512
+ // Send notification.
513
+ if ( $notification == 'all' || in_array( $ca->get( 'id' ), $ca_status_changed ) ) {
514
+ Lib\NotificationSender::send( $ca );
515
+ }
516
+ }
517
+ }
518
+
519
+ $response['success'] = true;
520
+ $response['data'] = $this->_getAppointmentForFC( 1, $appointment->get( 'id' ) );
521
+ } else {
522
+ $response['errors'] = array( 'db' => __( 'Could not save appointment in database.', 'bookly' ) );
523
+ }
524
+ }
525
+ }
526
+
527
+ wp_send_json( $response );
528
+ }
529
+
530
+ public function executeCheckAppointmentDateSelection()
531
+ {
532
+ $start_date = $this->getParameter( 'start_date' );
533
+ $end_date = $this->getParameter( 'end_date' );
534
+ $service_id = $this->getParameter( 'service_id' );
535
+ $appointment_id = $this->getParameter( 'appointment_id' );
536
+ $timestamp_diff = strtotime( $end_date ) - strtotime( $start_date );
537
+
538
+ $result = array(
539
+ 'date_interval_not_available' => false,
540
+ 'date_interval_warning' => false,
541
+ );
542
+
543
+ if ( ! $this->dateIntervalIsAvailableForAppointment( $start_date, $end_date, 1, $appointment_id ) ) {
544
+ $result['date_interval_not_available'] = true;
545
+ }
546
+
547
+ if ( $service_id ) {
548
+ $service = new Lib\Entities\Service();
549
+ $service->load( $service_id );
550
+
551
+ $duration = $service->get( 'duration' );
552
+
553
+ // Service duration interval is not equal to.
554
+ $result['date_interval_warning'] = ( $timestamp_diff != $duration );
555
+ }
556
+
557
+ wp_send_json( $result );
558
+ }
559
+
560
+ /**
561
+ * Delete single appointment.
562
+ */
563
+ public function executeDeleteAppointment()
564
+ {
565
+ $appointment_id = $this->getParameter( 'appointment_id' );
566
+ $reason = $this->getParameter( 'reason' );
567
+
568
+ if ( $this->getParameter( 'notify' ) ) {
569
+ $ca_list = Lib\Entities\CustomerAppointment::query()
570
+ ->where( 'appointment_id', $appointment_id )
571
+ ->find();
572
+ /** @var Lib\Entities\CustomerAppointment $ca */
573
+ foreach ( $ca_list as $ca ) {
574
+ switch ( $ca->get('status') ) {
575
+ case Lib\Entities\CustomerAppointment::STATUS_PENDING:
576
+ $ca->set( 'status', Lib\Entities\CustomerAppointment::STATUS_REJECTED );
577
+ break;
578
+ case Lib\Entities\CustomerAppointment::STATUS_APPROVED:
579
+ $ca->set( 'status', Lib\Entities\CustomerAppointment::STATUS_CANCELLED );
580
+ break;
581
+ }
582
+ Lib\NotificationSender::send( $ca, array( 'cancellation_reason' => $reason ) );
583
+ }
584
+ }
585
+
586
+ $appointment = new Lib\Entities\Appointment();
587
+ $appointment->load( $appointment_id );
588
+ $appointment->delete();
589
+
590
+ wp_send_json_success();
591
+ }
592
+
593
+ /**
594
+ * @param $start_date
595
+ * @param $end_date
596
+ * @param $staff_id
597
+ * @param $appointment_id
598
+ * @return bool
599
+ */
600
+ private function dateIntervalIsAvailableForAppointment( $start_date, $end_date, $staff_id, $appointment_id )
601
+ {
602
+ return Lib\Entities\Appointment::query( 'a' )
603
+ ->leftJoin( 'CustomerAppointment', 'ca', 'ca.appointment_id = a.id' )
604
+ ->whereNot( 'a.id', $appointment_id )
605
+ ->where( 'a.staff_id', $staff_id )
606
+ ->whereNot( 'ca.status', Lib\Entities\CustomerAppointment::STATUS_CANCELLED )
607
+ ->whereNot( 'ca.status', Lib\Entities\CustomerAppointment::STATUS_REJECTED )
608
+ ->whereLt( 'start_date', $end_date )
609
+ ->whereGt( 'end_date', $start_date )
610
+ ->count() == 0;
611
+ }
612
+
613
+ /**
614
+ * Get appointments for FullCalendar.
615
+ *
616
+ * @param integer $staff_id
617
+ * @param \DateTime $start_date
618
+ * @param \DateTime $end_date
619
+ * @return array
620
+ */
621
+ private function _getAppointmentsForFC( $staff_id, \DateTime $start_date, \DateTime $end_date )
622
+ {
623
+ $query = Lib\Entities\Appointment::query( 'a' )
624
+ ->where( 'st.id', $staff_id )
625
+ ->whereBetween( 'DATE(a.start_date)', $start_date->format( 'Y-m-d' ), $end_date->format( 'Y-m-d' ) );
626
+
627
+ return $this->_buildAppointmentsForFC( $staff_id, $query );
628
+ }
629
+
630
+ /**
631
+ * Get appointment for FullCalendar.
632
+ *
633
+ * @param integer $staff_id
634
+ * @param int $appointment_id
635
+ * @return array
636
+ */
637
+ private function _getAppointmentForFC( $staff_id, $appointment_id )
638
+ {
639
+ $query = Lib\Entities\Appointment::query( 'a' )
640
+ ->where( 'a.id', $appointment_id );
641
+
642
+ $appointments = $this->_buildAppointmentsForFC( $staff_id, $query );
643
+
644
+ return $appointments[0];
645
+ }
646
+
647
+ /**
648
+ * Build appointments for FullCalendar.
649
+ *
650
+ * @param integer $staff_id
651
+ * @param Lib\Query $query
652
+ * @return mixed
653
+ */
654
+ private function _buildAppointmentsForFC( $staff_id, Lib\Query $query )
655
+ {
656
+ $appointments = $query
657
+ ->select( 'a.id, a.series_id, a.start_date, DATE_ADD(a.end_date, INTERVAL a.extras_duration SECOND) AS end_date,
658
+ s.title AS service_title, s.color AS service_color,
659
+ ss.capacity AS max_capacity,
660
+ (SELECT SUM(ca.number_of_persons) FROM ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca WHERE ca.appointment_id = a.id) AS total_number_of_persons,
661
+ ca.number_of_persons,
662
+ ca.custom_fields,
663
+ ca.status AS appointment_status,
664
+ ca.extras,
665
+ ca.location_id,
666
+ c.name AS customer_name, c.phone AS customer_phone, c.email AS customer_email, c.id AS customer_id,
667
+ p.total,
668
+ p.type AS payment_gateway,
669
+ p.status AS payment_status' )
670
+ ->leftJoin( 'CustomerAppointment', 'ca', 'ca.appointment_id = a.id' )
671
+ ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
672
+ ->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
673
+ ->leftJoin( 'Service', 's', 's.id = a.service_id' )
674
+ ->leftJoin( 'Staff', 'st', 'st.id = a.staff_id' )
675
+ ->leftJoin( 'StaffService', 'ss', 'ss.staff_id = a.staff_id AND ss.service_id = a.service_id' )
676
+ ->where( 'st.id', 1 )
677
+ ->groupBy( 'a.id' )
678
+ ->fetchArray();
679
+
680
+ foreach ( $appointments as $key => $appointment ) {
681
+ $desc = '';
682
+ // Display customer information only if there is 1 customer. Don't confuse with number_of_persons.
683
+ if ( $appointment['number_of_persons'] == $appointment['total_number_of_persons'] ) {
684
+ // Customer information.
685
+ foreach ( array( 'customer_name', 'customer_phone', 'customer_email' ) as $data_entry ) {
686
+ if ( $appointment[ $data_entry ] ) {
687
+ $desc .= '<div>' . esc_html( $appointment[ $data_entry ] ) . '</div>';
688
+ }
689
+ }
690
+ $desc = apply_filters( 'bookly_prepare_calendar_appointment_description', $desc, $appointment );
691
+ // Custom fields.
692
+ if ( $appointment['custom_fields'] != '[]' ) {
693
+ $desc .= '<br/>';
694
+ $ca = new Lib\Entities\CustomerAppointment();
695
+ $ca->set( 'custom_fields', $appointment['custom_fields'] );
696
+ $ca->set( 'appointment_id', $appointment['id'] );
697
+ foreach ( $ca->getCustomFields() as $custom_field ) {
698
+ $desc .= sprintf( '<div>%s: %s</div>', wp_strip_all_tags( $custom_field['label'] ), nl2br( esc_html( $custom_field['value'] ) ) );
699
+ }
700
+ }
701
+ // Payment.
702
+ if ( $appointment['total'] ) {
703
+ $desc .= sprintf(
704
+ '<br/><div>%s: %s %s %s</div>',
705
+ __( 'Payment', 'bookly' ),
706
+ Lib\Utils\Common::formatPrice( $appointment['total'] ),
707
+ Lib\Entities\Payment::typeToString( $appointment['payment_gateway'] ),
708
+ Lib\Entities\Payment::statusToString( $appointment['payment_status'] )
709
+ );
710
+ }
711
+ // Status.
712
+ $desc .= sprintf( '<br/><div>%s: %s</div>', __( 'Status', 'bookly' ), Lib\Entities\CustomerAppointment::statusToString( $appointment['appointment_status'] ) );
713
+ // Signed up & Capacity.
714
+ if ( $appointment['max_capacity'] > 1 ) {
715
+ $desc .= sprintf( '<br/><div>%s: %d</div>', __( 'Signed up', 'bookly' ), $appointment['total_number_of_persons'] );
716
+ $desc .= sprintf( '<div>%s: %d</div>', __( 'Capacity', 'bookly' ), $appointment['max_capacity'] );
717
+ }
718
+ } else {
719
+ $desc .= sprintf( '<div>%s: %d</div>', __( 'Signed up', 'bookly' ), $appointment['total_number_of_persons'] );
720
+ $desc .= sprintf( '<div>%s: %d</div>', __( 'Capacity', 'bookly' ), $appointment['max_capacity'] );
721
+ }
722
+ $appointments[ $key ] = array(
723
+ 'id' => $appointment['id'],
724
+ 'series_id' => (int) $appointment['series_id'],
725
+ 'start' => $appointment['start_date'],
726
+ 'end' => $appointment['end_date'],
727
+ 'title' => $appointment['service_title'] ? esc_html( $appointment['service_title'] ) : __( 'Untitled', 'bookly' ),
728
+ 'desc' => $desc,
729
+ 'color' => $appointment['service_color'],
730
+ 'staffId' => 1,
731
+ );
732
+ }
733
+
734
+ return $appointments;
735
+ }
736
+
737
+ /**
738
+ * Override parent method to add 'wp_ajax_bookly_' prefix
739
+ * so current 'execute*' methods look nicer.
740
+ *
741
+ * @param string $prefix
742
+ */
743
+ protected function registerWpActions( $prefix = '' )
744
+ {
745
+ parent::registerWpActions( 'wp_ajax_bookly_' );
746
+ }
747
+
748
  }
backend/modules/calendar/resources/css/fullcalendar.min.css CHANGED
@@ -1,5 +1,5 @@
1
- /*!
2
- * FullCalendar v2.4.0 Stylesheet
3
- * Docs & License: http://fullcalendar.io/
4
- * (c) 2015 Adam Shaw
5
  */.fc{direction:ltr;text-align:left}.fc-rtl{text-align:right}body .fc{font-size:1em}.fc-unthemed .fc-divider,.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#ddd}.fc-unthemed .fc-popover{background-color:#fff}.fc-unthemed .fc-divider,.fc-unthemed .fc-popover .fc-header{background:#eee}.fc-unthemed .fc-popover .fc-header .fc-close{color:#666}.fc-unthemed .fc-today{background:#fcf8e3}.fc-highlight{background:#bce8f1;opacity:.3;filter:alpha(opacity=30)}.fc-bgevent{background:#8fdf82;opacity:.3;filter:alpha(opacity=30)}.fc-nonbusiness{background:#d7d7d7}.fc-icon{display:inline-block;width:1em;height:1em;line-height:1em;font-size:1em;text-align:center;overflow:hidden;font-family:"Courier New",Courier,monospace;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fc-icon:after{position:relative;margin:0 -1em}.fc-icon-left-single-arrow:after{content:"\02039";font-weight:700;font-size:200%;top:-7%;left:3%}.fc-icon-right-single-arrow:after{content:"\0203A";font-weight:700;font-size:200%;top:-7%;left:-3%}.fc-icon-left-double-arrow:after{content:"\000AB";font-size:160%;top:-7%}.fc-icon-right-double-arrow:after{content:"\000BB";font-size:160%;top:-7%}.fc-icon-left-triangle:after{content:"\25C4";font-size:125%;top:3%;left:-2%}.fc-icon-right-triangle:after{content:"\25BA";font-size:125%;top:3%;left:2%}.fc-icon-down-triangle:after{content:"\25BC";font-size:125%;top:2%}.fc-icon-x:after{content:"\000D7";font-size:200%;top:6%}.fc button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;height:2.1em;padding:0 .6em;font-size:1em;white-space:nowrap;cursor:pointer}.fc button::-moz-focus-inner{margin:0;padding:0}.fc-state-default{border:1px solid}.fc-state-default.fc-corner-left{border-top-left-radius:4px;border-bottom-left-radius:4px}.fc-state-default.fc-corner-right{border-top-right-radius:4px;border-bottom-right-radius:4px}.fc button .fc-icon{position:relative;top:-.05em;margin:0 .2em;vertical-align:middle}.fc-state-default{background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);color:#333;text-shadow:0 1px 1px rgba(255,255,255,.75);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.fc-state-active,.fc-state-disabled,.fc-state-down,.fc-state-hover{color:#333;background-color:#e6e6e6}.fc-state-hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.fc-state-active,.fc-state-down{background-color:#ccc;background-image:none;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.fc-state-disabled{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);box-shadow:none}.fc-button-group{display:inline-block}.fc .fc-button-group>*{float:left;margin:0 0 0 -1px}.fc .fc-button-group>:first-child{margin-left:0}.fc-popover{position:absolute;box-shadow:0 2px 6px rgba(0,0,0,.15)}.fc-popover .fc-header{padding:2px 4px}.fc-popover .fc-header .fc-title{margin:0 2px}.fc-popover .fc-header .fc-close{cursor:pointer}.fc-ltr .fc-popover .fc-header .fc-title,.fc-rtl .fc-popover .fc-header .fc-close{float:left}.fc-ltr .fc-popover .fc-header .fc-close,.fc-rtl .fc-popover .fc-header .fc-title{float:right}.fc-unthemed .fc-popover{border-width:1px;border-style:solid}.fc-unthemed .fc-popover .fc-header .fc-close{font-size:.9em;margin-top:2px}.fc-popover>.ui-widget-header+.ui-widget-content{border-top:0}.fc-divider{border-style:solid;border-width:1px}hr.fc-divider{height:0;margin:0;padding:0 0 2px;border-width:1px 0}.fc-clear{clear:both}.fc-bg,.fc-bgevent-skeleton,.fc-helper-skeleton,.fc-highlight-skeleton{position:absolute;top:0;left:0;right:0}.fc-bg{bottom:0}.fc-bg table{height:100%}.fc table{width:100%;table-layout:fixed;border-collapse:collapse;border-spacing:0;font-size:1em}.fc th{text-align:center}.fc td,.fc th{border-style:solid;border-width:1px;padding:0;vertical-align:top}.fc td.fc-today{border-style:double}.fc .fc-row{border-style:solid;border-width:0}.fc-row table{border-left:0 hidden transparent;border-right:0 hidden transparent;border-bottom:0 hidden transparent}.fc-row:first-child table{border-top:0 hidden transparent}.fc-row{position:relative}.fc-row .fc-bg{z-index:1}.fc-row .fc-bgevent-skeleton,.fc-row .fc-highlight-skeleton{bottom:0}.fc-row .fc-bgevent-skeleton table,.fc-row .fc-highlight-skeleton table{height:100%}.fc-row .fc-bgevent-skeleton td,.fc-row .fc-highlight-skeleton td{border-color:transparent}.fc-row .fc-bgevent-skeleton{z-index:2}.fc-row .fc-highlight-skeleton{z-index:3}.fc-row .fc-content-skeleton{position:relative;z-index:4;padding-bottom:2px}.fc-row .fc-helper-skeleton{z-index:5}.fc-row .fc-content-skeleton td,.fc-row .fc-helper-skeleton td{background:0 0;border-color:transparent;border-bottom:0}.fc-row .fc-content-skeleton tbody td,.fc-row .fc-helper-skeleton tbody td{border-top:0}.fc-scroller{overflow-y:scroll;overflow-x:hidden}.fc-scroller>*{position:relative;width:100%;overflow:hidden}.fc-event{position:relative;display:block;font-size:.85em;line-height:1.3;border-radius:3px;border:1px solid #3a87ad;background-color:#3a87ad;font-weight:400}.fc-event,.fc-event:hover,.ui-widget .fc-event{color:#fff;text-decoration:none}.fc-event.fc-draggable,.fc-event[href]{cursor:pointer}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc-event .fc-bg{z-index:1;background:#fff;opacity:.25;filter:alpha(opacity=25)}.fc-event .fc-content{position:relative;z-index:2}.fc-event .fc-resizer{position:absolute;z-index:3}.fc-ltr .fc-h-event.fc-not-start,.fc-rtl .fc-h-event.fc-not-end{margin-left:0;border-left-width:0;padding-left:1px;border-top-left-radius:0;border-bottom-left-radius:0}.fc-ltr .fc-h-event.fc-not-end,.fc-rtl .fc-h-event.fc-not-start{margin-right:0;border-right-width:0;padding-right:1px;border-top-right-radius:0;border-bottom-right-radius:0}.fc-h-event .fc-resizer{top:-1px;bottom:-1px;left:-1px;right:-1px;width:5px}.fc-ltr .fc-h-event .fc-start-resizer,.fc-ltr .fc-h-event .fc-start-resizer:after,.fc-ltr .fc-h-event .fc-start-resizer:before,.fc-rtl .fc-h-event .fc-end-resizer,.fc-rtl .fc-h-event .fc-end-resizer:after,.fc-rtl .fc-h-event .fc-end-resizer:before{right:auto;cursor:w-resize}.fc-ltr .fc-h-event .fc-end-resizer,.fc-ltr .fc-h-event .fc-end-resizer:after,.fc-ltr .fc-h-event .fc-end-resizer:before,.fc-rtl .fc-h-event .fc-start-resizer,.fc-rtl .fc-h-event .fc-start-resizer:after,.fc-rtl .fc-h-event .fc-start-resizer:before{left:auto;cursor:e-resize}.fc-day-grid-event{margin:1px 2px 0;padding:0 1px}.fc-day-grid-event .fc-content{white-space:nowrap;overflow:hidden}.fc-day-grid-event .fc-time{font-weight:700}.fc-day-grid-event .fc-resizer{left:-3px;right:-3px;width:7px}a.fc-more{margin:1px 3px;font-size:.85em;cursor:pointer;text-decoration:none}a.fc-more:hover{text-decoration:underline}.fc-limited{display:none}.fc-day-grid .fc-row{z-index:1}.fc-more-popover{z-index:2;width:220px}.fc-more-popover .fc-event-container{padding:10px}.fc-toolbar{text-align:center;margin-bottom:1em}.fc-toolbar .fc-left{float:left}.fc-toolbar .fc-right{float:right}.fc-toolbar .fc-center{display:inline-block}.fc .fc-toolbar>*>*{float:left;margin-left:.75em}.fc .fc-toolbar>*>:first-child{margin-left:0}.fc-toolbar h2{margin:0}.fc-toolbar button{position:relative}.fc-toolbar .fc-state-hover,.fc-toolbar .ui-state-hover{z-index:2}.fc-toolbar .fc-state-down{z-index:3}.fc-toolbar .fc-state-active,.fc-toolbar .ui-state-active{z-index:4}.fc-toolbar button:focus{z-index:5}.fc-view-container *,.fc-view-container :after,.fc-view-container :before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fc-view,.fc-view>table{position:relative;z-index:1}.fc-basicDay-view .fc-content-skeleton,.fc-basicWeek-view .fc-content-skeleton{padding-top:1px;padding-bottom:1em}.fc-basic-view .fc-body .fc-row{min-height:4em}.fc-row.fc-rigid{overflow:hidden}.fc-row.fc-rigid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-basic-view .fc-day-number,.fc-basic-view .fc-week-number{padding:0 2px}.fc-basic-view td.fc-day-number,.fc-basic-view td.fc-week-number span{padding-top:2px;padding-bottom:2px}.fc-basic-view .fc-week-number{text-align:center}.fc-basic-view .fc-week-number span{display:inline-block;min-width:1.25em}.fc-ltr .fc-basic-view .fc-day-number{text-align:right}.fc-rtl .fc-basic-view .fc-day-number{text-align:left}.fc-day-number.fc-other-month{opacity:.3;filter:alpha(opacity=30)}.fc-agenda-view .fc-day-grid{position:relative;z-index:2}.fc-agenda-view .fc-day-grid .fc-row{min-height:3em}.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton{padding-top:1px;padding-bottom:1em}.fc .fc-axis{vertical-align:middle;padding:0 4px;white-space:nowrap}.fc-ltr .fc-axis{text-align:right}.fc-rtl .fc-axis{text-align:left}.ui-widget td.fc-axis{font-weight:400}.fc-time-grid,.fc-time-grid-container{position:relative;z-index:1}.fc-time-grid{min-height:100%}.fc-time-grid table{border:0 hidden transparent}.fc-time-grid>.fc-bg{z-index:1}.fc-time-grid .fc-slats,.fc-time-grid>hr{position:relative;z-index:2}.fc-time-grid .fc-bgevent-skeleton,.fc-time-grid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-time-grid .fc-bgevent-skeleton{z-index:3}.fc-time-grid .fc-highlight-skeleton{z-index:4}.fc-time-grid .fc-content-skeleton{z-index:5}.fc-time-grid .fc-helper-skeleton{z-index:6}.fc-time-grid .fc-slats td{height:1.5em;border-bottom:0}.fc-time-grid .fc-slats .fc-minor td{border-top-style:dotted}.fc-time-grid .fc-slats .ui-widget-content{background:0 0}.fc-time-grid .fc-highlight-container{position:relative}.fc-time-grid .fc-highlight{position:absolute;left:0;right:0}.fc-time-grid .fc-bgevent-container,.fc-time-grid .fc-event-container{position:relative}.fc-ltr .fc-time-grid .fc-event-container{margin:0 2.5% 0 2px}.fc-rtl .fc-time-grid .fc-event-container{margin:0 2px 0 2.5%}.fc-time-grid .fc-bgevent,.fc-time-grid .fc-event{position:absolute;z-index:1}.fc-time-grid .fc-bgevent{left:0;right:0}.fc-v-event.fc-not-start{border-top-width:0;padding-top:1px;border-top-left-radius:0;border-top-right-radius:0}.fc-v-event.fc-not-end{border-bottom-width:0;padding-bottom:1px;border-bottom-left-radius:0;border-bottom-right-radius:0}.fc-time-grid-event{overflow:hidden}.fc-time-grid-event .fc-time,.fc-time-grid-event .fc-title{padding:0 1px}.fc-time-grid-event .fc-time{font-size:.85em;white-space:nowrap}.fc-time-grid-event.fc-short .fc-content{white-space:nowrap}.fc-time-grid-event.fc-short .fc-time,.fc-time-grid-event.fc-short .fc-title{display:inline-block;vertical-align:top}.fc-time-grid-event.fc-short .fc-time span{display:none}.fc-time-grid-event.fc-short .fc-time:before{content:attr(data-start)}.fc-time-grid-event.fc-short .fc-time:after{content:"\000A0-\000A0"}.fc-time-grid-event.fc-short .fc-title{font-size:.85em;padding:0}.fc-time-grid-event .fc-resizer{left:0;right:0;bottom:0;height:8px;overflow:hidden;line-height:8px;font-size:11px;font-family:monospace;text-align:center;cursor:s-resize}.fc-time-grid-event .fc-resizer:after{content:"="}
1
+ /*!
2
+ * FullCalendar v2.4.0 Stylesheet
3
+ * Docs & License: http://fullcalendar.io/
4
+ * (c) 2015 Adam Shaw
5
  */.fc{direction:ltr;text-align:left}.fc-rtl{text-align:right}body .fc{font-size:1em}.fc-unthemed .fc-divider,.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#ddd}.fc-unthemed .fc-popover{background-color:#fff}.fc-unthemed .fc-divider,.fc-unthemed .fc-popover .fc-header{background:#eee}.fc-unthemed .fc-popover .fc-header .fc-close{color:#666}.fc-unthemed .fc-today{background:#fcf8e3}.fc-highlight{background:#bce8f1;opacity:.3;filter:alpha(opacity=30)}.fc-bgevent{background:#8fdf82;opacity:.3;filter:alpha(opacity=30)}.fc-nonbusiness{background:#d7d7d7}.fc-icon{display:inline-block;width:1em;height:1em;line-height:1em;font-size:1em;text-align:center;overflow:hidden;font-family:"Courier New",Courier,monospace;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fc-icon:after{position:relative;margin:0 -1em}.fc-icon-left-single-arrow:after{content:"\02039";font-weight:700;font-size:200%;top:-7%;left:3%}.fc-icon-right-single-arrow:after{content:"\0203A";font-weight:700;font-size:200%;top:-7%;left:-3%}.fc-icon-left-double-arrow:after{content:"\000AB";font-size:160%;top:-7%}.fc-icon-right-double-arrow:after{content:"\000BB";font-size:160%;top:-7%}.fc-icon-left-triangle:after{content:"\25C4";font-size:125%;top:3%;left:-2%}.fc-icon-right-triangle:after{content:"\25BA";font-size:125%;top:3%;left:2%}.fc-icon-down-triangle:after{content:"\25BC";font-size:125%;top:2%}.fc-icon-x:after{content:"\000D7";font-size:200%;top:6%}.fc button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;height:2.1em;padding:0 .6em;font-size:1em;white-space:nowrap;cursor:pointer}.fc button::-moz-focus-inner{margin:0;padding:0}.fc-state-default{border:1px solid}.fc-state-default.fc-corner-left{border-top-left-radius:4px;border-bottom-left-radius:4px}.fc-state-default.fc-corner-right{border-top-right-radius:4px;border-bottom-right-radius:4px}.fc button .fc-icon{position:relative;top:-.05em;margin:0 .2em;vertical-align:middle}.fc-state-default{background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);color:#333;text-shadow:0 1px 1px rgba(255,255,255,.75);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.fc-state-active,.fc-state-disabled,.fc-state-down,.fc-state-hover{color:#333;background-color:#e6e6e6}.fc-state-hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.fc-state-active,.fc-state-down{background-color:#ccc;background-image:none;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.fc-state-disabled{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);box-shadow:none}.fc-button-group{display:inline-block}.fc .fc-button-group>*{float:left;margin:0 0 0 -1px}.fc .fc-button-group>:first-child{margin-left:0}.fc-popover{position:absolute;box-shadow:0 2px 6px rgba(0,0,0,.15)}.fc-popover .fc-header{padding:2px 4px}.fc-popover .fc-header .fc-title{margin:0 2px}.fc-popover .fc-header .fc-close{cursor:pointer}.fc-ltr .fc-popover .fc-header .fc-title,.fc-rtl .fc-popover .fc-header .fc-close{float:left}.fc-ltr .fc-popover .fc-header .fc-close,.fc-rtl .fc-popover .fc-header .fc-title{float:right}.fc-unthemed .fc-popover{border-width:1px;border-style:solid}.fc-unthemed .fc-popover .fc-header .fc-close{font-size:.9em;margin-top:2px}.fc-popover>.ui-widget-header+.ui-widget-content{border-top:0}.fc-divider{border-style:solid;border-width:1px}hr.fc-divider{height:0;margin:0;padding:0 0 2px;border-width:1px 0}.fc-clear{clear:both}.fc-bg,.fc-bgevent-skeleton,.fc-helper-skeleton,.fc-highlight-skeleton{position:absolute;top:0;left:0;right:0}.fc-bg{bottom:0}.fc-bg table{height:100%}.fc table{width:100%;table-layout:fixed;border-collapse:collapse;border-spacing:0;font-size:1em}.fc th{text-align:center}.fc td,.fc th{border-style:solid;border-width:1px;padding:0;vertical-align:top}.fc td.fc-today{border-style:double}.fc .fc-row{border-style:solid;border-width:0}.fc-row table{border-left:0 hidden transparent;border-right:0 hidden transparent;border-bottom:0 hidden transparent}.fc-row:first-child table{border-top:0 hidden transparent}.fc-row{position:relative}.fc-row .fc-bg{z-index:1}.fc-row .fc-bgevent-skeleton,.fc-row .fc-highlight-skeleton{bottom:0}.fc-row .fc-bgevent-skeleton table,.fc-row .fc-highlight-skeleton table{height:100%}.fc-row .fc-bgevent-skeleton td,.fc-row .fc-highlight-skeleton td{border-color:transparent}.fc-row .fc-bgevent-skeleton{z-index:2}.fc-row .fc-highlight-skeleton{z-index:3}.fc-row .fc-content-skeleton{position:relative;z-index:4;padding-bottom:2px}.fc-row .fc-helper-skeleton{z-index:5}.fc-row .fc-content-skeleton td,.fc-row .fc-helper-skeleton td{background:0 0;border-color:transparent;border-bottom:0}.fc-row .fc-content-skeleton tbody td,.fc-row .fc-helper-skeleton tbody td{border-top:0}.fc-scroller{overflow-y:scroll;overflow-x:hidden}.fc-scroller>*{position:relative;width:100%;overflow:hidden}.fc-event{position:relative;display:block;font-size:.85em;line-height:1.3;border-radius:3px;border:1px solid #3a87ad;background-color:#3a87ad;font-weight:400}.fc-event,.fc-event:hover,.ui-widget .fc-event{color:#fff;text-decoration:none}.fc-event.fc-draggable,.fc-event[href]{cursor:pointer}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc-event .fc-bg{z-index:1;background:#fff;opacity:.25;filter:alpha(opacity=25)}.fc-event .fc-content{position:relative;z-index:2}.fc-event .fc-resizer{position:absolute;z-index:3}.fc-ltr .fc-h-event.fc-not-start,.fc-rtl .fc-h-event.fc-not-end{margin-left:0;border-left-width:0;padding-left:1px;border-top-left-radius:0;border-bottom-left-radius:0}.fc-ltr .fc-h-event.fc-not-end,.fc-rtl .fc-h-event.fc-not-start{margin-right:0;border-right-width:0;padding-right:1px;border-top-right-radius:0;border-bottom-right-radius:0}.fc-h-event .fc-resizer{top:-1px;bottom:-1px;left:-1px;right:-1px;width:5px}.fc-ltr .fc-h-event .fc-start-resizer,.fc-ltr .fc-h-event .fc-start-resizer:after,.fc-ltr .fc-h-event .fc-start-resizer:before,.fc-rtl .fc-h-event .fc-end-resizer,.fc-rtl .fc-h-event .fc-end-resizer:after,.fc-rtl .fc-h-event .fc-end-resizer:before{right:auto;cursor:w-resize}.fc-ltr .fc-h-event .fc-end-resizer,.fc-ltr .fc-h-event .fc-end-resizer:after,.fc-ltr .fc-h-event .fc-end-resizer:before,.fc-rtl .fc-h-event .fc-start-resizer,.fc-rtl .fc-h-event .fc-start-resizer:after,.fc-rtl .fc-h-event .fc-start-resizer:before{left:auto;cursor:e-resize}.fc-day-grid-event{margin:1px 2px 0;padding:0 1px}.fc-day-grid-event .fc-content{white-space:nowrap;overflow:hidden}.fc-day-grid-event .fc-time{font-weight:700}.fc-day-grid-event .fc-resizer{left:-3px;right:-3px;width:7px}a.fc-more{margin:1px 3px;font-size:.85em;cursor:pointer;text-decoration:none}a.fc-more:hover{text-decoration:underline}.fc-limited{display:none}.fc-day-grid .fc-row{z-index:1}.fc-more-popover{z-index:2;width:220px}.fc-more-popover .fc-event-container{padding:10px}.fc-toolbar{text-align:center;margin-bottom:1em}.fc-toolbar .fc-left{float:left}.fc-toolbar .fc-right{float:right}.fc-toolbar .fc-center{display:inline-block}.fc .fc-toolbar>*>*{float:left;margin-left:.75em}.fc .fc-toolbar>*>:first-child{margin-left:0}.fc-toolbar h2{margin:0}.fc-toolbar button{position:relative}.fc-toolbar .fc-state-hover,.fc-toolbar .ui-state-hover{z-index:2}.fc-toolbar .fc-state-down{z-index:3}.fc-toolbar .fc-state-active,.fc-toolbar .ui-state-active{z-index:4}.fc-toolbar button:focus{z-index:5}.fc-view-container *,.fc-view-container :after,.fc-view-container :before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fc-view,.fc-view>table{position:relative;z-index:1}.fc-basicDay-view .fc-content-skeleton,.fc-basicWeek-view .fc-content-skeleton{padding-top:1px;padding-bottom:1em}.fc-basic-view .fc-body .fc-row{min-height:4em}.fc-row.fc-rigid{overflow:hidden}.fc-row.fc-rigid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-basic-view .fc-day-number,.fc-basic-view .fc-week-number{padding:0 2px}.fc-basic-view td.fc-day-number,.fc-basic-view td.fc-week-number span{padding-top:2px;padding-bottom:2px}.fc-basic-view .fc-week-number{text-align:center}.fc-basic-view .fc-week-number span{display:inline-block;min-width:1.25em}.fc-ltr .fc-basic-view .fc-day-number{text-align:right}.fc-rtl .fc-basic-view .fc-day-number{text-align:left}.fc-day-number.fc-other-month{opacity:.3;filter:alpha(opacity=30)}.fc-agenda-view .fc-day-grid{position:relative;z-index:2}.fc-agenda-view .fc-day-grid .fc-row{min-height:3em}.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton{padding-top:1px;padding-bottom:1em}.fc .fc-axis{vertical-align:middle;padding:0 4px;white-space:nowrap}.fc-ltr .fc-axis{text-align:right}.fc-rtl .fc-axis{text-align:left}.ui-widget td.fc-axis{font-weight:400}.fc-time-grid,.fc-time-grid-container{position:relative;z-index:1}.fc-time-grid{min-height:100%}.fc-time-grid table{border:0 hidden transparent}.fc-time-grid>.fc-bg{z-index:1}.fc-time-grid .fc-slats,.fc-time-grid>hr{position:relative;z-index:2}.fc-time-grid .fc-bgevent-skeleton,.fc-time-grid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-time-grid .fc-bgevent-skeleton{z-index:3}.fc-time-grid .fc-highlight-skeleton{z-index:4}.fc-time-grid .fc-content-skeleton{z-index:5}.fc-time-grid .fc-helper-skeleton{z-index:6}.fc-time-grid .fc-slats td{height:1.5em;border-bottom:0}.fc-time-grid .fc-slats .fc-minor td{border-top-style:dotted}.fc-time-grid .fc-slats .ui-widget-content{background:0 0}.fc-time-grid .fc-highlight-container{position:relative}.fc-time-grid .fc-highlight{position:absolute;left:0;right:0}.fc-time-grid .fc-bgevent-container,.fc-time-grid .fc-event-container{position:relative}.fc-ltr .fc-time-grid .fc-event-container{margin:0 2.5% 0 2px}.fc-rtl .fc-time-grid .fc-event-container{margin:0 2px 0 2.5%}.fc-time-grid .fc-bgevent,.fc-time-grid .fc-event{position:absolute;z-index:1}.fc-time-grid .fc-bgevent{left:0;right:0}.fc-v-event.fc-not-start{border-top-width:0;padding-top:1px;border-top-left-radius:0;border-top-right-radius:0}.fc-v-event.fc-not-end{border-bottom-width:0;padding-bottom:1px;border-bottom-left-radius:0;border-bottom-right-radius:0}.fc-time-grid-event{overflow:hidden}.fc-time-grid-event .fc-time,.fc-time-grid-event .fc-title{padding:0 1px}.fc-time-grid-event .fc-time{font-size:.85em;white-space:nowrap}.fc-time-grid-event.fc-short .fc-content{white-space:nowrap}.fc-time-grid-event.fc-short .fc-time,.fc-time-grid-event.fc-short .fc-title{display:inline-block;vertical-align:top}.fc-time-grid-event.fc-short .fc-time span{display:none}.fc-time-grid-event.fc-short .fc-time:before{content:attr(data-start)}.fc-time-grid-event.fc-short .fc-time:after{content:"\000A0-\000A0"}.fc-time-grid-event.fc-short .fc-title{font-size:.85em;padding:0}.fc-time-grid-event .fc-resizer{left:0;right:0;bottom:0;height:8px;overflow:hidden;line-height:8px;font-size:11px;font-family:monospace;text-align:center;cursor:s-resize}.fc-time-grid-event .fc-resizer:after{content:"="}
backend/modules/calendar/resources/js/calendar.js CHANGED
@@ -1,371 +1,401 @@
1
- jQuery(function ($) {
2
-
3
- var $fullCalendar = $('#bookly-fc-wrapper .bookly-js-calendar-element'),
4
- $tabs = $('.bookly-js-calendar-tab'),
5
- $staff = $('input.bookly-js-check-entity'),
6
- $showAll = $('input#bookly-check-all-entities'),
7
- firstHour = new Date().getHours(),
8
- $staffButton = $('#ab-staff-button'),
9
- staffMembers = [],
10
- staffIds = getCookie('bookly_cal_st_ids'),
11
- tabId = getCookie('bookly_cal_tab_id'),
12
- lastView = getCookie('bookly_cal_view'),
13
- views = 'month,agendaWeek,agendaDay,multiStaffDay';
14
-
15
- if (views.indexOf(lastView) == -1) {
16
- lastView = 'agendaWeek';
17
- }
18
- // Init tabs and staff member filters.
19
- if (staffIds === null) {
20
- $staff.each(function (index, value) {
21
- this.checked = true;
22
- $tabs.filter('[data-staff_id=' + this.value + ']').show();
23
- });
24
- } else if (staffIds != '') {
25
- $.each(staffIds.split(','), function (index, value) {
26
- $staff.filter('[value=' + value + ']').prop('checked', true);
27
- $tabs.filter('[data-staff_id=' + value + ']').show();
28
- });
29
- } else {
30
- $('.dropdown-toggle').dropdown('toggle');
31
- }
32
-
33
- $tabs.filter('[data-staff_id=' + tabId + ']').addClass('active');
34
- if ($tabs.filter('li.active').length == 0) {
35
- $tabs.eq(0).addClass('active').show();
36
- $staff.filter('[value=' + $tabs.eq(0).data('staff_id') + ']').prop('checked', true);
37
- }
38
- updateStaffButton();
39
-
40
- // Init FullCalendar.
41
- $fullCalendar.fullCalendar({
42
- // General Display.
43
- firstDay: BooklyL10n.startOfWeek,
44
- header: {
45
- left: 'prev,next today',
46
- center: 'title',
47
- right: views
48
- },
49
- height: heightFC(),
50
- // Views.
51
- defaultView: lastView,
52
- scrollTime: firstHour + ':00:00',
53
- views: {
54
- agendaWeek: {
55
- columnFormat: 'ddd, D'
56
- },
57
- multiStaffDay: {
58
- staffMembers: staffMembers
59
- }
60
- },
61
- eventBackgroundColor: 'silver',
62
- // Agenda Options.
63
- allDaySlot: false,
64
- allDayText: BooklyL10n.allDay,
65
- axisFormat: BooklyL10n.mjsTimeFormat,
66
- slotDuration: BooklyL10n.slotDuration,
67
- // Text/Time Customization.
68
- timeFormat: BooklyL10n.mjsTimeFormat,
69
- displayEventEnd: true,
70
- buttonText: {
71
- today: BooklyL10n.today,
72
- month: BooklyL10n.month,
73
- week: BooklyL10n.week,
74
- day: BooklyL10n.day
75
- },
76
- monthNames: BooklyL10n.calendar.longMonths,
77
- monthNamesShort: BooklyL10n.calendar.shortMonths,
78
- dayNames: BooklyL10n.calendar.longDays,
79
- dayNamesShort: BooklyL10n.calendar.shortDays,
80
- // Event Dragging & Resizing.
81
- editable: false,
82
- // Event Data.
83
- eventSources: [{
84
- url: ajaxurl,
85
- data: {
86
- action : 'ab_get_staff_appointments',
87
- staff_ids : function () {
88
- var ids = [];
89
- if ($tabs.filter('.active').data('staff_id') == 0) {
90
- for (var i = 0; i < staffMembers.length; ++i) {
91
- ids.push(staffMembers[i].id);
92
- }
93
- } else {
94
- ids.push($tabs.filter('.active').data('staff_id'));
95
- }
96
- return ids;
97
- }
98
- }
99
- }],
100
- // Event Rendering.
101
- eventRender : function (calEvent, $event) {
102
- var body = calEvent.title + '<a class="delete-event dashicons dashicons-trash" title="' + BooklyL10n.delete + '"></a>';
103
-
104
- if (calEvent.desc) {
105
- body += calEvent.desc;
106
- }
107
-
108
- $event.find('.fc-title').html(body);
109
-
110
- var $time = $event.find('.fc-time');
111
- $time.attr('data-start', $time.find('span').text());
112
-
113
- $event.find('.delete-event').on('click', function(e) {
114
- e.stopPropagation();
115
- if (confirm(BooklyL10n.are_you_sure)) {
116
- $.post(ajaxurl, {'action' : 'ab_delete_appointment', 'appointment_id' : calEvent.id }, function () {
117
- $fullCalendar.fullCalendar('removeEvents', calEvent.id);
118
- });
119
- }
120
- });
121
- },
122
- eventAfterRender : function (calEvent, $calEventList, calendar) {
123
- $calEventList.each(function () {
124
- var $calEvent = $(this);
125
- var titleHeight = $calEvent.find('.fc-title').height(),
126
- origHeight = $calEvent.outerHeight();
127
- if (origHeight < titleHeight) {
128
- var z_index = $calEvent.zIndex();
129
- // Mouse handlers.
130
- $calEvent.on('mouseenter', function () {
131
- $calEvent.removeClass('fc-short')
132
- .css({'z-index': 64, bottom: '', height: ''});
133
- }).on('mouseleave', function () {
134
- $calEvent.css({'z-index': z_index, height: origHeight});
135
- });
136
- }
137
- });
138
- },
139
- // Clicking & Hovering.
140
- dayClick: function (date, jsEvent, view) {
141
- var staff_id, visible_staff_id;
142
- if (view.type == 'multiStaffDay') {
143
- var cell = view.coordMap.getCell(jsEvent.pageX, jsEvent.pageY);
144
- var staffMembers = view.opt('staffMembers');
145
- staff_id = staffMembers[cell.col].id;
146
- visible_staff_id = 0;
147
- } else {
148
- staff_id = visible_staff_id = $tabs.filter('.active').data('staff_id');
149
- }
150
-
151
- showAppointmentDialog(
152
- null,
153
- staff_id,
154
- date,
155
- function (event) {
156
- if (visible_staff_id == event.staffId || visible_staff_id == 0) {
157
- // Create event in calendar.
158
- $fullCalendar.fullCalendar('renderEvent', event);
159
- } else {
160
- // Switch to the event owner tab.
161
- jQuery('li[data-staff_id=' + event.staffId + ']').click();
162
- }
163
- }
164
- );
165
- },
166
- eventClick: function (calEvent, jsEvent, view) {
167
- var visible_staff_id;
168
- if (view.type == 'multiStaffDay') {
169
- visible_staff_id = 0;
170
- } else {
171
- visible_staff_id = calEvent.staffId;
172
- }
173
-
174
- showAppointmentDialog(
175
- calEvent.id,
176
- null,
177
- null,
178
- function (event) {
179
- if (visible_staff_id == event.staffId || visible_staff_id == 0) {
180
- // Update event in calendar.
181
- jQuery.extend(calEvent, event);
182
- $fullCalendar.fullCalendar('updateEvent', calEvent);
183
- } else {
184
- // Switch to the event owner tab.
185
- jQuery('li[data-staff_id=' + event.staffId + ']').click();
186
- }
187
- }
188
- );
189
- },
190
- loading: function (bool) {
191
- $('.fc-loading-inner').toggle(bool);
192
- },
193
- viewRender: function (view, element) {
194
- setCookie('bookly_cal_view', view.type);
195
- }
196
- });
197
-
198
- $('.fc-agendaDay-button').addClass('fc-corner-right');
199
- if ($tabs.filter('.active').data('staff_id') == 0) {
200
- $('.fc-agendaDay-button').hide();
201
- } else {
202
- $('.fc-multiStaffDay-button').hide();
203
- }
204
-
205
- // Init date picker for fast navigation in FullCalendar.
206
- var $fcDatePicker = $('<input type=hidden />');
207
- $('.fc-toolbar .fc-center h2').before($fcDatePicker).on('click', function () {
208
- $fcDatePicker.datepicker('setDate', $fullCalendar.fullCalendar('getDate').toDate()).datepicker('show');
209
- });
210
- $fcDatePicker.datepicker({
211
- dayNamesMin : BooklyL10n.calendar.shortDays,
212
- monthNames : BooklyL10n.calendar.longMonths,
213
- monthNamesShort : BooklyL10n.calendar.shortMonths,
214
- firstDay : BooklyL10n.startOfWeek,
215
- beforeShow: function (input, inst) {
216
- inst.dpDiv.queue(function () {
217
- inst.dpDiv.css({marginTop: '35px'});
218
- inst.dpDiv.dequeue();
219
- });
220
- },
221
- onSelect: function (dateText, inst) {
222
- var d = new Date(dateText);
223
- $fullCalendar.fullCalendar('gotoDate', d);
224
- if ($fullCalendar.fullCalendar('getView').type != 'agendaDay' &&
225
- $fullCalendar.fullCalendar('getView').type != 'multiStaffDay')
226
- {
227
- $fullCalendar.find('.fc-day').removeClass('bookly-fc-day-active');
228
- $fullCalendar.find('.fc-day[data-date="' + moment(d).format('YYYY-MM-DD') + '"]').addClass('bookly-fc-day-active');
229
- }
230
- },
231
- onClose: function (dateText, inst) {
232
- inst.dpDiv.queue(function () {
233
- inst.dpDiv.css({marginTop: '0'});
234
- inst.dpDiv.dequeue();
235
- });
236
- }
237
- });
238
-
239
- $(window).on('resize', function () {
240
- $fullCalendar.fullCalendar('option', 'height', heightFC());
241
- });
242
-
243
- // Click on tabs.
244
- $tabs.on('click', function (e) {
245
- e.preventDefault();
246
- $tabs.removeClass('active');
247
- $(this).addClass('active');
248
-
249
- var staff_id = $(this).data('staff_id');
250
- setCookie('bookly_cal_tab_id', staff_id);
251
-
252
- if (staff_id == 0) {
253
- $('.fc-agendaDay-button').hide();
254
- $('.fc-multiStaffDay-button').show();
255
- $fullCalendar.fullCalendar('changeView', 'multiStaffDay');
256
- $fullCalendar.fullCalendar('refetchEvents');
257
- } else {
258
- $('.fc-multiStaffDay-button').hide();
259
- $('.fc-agendaDay-button').show();
260
- var view = $fullCalendar.fullCalendar('getView');
261
- if (view.type == 'multiStaffDay') {
262
- $fullCalendar.fullCalendar('changeView', 'agendaDay');
263
- }
264
- $fullCalendar.fullCalendar('refetchEvents');
265
- }
266
- });
267
-
268
- $('.dropdown-menu').on('click', function (e) {
269
- e.stopPropagation();
270
- });
271
-
272
- /**
273
- * On show all staff checkbox click.
274
- */
275
- $showAll.on('change', function () {
276
- $tabs.filter('[data-staff_id!=0]').toggle(this.checked);
277
- $staff
278
- .prop('checked', this.checked)
279
- .filter(':first').triggerHandler('change');
280
- });
281
-
282
- /**
283
- * On staff checkbox click.
284
- */
285
- $staff.on('change', function (e) {
286
- updateStaffButton();
287
-
288
- $tabs.filter('[data-staff_id=' + this.value + ']').toggle(this.checked);
289
- if ($tabs.filter(':visible.active').length == 0) {
290
- $tabs.filter(':visible:first').triggerHandler('click');
291
- } else if ($tabs.filter('.active').data('staff_id') == 0) {
292
- var view = $fullCalendar.fullCalendar('getView');
293
- if (view.type == 'multiStaffDay') {
294
- view.displayView($fullCalendar.fullCalendar('getDate'));
295
- }
296
- $fullCalendar.fullCalendar('refetchEvents');
297
- }
298
- });
299
-
300
- function updateStaffButton() {
301
- $showAll.prop('checked', $staff.filter(':not(:checked)').length == 0);
302
-
303
- // Update staffMembers array.
304
- var ids = [];
305
- staffMembers.length = 0;
306
- $staff.filter(':checked').each(function () {
307
- staffMembers.push({id: this.value, name: this.getAttribute('data-staff_name')});
308
- ids.push(this.value);
309
- });
310
- setCookie('bookly_cal_st_ids', ids);
311
-
312
- // Update button text.
313
- var number = $staff.filter(':checked').length;
314
- if (number == 0) {
315
- $staffButton.text(BooklyL10n.noStaffSelected);
316
- } else if (number == 1) {
317
- $staffButton.text($staff.filter(':checked').data('staff_name'));
318
- } else {
319
- $staffButton.text(number + '/' + $staff.length);
320
- }
321
- }
322
-
323
- /**
324
- * Set cookie.
325
- *
326
- * @param key
327
- * @param value
328
- */
329
- function setCookie(key, value) {
330
- var expires = new Date();
331
- expires.setTime(expires.getTime() + 86400000); // 60 × 60 × 24 × 1000
332
- document.cookie = key + '=' + value + ';expires=' + expires.toUTCString();
333
- }
334
-
335
- /**
336
- * Get cookie.
337
- *
338
- * @param key
339
- * @return {*}
340
- */
341
- function getCookie(key) {
342
- var keyValue = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
343
- return keyValue ? keyValue[2] : null;
344
- }
345
-
346
- /**
347
- * Calculate height of FullCalendar.
348
- *
349
- * @return {number}
350
- */
351
- function heightFC() {
352
- var window_height = $(window).height(),
353
- wp_admin_bar_height = $('#wpadminbar').height(),
354
- ab_calendar_tabs_height = $('#bookly-fc-wrapper .tabbable').outerHeight(true),
355
- height_to_reduce = wp_admin_bar_height + ab_calendar_tabs_height,
356
- $wrap = $('#wpbody-content .wrap');
357
-
358
- if ($wrap.css('margin-top')) {
359
- height_to_reduce += parseInt($wrap.css('margin-top').replace('px', ''), 10);
360
- }
361
-
362
- if ($wrap.css('margin-bottom')) {
363
- height_to_reduce += parseInt($wrap.css('margin-bottom').replace('px', ''), 10);
364
- }
365
-
366
- var res = window_height - height_to_reduce - 130;
367
-
368
- return res > 620 ? res : 620;
369
- }
370
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  });
1
+ jQuery(function ($) {
2
+
3
+ var $fullCalendar = $('#bookly-fc-wrapper .bookly-js-calendar-element'),
4
+ $tabs = $('.bookly-js-calendar-tab'),
5
+ $staff = $('input.bookly-js-check-entity'),
6
+ $showAll = $('input#bookly-check-all-entities'),
7
+ firstHour = new Date().getHours(),
8
+ $staffButton = $('#ab-staff-button'),
9
+ staffMembers = [],
10
+ staffIds = getCookie('bookly_cal_st_ids'),
11
+ tabId = getCookie('bookly_cal_tab_id'),
12
+ lastView = getCookie('bookly_cal_view'),
13
+ views = 'month,agendaWeek,agendaDay,multiStaffDay',
14
+ $deleteDialog = $('#bookly-delete-dialog'),
15
+ $deleteButton = $('#bookly-delete');
16
+
17
+ if (views.indexOf(lastView) == -1) {
18
+ lastView = 'multiStaffDay';
19
+ }
20
+ // Init tabs and staff member filters.
21
+ if (staffIds === null) {
22
+ $staff.each(function (index, value) {
23
+ this.checked = true;
24
+ $tabs.filter('[data-staff_id=' + this.value + ']').show();
25
+ });
26
+ } else if (staffIds != '') {
27
+ $.each(staffIds.split(','), function (index, value) {
28
+ $staff.filter('[value=' + value + ']').prop('checked', true);
29
+ $tabs.filter('[data-staff_id=' + value + ']').show();
30
+ });
31
+ } else {
32
+ $('.dropdown-toggle').dropdown('toggle');
33
+ }
34
+
35
+ $tabs.filter('[data-staff_id=' + tabId + ']').addClass('active');
36
+ if ($tabs.filter('li.active').length == 0) {
37
+ $tabs.eq(0).addClass('active').show();
38
+ $staff.filter('[value=' + $tabs.eq(0).data('staff_id') + ']').prop('checked', true);
39
+ }
40
+ updateStaffButton();
41
+
42
+ // Init FullCalendar.
43
+ $fullCalendar.fullCalendar({
44
+ // General Display.
45
+ firstDay: BooklyL10n.startOfWeek,
46
+ header: {
47
+ left: 'prev,next today',
48
+ center: 'title',
49
+ right: views
50
+ },
51
+ height: heightFC(),
52
+ // Views.
53
+ defaultView: lastView,
54
+ scrollTime: firstHour + ':00:00',
55
+ views: {
56
+ agendaWeek: {
57
+ columnFormat: 'ddd, D'
58
+ },
59
+ multiStaffDay: {
60
+ staffMembers: staffMembers
61
+ }
62
+ },
63
+ eventBackgroundColor: 'silver',
64
+ // Agenda Options.
65
+ allDaySlot: false,
66
+ allDayText: BooklyL10n.allDay,
67
+ axisFormat: BooklyL10n.mjsTimeFormat,
68
+ slotDuration: BooklyL10n.slotDuration,
69
+ // Text/Time Customization.
70
+ timeFormat: BooklyL10n.mjsTimeFormat,
71
+ displayEventEnd: true,
72
+ buttonText: {
73
+ today: BooklyL10n.today,
74
+ month: BooklyL10n.month,
75
+ week: BooklyL10n.week,
76
+ day: BooklyL10n.day
77
+ },
78
+ monthNames: BooklyL10n.calendar.longMonths,
79
+ monthNamesShort: BooklyL10n.calendar.shortMonths,
80
+ dayNames: BooklyL10n.calendar.longDays,
81
+ dayNamesShort: BooklyL10n.calendar.shortDays,
82
+ // Event Dragging & Resizing.
83
+ editable: false,
84
+ // Event Data.
85
+ eventSources: [{
86
+ url: ajaxurl,
87
+ data: {
88
+ action : 'bookly_get_staff_appointments',
89
+ staff_ids : function () {
90
+ var ids = [];
91
+ if ($tabs.filter('.active').data('staff_id') == 0) {
92
+ for (var i = 0; i < staffMembers.length; ++i) {
93
+ ids.push(staffMembers[i].id);
94
+ }
95
+ } else {
96
+ ids.push($tabs.filter('.active').data('staff_id'));
97
+ }
98
+ return ids;
99
+ }
100
+ }
101
+ }],
102
+ // Event Rendering.
103
+ eventRender : function (calEvent, $event) {
104
+ var body = calEvent.title + '<a class="delete-event dashicons dashicons-trash" title="' + BooklyL10n.delete + '"></a>';
105
+
106
+ if (calEvent.desc) {
107
+ body += calEvent.desc;
108
+ }
109
+
110
+ $event.find('.fc-title').html(body);
111
+
112
+ var $time = $event.find('.fc-time');
113
+ $time.attr('data-start', $time.find('span').text());
114
+
115
+ $event.find('.delete-event').on('click', function(e) {
116
+ e.stopPropagation();
117
+ // Localize contains only string values
118
+ if (BooklyL10n.recurring_appointments_enabled == '1' && calEvent.series_id) {
119
+ $(document.body).trigger( 'recurring_appointments.delete_dialog', [ $fullCalendar, calEvent ] );
120
+ } else {
121
+ $deleteDialog.data('calEvent', calEvent).modal('show');
122
+ }
123
+ });
124
+ },
125
+ eventAfterRender : function (calEvent, $calEventList, calendar) {
126
+ $calEventList.each(function () {
127
+ var $calEvent = $(this);
128
+ var titleHeight = $calEvent.find('.fc-title').height(),
129
+ origHeight = $calEvent.outerHeight();
130
+ if (origHeight < titleHeight) {
131
+ var z_index = $calEvent.zIndex();
132
+ // Mouse handlers.
133
+ $calEvent.on('mouseenter', function () {
134
+ $calEvent.removeClass('fc-short')
135
+ .css({'z-index': 64, bottom: '', height: ''});
136
+ }).on('mouseleave', function () {
137
+ $calEvent.css({'z-index': z_index, height: origHeight});
138
+ });
139
+ }
140
+ });
141
+ },
142
+ // Clicking & Hovering.
143
+ dayClick: function (date, jsEvent, view) {
144
+ var staff_id, visible_staff_id;
145
+ if (view.type == 'multiStaffDay') {
146
+ var cell = view.coordMap.getCell(jsEvent.pageX, jsEvent.pageY);
147
+ var staffMembers = view.opt('staffMembers');
148
+ staff_id = staffMembers[cell.col].id;
149
+ visible_staff_id = 0;
150
+ } else {
151
+ staff_id = visible_staff_id = $tabs.filter('.active').data('staff_id');
152
+ }
153
+
154
+ showAppointmentDialog(
155
+ null,
156
+ staff_id,
157
+ date,
158
+ function (event) {
159
+ if (visible_staff_id == event.staffId || visible_staff_id == 0) {
160
+ if (event.id) {
161
+ // Create event in calendar.
162
+ $fullCalendar.fullCalendar('renderEvent', event);
163
+ } else {
164
+ $fullCalendar.fullCalendar('refetchEvents');
165
+ }
166
+ } else {
167
+ // Switch to the event owner tab.
168
+ jQuery('li[data-staff_id=' + event.staffId + ']').click();
169
+ }
170
+ }
171
+ );
172
+ },
173
+ eventClick: function (calEvent, jsEvent, view) {
174
+ var visible_staff_id;
175
+ if (view.type == 'multiStaffDay') {
176
+ visible_staff_id = 0;
177
+ } else {
178
+ visible_staff_id = calEvent.staffId;
179
+ }
180
+
181
+ showAppointmentDialog(
182
+ calEvent.id,
183
+ null,
184
+ null,
185
+ function (event) {
186
+ if (visible_staff_id == event.staffId || visible_staff_id == 0) {
187
+ // Update event in calendar.
188
+ jQuery.extend(calEvent, event);
189
+ $fullCalendar.fullCalendar('updateEvent', calEvent);
190
+ } else {
191
+ // Switch to the event owner tab.
192
+ jQuery('li[data-staff_id=' + event.staffId + ']').click();
193
+ }
194
+ }
195
+ );
196
+ },
197
+ loading: function (bool) {
198
+ $('.fc-loading-inner').toggle(bool);
199
+ },
200
+ viewRender: function (view, element) {
201
+ setCookie('bookly_cal_view', view.type);
202
+ }
203
+ });
204
+
205
+ $('.fc-agendaDay-button').addClass('fc-corner-right');
206
+ if ($tabs.filter('.active').data('staff_id') == 0) {
207
+ $('.fc-agendaDay-button').hide();
208
+ } else {
209
+ $('.fc-multiStaffDay-button').hide();
210
+ }
211
+
212
+ // Init date picker for fast navigation in FullCalendar.
213
+ var $fcDatePicker = $('<input type=hidden />');
214
+ $('.fc-toolbar .fc-center h2').before($fcDatePicker).on('click', function () {
215
+ $fcDatePicker.datepicker('setDate', $fullCalendar.fullCalendar('getDate').toDate()).datepicker('show');
216
+ });
217
+ $fcDatePicker.datepicker({
218
+ dayNamesMin : BooklyL10n.calendar.shortDays,
219
+ monthNames : BooklyL10n.calendar.longMonths,
220
+ monthNamesShort : BooklyL10n.calendar.shortMonths,
221
+ firstDay : BooklyL10n.startOfWeek,
222
+ beforeShow: function (input, inst) {
223
+ inst.dpDiv.queue(function () {
224
+ inst.dpDiv.css({marginTop: '35px'});
225
+ inst.dpDiv.dequeue();
226
+ });
227
+ },
228
+ onSelect: function (dateText, inst) {
229
+ var d = new Date(dateText);
230
+ $fullCalendar.fullCalendar('gotoDate', d);
231
+ if ($fullCalendar.fullCalendar('getView').type != 'agendaDay' &&
232
+ $fullCalendar.fullCalendar('getView').type != 'multiStaffDay')
233
+ {
234
+ $fullCalendar.find('.fc-day').removeClass('bookly-fc-day-active');
235
+ $fullCalendar.find('.fc-day[data-date="' + moment(d).format('YYYY-MM-DD') + '"]').addClass('bookly-fc-day-active');
236
+ }
237
+ },
238
+ onClose: function (dateText, inst) {
239
+ inst.dpDiv.queue(function () {
240
+ inst.dpDiv.css({marginTop: '0'});
241
+ inst.dpDiv.dequeue();
242
+ });
243
+ }
244
+ });
245
+
246
+ $(window).on('resize', function () {
247
+ $fullCalendar.fullCalendar('option', 'height', heightFC());
248
+ });
249
+
250
+ // Click on tabs.
251
+ $tabs.on('click', function (e) {
252
+ e.preventDefault();
253
+ $tabs.removeClass('active');
254
+ $(this).addClass('active');
255
+
256
+ var staff_id = $(this).data('staff_id');
257
+ setCookie('bookly_cal_tab_id', staff_id);
258
+
259
+ if (staff_id == 0) {
260
+ $('.fc-agendaDay-button').hide();
261
+ $('.fc-multiStaffDay-button').show();
262
+ $fullCalendar.fullCalendar('changeView', 'multiStaffDay');
263
+ $fullCalendar.fullCalendar('refetchEvents');
264
+ } else {
265
+ $('.fc-multiStaffDay-button').hide();
266
+ $('.fc-agendaDay-button').show();
267
+ var view = $fullCalendar.fullCalendar('getView');
268
+ if (view.type == 'multiStaffDay') {
269
+ $fullCalendar.fullCalendar('changeView', 'agendaDay');
270
+ }
271
+ $fullCalendar.fullCalendar('refetchEvents');
272
+ }
273
+ });
274
+
275
+ $('.dropdown-menu').on('click', function (e) {
276
+ e.stopPropagation();
277
+ });
278
+
279
+ /**
280
+ * On show all staff checkbox click.
281
+ */
282
+ $showAll.on('change', function () {
283
+ $tabs.filter('[data-staff_id!=0]').toggle(this.checked);
284
+ $staff
285
+ .prop('checked', this.checked)
286
+ .filter(':first').triggerHandler('change');
287
+ });
288
+
289
+ /**
290
+ * On staff checkbox click.
291
+ */
292
+ $staff.on('change', function (e) {
293
+ updateStaffButton();
294
+
295
+ $tabs.filter('[data-staff_id=' + this.value + ']').toggle(this.checked);
296
+ if ($tabs.filter(':visible.active').length == 0) {
297
+ $tabs.filter(':visible:first').triggerHandler('click');
298
+ } else if ($tabs.filter('.active').data('staff_id') == 0) {
299
+ var view = $fullCalendar.fullCalendar('getView');
300
+ if (view.type == 'multiStaffDay') {
301
+ view.displayView($fullCalendar.fullCalendar('getDate'));
302
+ }
303
+ $fullCalendar.fullCalendar('refetchEvents');
304
+ }
305
+ });
306
+
307
+ /**
308
+ * On delete appointment click.
309
+ */
310
+ $deleteButton.on('click', function (e) {
311
+ var calEvent = $deleteDialog.data('calEvent'),
312
+ ladda = Ladda.create(this);
313
+ ladda.start();
314
+ $.post(
315
+ ajaxurl,
316
+ {
317
+ 'action' : 'bookly_delete_appointment',
318
+ 'appointment_id' : calEvent.id,
319
+ 'notify' : $('#bookly-delete-notify').prop('checked') ? 1 : 0,
320
+ 'reason' : $('#bookly-delete-reason').val()
321
+ },
322
+ function () {
323
+ ladda.stop();
324
+ $fullCalendar.fullCalendar('removeEvents', calEvent.id);
325
+ $deleteDialog.modal('hide');
326
+ }
327
+ );
328
+ });
329
+
330
+ function updateStaffButton() {
331
+ $showAll.prop('checked', $staff.filter(':not(:checked)').length == 0);
332
+
333
+ // Update staffMembers array.
334
+ var ids = [];
335
+ staffMembers.length = 0;
336
+ $staff.filter(':checked').each(function () {
337
+ staffMembers.push({id: this.value, name: this.getAttribute('data-staff_name')});
338
+ ids.push(this.value);
339
+ });
340
+ setCookie('bookly_cal_st_ids', ids);
341
+
342
+ // Update button text.
343
+ var number = $staff.filter(':checked').length;
344
+ if (number == 0) {
345
+ $staffButton.text(BooklyL10n.noStaffSelected);
346
+ } else if (number == 1) {
347
+ $staffButton.text($staff.filter(':checked').data('staff_name'));
348
+ } else {
349
+ $staffButton.text(number + '/' + $staff.length);
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Set cookie.
355
+ *
356
+ * @param key
357
+ * @param value
358
+ */
359
+ function setCookie(key, value) {
360
+ var expires = new Date();
361
+ expires.setTime(expires.getTime() + 86400000); // 60 × 60 × 24 × 1000
362
+ document.cookie = key + '=' + value + ';expires=' + expires.toUTCString();
363
+ }
364
+
365
+ /**
366
+ * Get cookie.
367
+ *
368
+ * @param key
369
+ * @return {*}
370
+ */
371
+ function getCookie(key) {
372
+ var keyValue = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
373
+ return keyValue ? keyValue[2] : null;
374
+ }
375
+
376
+ /**
377
+ * Calculate height of FullCalendar.
378
+ *
379
+ * @return {number}
380
+ */
381
+ function heightFC() {
382
+ var window_height = $(window).height(),
383
+ wp_admin_bar_height = $('#wpadminbar').height(),
384
+ ab_calendar_tabs_height = $('#bookly-fc-wrapper .tabbable').outerHeight(true),
385
+ height_to_reduce = wp_admin_bar_height + ab_calendar_tabs_height,
386
+ $wrap = $('#wpbody-content .wrap');
387
+
388
+ if ($wrap.css('margin-top')) {
389
+ height_to_reduce += parseInt($wrap.css('margin-top').replace('px', ''), 10);
390
+ }
391
+
392
+ if ($wrap.css('margin-bottom')) {
393
+ height_to_reduce += parseInt($wrap.css('margin-bottom').replace('px', ''), 10);
394
+ }
395
+
396
+ var res = window_height - height_to_reduce - 130;
397
+
398
+ return res > 620 ? res : 620;
399
+ }
400
+
401
  });
backend/modules/calendar/resources/js/delete_dialog.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Scripts for delete dialog
3
+ */
4
+
5
+ jQuery(function($) {
6
+ $("#bookly-delete-notify").on('change', function() {
7
+ $('#bookly-delete-reason-cover').toggle();
8
+ })
9
+ });
backend/modules/calendar/resources/js/fullcalendar.min.js CHANGED
@@ -1,9 +1,9 @@
1
- /*!
2
- * FullCalendar v2.4.0
3
- * Docs & License: http://fullcalendar.io/
4
- * (c) 2015 Adam Shaw
5
- */
6
- !function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):"object"==typeof exports?module.exports=a(require("jquery"),require("moment")):a(jQuery,moment)}(function(a,b){function c(a){return Q(a,Oa)}function d(b){var c,d={views:b.views||{}};return a.each(b,function(b,e){"views"!=b&&(a.isPlainObject(e)&&!/(time|duration|interval)$/i.test(b)&&-1==a.inArray(b,Oa)?(c=null,a.each(e,function(a,e){/^(month|week|day|default|basic(Week|Day)?|agenda(Week|Day)?)$/.test(a)?(d.views[a]||(d.views[a]={}),d.views[a][b]=e):(c||(c={}),c[a]=e)}),c&&(d[b]=c)):d[b]=e)}),d}function e(a,b){b.left&&a.css({"border-left-width":1,"margin-left":b.left-1}),b.right&&a.css({"border-right-width":1,"margin-right":b.right-1})}function f(a){a.css({"margin-left":"","margin-right":"","border-left-width":"","border-right-width":""})}function g(){a("body").addClass("fc-not-allowed")}function h(){a("body").removeClass("fc-not-allowed")}function i(b,c,d){var e=Math.floor(c/b.length),f=Math.floor(c-e*(b.length-1)),g=[],h=[],i=[],k=0;j(b),b.each(function(c,d){var j=c===b.length-1?f:e,l=a(d).outerHeight(!0);j>l?(g.push(d),h.push(l),i.push(a(d).height())):k+=l}),d&&(c-=k,e=Math.floor(c/g.length),f=Math.floor(c-e*(g.length-1))),a(g).each(function(b,c){var d=b===g.length-1?f:e,j=h[b],k=i[b],l=d-(j-k);d>j&&a(c).height(l)})}function j(a){a.height("")}function k(b){var c=0;return b.find("> *").each(function(b,d){var e=a(d).outerWidth();e>c&&(c=e)}),c++,b.width(c),c}function l(a,b){return a.height(b).addClass("fc-scroller"),a[0].scrollHeight-1>a[0].clientHeight?!0:(m(a),!1)}function m(a){a.height("").removeClass("fc-scroller")}function n(b){var c=b.css("position"),d=b.parents().filter(function(){var b=a(this);return/(auto|scroll)/.test(b.css("overflow")+b.css("overflow-y")+b.css("overflow-x"))}).eq(0);return"fixed"!==c&&d.length?d:a(b[0].ownerDocument||document)}function o(a){var b=a.offset();return{left:b.left,right:b.left+a.outerWidth(),top:b.top,bottom:b.top+a.outerHeight()}}function p(a){var b=a.offset(),c=r(a),d=b.left+u(a,"border-left-width")+c.left,e=b.top+u(a,"border-top-width")+c.top;return{left:d,right:d+a[0].clientWidth,top:e,bottom:e+a[0].clientHeight}}function q(a){var b=a.offset(),c=b.left+u(a,"border-left-width")+u(a,"padding-left"),d=b.top+u(a,"border-top-width")+u(a,"padding-top");return{left:c,right:c+a.width(),top:d,bottom:d+a.height()}}function r(a){var b=a.innerWidth()-a[0].clientWidth,c={left:0,right:0,top:0,bottom:a.innerHeight()-a[0].clientHeight};return s()&&"rtl"==a.css("direction")?c.left=b:c.right=b,c}function s(){return null===Pa&&(Pa=t()),Pa}function t(){var b=a("<div><div/></div>").css({position:"absolute",top:-1e3,left:0,border:0,padding:0,overflow:"scroll",direction:"rtl"}).appendTo("body"),c=b.children(),d=c.offset().left>b.offset().left;return b.remove(),d}function u(a,b){return parseFloat(a.css(b))||0}function v(a){return 1==a.which&&!a.ctrlKey}function w(a,b){var c={left:Math.max(a.left,b.left),right:Math.min(a.right,b.right),top:Math.max(a.top,b.top),bottom:Math.min(a.bottom,b.bottom)};return c.left<c.right&&c.top<c.bottom?c:!1}function x(a,b){return{left:Math.min(Math.max(a.left,b.left),b.right),top:Math.min(Math.max(a.top,b.top),b.bottom)}}function y(a){return{left:(a.left+a.right)/2,top:(a.top+a.bottom)/2}}function z(a,b){return{left:a.left-b.left,top:a.top-b.top}}function A(b){var c,d,e=[],f=[];for("string"==typeof b?f=b.split(/\s*,\s*/):"function"==typeof b?f=[b]:a.isArray(b)&&(f=b),c=0;c<f.length;c++)d=f[c],"string"==typeof d?e.push("-"==d.charAt(0)?{field:d.substring(1),order:-1}:{field:d,order:1}):"function"==typeof d&&e.push({func:d});return e}function B(a,b,c){var d,e;for(d=0;d<c.length;d++)if(e=C(a,b,c[d]))return e;return 0}function C(a,b,c){return c.func?c.func(a,b):D(a[c.field],b[c.field])*(c.order||1)}function D(b,c){return b||c?null==c?-1:null==b?1:"string"===a.type(b)||"string"===a.type(c)?String(b).localeCompare(String(c)):b-c:0}function E(a,b){var c,d,e,f,g=a.start,h=a.end,i=b.start,j=b.end;return h>i&&j>g?(g>=i?(c=g.clone(),e=!0):(c=i.clone(),e=!1),j>=h?(d=h.clone(),f=!0):(d=j.clone(),f=!1),{start:c,end:d,isStart:e,isEnd:f}):void 0}function F(a,c){return b.duration({days:a.clone().stripTime().diff(c.clone().stripTime(),"days"),ms:a.time()-c.time()})}function G(a,c){return b.duration({days:a.clone().stripTime().diff(c.clone().stripTime(),"days")})}function H(a,c,d){return b.duration(Math.round(a.diff(c,d,!0)),d)}function I(a,b){var c,d,e;for(c=0;c<Ra.length&&(d=Ra[c],e=J(d,a,b),!(e>=1&&ba(e)));c++);return d}function J(a,c,d){return null!=d?d.diff(c,a,!0):b.isDuration(c)?c.as(a):c.end.diff(c.start,a,!0)}function K(a,b,c){var d;return N(c)?(b-a)/c:(d=c.asMonths(),Math.abs(d)>=1&&ba(d)?b.diff(a,"months",!0)/d:b.diff(a,"days",!0)/c.asDays())}function L(a,b){var c,d;return N(a)||N(b)?a/b:(c=a.asMonths(),d=b.asMonths(),Math.abs(c)>=1&&ba(c)&&Math.abs(d)>=1&&ba(d)?c/d:a.asDays()/b.asDays())}function M(a,c){var d;return N(a)?b.duration(a*c):(d=a.asMonths(),Math.abs(d)>=1&&ba(d)?b.duration({months:d*c}):b.duration({days:a.asDays()*c}))}function N(a){return Boolean(a.hours()||a.minutes()||a.seconds()||a.milliseconds())}function O(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function P(a){return/^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(a)}function Q(a,b){var c,d,e,f,g,h,i={};if(b)for(c=0;c<b.length;c++){for(d=b[c],e=[],f=a.length-1;f>=0;f--)if(g=a[f][d],"object"==typeof g)e.unshift(g);else if(void 0!==g){i[d]=g;break}e.length&&(i[d]=Q(e))}for(c=a.length-1;c>=0;c--){h=a[c];for(d in h)d in i||(i[d]=h[d])}return i}function R(a){var b=function(){};return b.prototype=a,new b}function S(a,b){for(var c in a)U(a,c)&&(b[c]=a[c])}function T(a,b){var c,d,e=["constructor","toString","valueOf"];for(c=0;c<e.length;c++)d=e[c],a[d]!==Object.prototype[d]&&(b[d]=a[d])}function U(a,b){return Va.call(a,b)}function V(b){return/undefined|null|boolean|number|string/.test(a.type(b))}function W(b,c,d){if(a.isFunction(b)&&(b=[b]),b){var e,f;for(e=0;e<b.length;e++)f=b[e].apply(c,d)||f;return f}}function X(){for(var a=0;a<arguments.length;a++)if(void 0!==arguments[a])return arguments[a]}function Y(a){return(a+"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/'/g,"&#039;").replace(/"/g,"&quot;").replace(/\n/g,"<br />")}function Z(a){return a.replace(/&.*?;/g,"")}function $(b){var c=[];return a.each(b,function(a,b){null!=b&&c.push(a+":"+b)}),c.join(";")}function _(a){return a.charAt(0).toUpperCase()+a.slice(1)}function aa(a,b){return a-b}function ba(a){return a%1===0}function ca(a,b){var c=a[b];return function(){return c.apply(a,arguments)}}function da(a,b){var c,d,e,f,g=function(){var h=+new Date-f;b>h&&h>0?c=setTimeout(g,b-h):(c=null,a.apply(e,d),c||(e=d=null))};return function(){e=this,d=arguments,f=+new Date,c||(c=setTimeout(g,b))}}function ea(c,d,e){var f,g,h,i,j=c[0],k=1==c.length&&"string"==typeof j;return b.isMoment(j)?(i=b.apply(null,c),ga(j,i)):O(j)||void 0===j?i=b.apply(null,c):(f=!1,g=!1,k?Wa.test(j)?(j+="-01",c=[j],f=!0,g=!0):(h=Xa.exec(j))&&(f=!h[5],g=!0):a.isArray(j)&&(g=!0),i=d||f?b.utc.apply(b,c):b.apply(null,c),f?(i._ambigTime=!0,i._ambigZone=!0):e&&(g?i._ambigZone=!0:k&&(i.utcOffset?i.utcOffset(j):i.zone(j)))),i._fullCalendar=!0,i}function fa(a,c){var d,e,f=!1,g=!1,h=a.length,i=[];for(d=0;h>d;d++)e=a[d],b.isMoment(e)||(e=Ma.moment.parseZone(e)),f=f||e._ambigTime,g=g||e._ambigZone,i.push(e);for(d=0;h>d;d++)e=i[d],c||!f||e._ambigTime?g&&!e._ambigZone&&(i[d]=e.clone().stripZone()):i[d]=e.clone().stripTime();return i}function ga(a,b){a._ambigTime?b._ambigTime=!0:b._ambigTime&&(b._ambigTime=!1),a._ambigZone?b._ambigZone=!0:b._ambigZone&&(b._ambigZone=!1)}function ha(a,b){a.year(b[0]||0).month(b[1]||0).date(b[2]||0).hours(b[3]||0).minutes(b[4]||0).seconds(b[5]||0).milliseconds(b[6]||0)}function ia(a,b){return Za.format.call(a,b)}function ja(a,b){return ka(a,pa(b))}function ka(a,b){var c,d="";for(c=0;c<b.length;c++)d+=la(a,b[c]);return d}function la(a,b){var c,d;return"string"==typeof b?b:(c=b.token)?$a[c]?$a[c](a):ia(a,c):b.maybe&&(d=ka(a,b.maybe),d.match(/[1-9]/))?d:""}function ma(a,b,c,d,e){var f;return a=Ma.moment.parseZone(a),b=Ma.moment.parseZone(b),f=(a.localeData||a.lang).call(a),c=f.longDateFormat(c)||c,d=d||" - ",na(a,b,pa(c),d,e)}function na(a,b,c,d,e){var f,g,h,i,j="",k="",l="",m="",n="";for(g=0;g<c.length&&(f=oa(a,b,c[g]),f!==!1);g++)j+=f;for(h=c.length-1;h>g&&(f=oa(a,b,c[h]),f!==!1);h--)k=f+k;for(i=g;h>=i;i++)l+=la(a,c[i]),m+=la(b,c[i]);return(l||m)&&(n=e?m+d+l:l+d+m),j+n+k}function oa(a,b,c){var d,e;return"string"==typeof c?c:(d=c.token)&&(e=_a[d.charAt(0)],e&&a.isSame(b,e))?ia(a,d):!1}function pa(a){return a in ab?ab[a]:ab[a]=qa(a)}function qa(a){for(var b,c=[],d=/\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g;b=d.exec(a);)b[1]?c.push(b[1]):b[2]?c.push({maybe:qa(b[2])}):b[3]?c.push({token:b[3]}):b[5]&&c.push(b[5]);return c}function ra(){}function sa(a,b){return a||b?a&&b?a.grid===b.grid&&a.row===b.row&&a.col===b.col:!1:!0}function ta(a){var b=va(a);return"background"===b||"inverse-background"===b}function ua(a){return"inverse-background"===va(a)}function va(a){return X((a.source||{}).rendering,a.rendering)}function wa(a){var b,c,d={};for(b=0;b<a.length;b++)c=a[b],(d[c._id]||(d[c._id]=[])).push(c);return d}function xa(a,b){return a.eventStartMS-b.eventStartMS}function ya(c){var d,e,f,g,h=Ma.dataAttrPrefix;return h&&(h+="-"),d=c.data(h+"event")||null,d&&(d="object"==typeof d?a.extend({},d):{},e=d.start,null==e&&(e=d.time),f=d.duration,g=d.stick,delete d.start,delete d.time,delete d.duration,delete d.stick),null==e&&(e=c.data(h+"start")),null==e&&(e=c.data(h+"time")),null==f&&(f=c.data(h+"duration")),null==g&&(g=c.data(h+"stick")),e=null!=e?b.duration(e):null,f=null!=f?b.duration(f):null,g=Boolean(g),{eventProps:d,startTime:e,duration:f,stick:g}}function za(a,b){var c,d;for(c=0;c<b.length;c++)if(d=b[c],d.leftCol<=a.rightCol&&d.rightCol>=a.leftCol)return!0;return!1}function Aa(a,b){return a.leftCol-b.leftCol}function Ba(a){var b,c,d,e=[];for(b=0;b<a.length;b++){for(c=a[b],d=0;d<e.length&&Ea(c,e[d]).length;d++);c.level=d,(e[d]||(e[d]=[])).push(c)}return e}function Ca(a){var b,c,d,e,f;for(b=0;b<a.length;b++)for(c=a[b],d=0;d<c.length;d++)for(e=c[d],e.forwardSegs=[],f=b+1;f<a.length;f++)Ea(e,a[f],e.forwardSegs)}function Da(a){var b,c,d=a.forwardSegs,e=0;if(void 0===a.forwardPressure){for(b=0;b<d.length;b++)c=d[b],Da(c),e=Math.max(e,1+c.forwardPressure);a.forwardPressure=e}}function Ea(a,b,c){c=c||[];for(var d=0;d<b.length;d++)Fa(a,b[d])&&c.push(b[d]);return c}function Fa(a,b){return a.bottom>b.top&&a.top<b.bottom}function Ga(c,d){function e(){U?h()&&(k(),i()):f()}function f(){V=O.theme?"ui":"fc",c.addClass("fc"),O.isRTL?c.addClass("fc-rtl"):c.addClass("fc-ltr"),O.theme?c.addClass("ui-widget"):c.addClass("fc-unthemed"),U=a("<div class='fc-view-container'/>").prependTo(c),S=N.header=new Ja(N,O),T=S.render(),T&&c.prepend(T),i(O.defaultView),O.handleWindowResize&&(Y=da(m,O.windowResizeDelay),a(window).resize(Y))}function g(){W&&W.removeElement(),S.removeElement(),U.remove(),c.removeClass("fc fc-ltr fc-rtl fc-unthemed ui-widget"),Y&&a(window).unbind("resize",Y)}function h(){return c.is(":visible")}function i(b){ca++,W&&b&&W.type!==b&&(S.deactivateButton(W.type),H(),W.removeElement(),W=N.view=null),!W&&b&&(W=N.view=ba[b]||(ba[b]=N.instantiateView(b)),W.setElement(a("<div class='fc-view fc-"+b+"-view' />").appendTo(U)),S.activateButton(b)),W&&(Z=W.massageCurrentDate(Z),W.displaying&&Z.isWithin(W.intervalStart,W.intervalEnd)||h()&&(H(),W.display(Z),I(),u(),v(),q())),I(),ca--}function j(a){return h()?(a&&l(),ca++,W.updateSize(!0),ca--,!0):void 0}function k(){h()&&l()}function l(){X="number"==typeof O.contentHeight?O.contentHeight:"number"==typeof O.height?O.height-(T?T.outerHeight(!0):0):Math.round(U.width()/Math.max(O.aspectRatio,.5))}function m(a){!ca&&a.target===window&&W.start&&j(!0)&&W.trigger("windowResize",aa)}function n(){p(),r()}function o(){h()&&(H(),W.displayEvents(ea),I())}function p(){H(),W.clearEvents(),I()}function q(){!O.lazyFetching||$(W.start,W.end)?r():o()}function r(){_(W.start,W.end)}function s(a){ea=a,o()}function t(){o()}function u(){S.updateTitle(W.title)}function v(){var a=N.getNow();a.isWithin(W.intervalStart,W.intervalEnd)?S.disableButton("today"):S.enableButton("today")}function w(a,b){W.select(N.buildSelectRange.apply(N,arguments))}function x(){W&&W.unselect()}function y(){Z=W.computePrevDate(Z),i()}function z(){Z=W.computeNextDate(Z),i()}function A(){Z.add(-1,"years"),i()}function B(){Z.add(1,"years"),i()}function C(){Z=N.getNow(),i()}function D(a){Z=N.moment(a),i()}function E(a){Z.add(b.duration(a)),i()}function F(a,b){var c;b=b||"day",c=N.getViewSpec(b)||N.getUnitViewSpec(b),Z=a,i(c?c.type:null)}function G(){return Z.clone()}function H(){U.css({width:"100%",height:U.height(),overflow:"hidden"})}function I(){U.css({width:"",height:"",overflow:""})}function J(){return N}function K(){return W}function L(a,b){return void 0===b?O[a]:void(("height"==a||"contentHeight"==a||"aspectRatio"==a)&&(O[a]=b,j(!0)))}function M(a,b){var c=Array.prototype.slice.call(arguments,2);return b=b||aa,this.triggerWith(a,b,c),O[a]?O[a].apply(b,c):void 0}var N=this;N.initOptions(d||{});var O=this.options;N.render=e,N.destroy=g,N.refetchEvents=n,N.reportEvents=s,N.reportEventChange=t,N.rerenderEvents=o,N.changeView=i,N.select=w,N.unselect=x,N.prev=y,N.next=z,N.prevYear=A,N.nextYear=B,N.today=C,N.gotoDate=D,N.incrementDate=E,N.zoomTo=F,N.getDate=G,N.getCalendar=J,N.getView=K,N.option=L,N.trigger=M;var P=R(Ia(O.lang));if(O.monthNames&&(P._months=O.monthNames),O.monthNamesShort&&(P._monthsShort=O.monthNamesShort),O.dayNames&&(P._weekdays=O.dayNames),O.dayNamesShort&&(P._weekdaysShort=O.dayNamesShort),null!=O.firstDay){var Q=R(P._week);Q.dow=O.firstDay,P._week=Q}P._fullCalendar_weekCalc=function(a){return"function"==typeof a?a:"local"===a?a:"iso"===a||"ISO"===a?"ISO":void 0}(O.weekNumberCalculation),N.defaultAllDayEventDuration=b.duration(O.defaultAllDayEventDuration),N.defaultTimedEventDuration=b.duration(O.defaultTimedEventDuration),N.moment=function(){var a;return"local"===O.timezone?(a=Ma.moment.apply(null,arguments),a.hasTime()&&a.local()):a="UTC"===O.timezone?Ma.moment.utc.apply(null,arguments):Ma.moment.parseZone.apply(null,arguments),"_locale"in a?a._locale=P:a._lang=P,a},N.getIsAmbigTimezone=function(){return"local"!==O.timezone&&"UTC"!==O.timezone},N.rezoneDate=function(a){return N.moment(a.toArray())},N.getNow=function(){var a=O.now;return"function"==typeof a&&(a=a()),N.moment(a)},N.getEventEnd=function(a){return a.end?a.end.clone():N.getDefaultEventEnd(a.allDay,a.start)},N.getDefaultEventEnd=function(a,b){var c=b.clone();return a?c.stripTime().add(N.defaultAllDayEventDuration):c.add(N.defaultTimedEventDuration),N.getIsAmbigTimezone()&&c.stripZone(),c},N.humanizeDuration=function(a){return(a.locale||a.lang).call(a,O.lang).humanize()},Ka.call(N,O);var S,T,U,V,W,X,Y,Z,$=N.isFetchNeeded,_=N.fetchEvents,aa=c[0],ba={},ca=0,ea=[];Z=null!=O.defaultDate?N.moment(O.defaultDate):N.getNow(),N.getSuggestedViewHeight=function(){return void 0===X&&k(),X},N.isHeightAuto=function(){return"auto"===O.contentHeight||"auto"===O.height},N.initialize()}function Ha(b){a.each(rb,function(a,c){null==b[a]&&(b[a]=c(b))})}function Ia(a){var c=b.localeData||b.langData;return c.call(b,a)||c.call(b,"en")}function Ja(b,c){function d(){var b=c.header;return n=c.theme?"ui":"fc",b?o=a("<div class='fc-toolbar'/>").append(f("left")).append(f("right")).append(f("center")).append('<div class="fc-clear"/>'):void 0}function e(){o.remove(),o=a()}function f(d){var e=a('<div class="fc-'+d+'"/>'),f=c.header[d];return f&&a.each(f.split(" "),function(d){var f,g=a(),h=!0;a.each(this.split(","),function(d,e){var f,i,j,k,l,m,o,q,r,s;"title"==e?(g=g.add(a("<h2>&nbsp;</h2>")),h=!1):((f=(b.options.customButtons||{})[e])?(j=function(a){f.click&&f.click.call(s[0],a)},k="",l=f.text):(i=b.getViewSpec(e))?(j=function(){b.changeView(e)},p.push(e),k=i.buttonTextOverride,l=i.buttonTextDefault):b[e]&&(j=function(){b[e]()},k=(b.overrides.buttonText||{})[e],l=c.buttonText[e]),j&&(m=f?f.themeIcon:c.themeButtonIcons[e],o=f?f.icon:c.buttonIcons[e],q=k?Y(k):m&&c.theme?"<span class='ui-icon ui-icon-"+m+"'></span>":o&&!c.theme?"<span class='fc-icon fc-icon-"+o+"'></span>":Y(l),r=["fc-"+e+"-button",n+"-button",n+"-state-default"],s=a('<button type="button" class="'+r.join(" ")+'">'+q+"</button>").click(function(a){s.hasClass(n+"-state-disabled")||(j(a),(s.hasClass(n+"-state-active")||s.hasClass(n+"-state-disabled"))&&s.removeClass(n+"-state-hover"))}).mousedown(function(){s.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-down")}).mouseup(function(){s.removeClass(n+"-state-down")}).hover(function(){s.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-hover")},function(){s.removeClass(n+"-state-hover").removeClass(n+"-state-down")}),g=g.add(s)))}),h&&g.first().addClass(n+"-corner-left").end().last().addClass(n+"-corner-right").end(),g.length>1?(f=a("<div/>"),h&&f.addClass("fc-button-group"),f.append(g),e.append(f)):e.append(g)}),e}function g(a){o.find("h2").text(a)}function h(a){o.find(".fc-"+a+"-button").addClass(n+"-state-active")}function i(a){o.find(".fc-"+a+"-button").removeClass(n+"-state-active")}function j(a){o.find(".fc-"+a+"-button").attr("disabled","disabled").addClass(n+"-state-disabled")}function k(a){o.find(".fc-"+a+"-button").removeAttr("disabled").removeClass(n+"-state-disabled")}function l(){return p}var m=this;m.render=d,m.removeElement=e,m.updateTitle=g,m.activateButton=h,m.deactivateButton=i,m.disableButton=j,m.enableButton=k,m.getViewsWithButtons=l;var n,o=a(),p=[]}function Ka(c){function d(a,b){return!M||a.clone().stripZone()<M.clone().stripZone()||b.clone().stripZone()>N.clone().stripZone()}function e(a,b){M=a,N=b,U=[];var c=++S,d=R.length;T=d;for(var e=0;d>e;e++)f(R[e],c)}function f(b,c){g(b,function(d){var e,f,g,h=a.isArray(b.events);if(c==S){if(d)for(e=0;e<d.length;e++)f=d[e],g=h?f:s(f,b),g&&U.push.apply(U,x(g));T--,T||O(U)}})}function g(b,d){var e,f,h=Ma.sourceFetchers;for(e=0;e<h.length;e++){if(f=h[e].call(L,b,M.clone(),N.clone(),c.timezone,d),f===!0)return;if("object"==typeof f)return void g(f,d)}var i=b.events;if(i)a.isFunction(i)?(L.pushLoading(),i.call(L,M.clone(),N.clone(),c.timezone,function(a){d(a),L.popLoading()})):a.isArray(i)?d(i):d();else{var j=b.url;if(j){var k,l=b.success,m=b.error,n=b.complete;k=a.isFunction(b.data)?b.data():b.data;var o=a.extend({},k||{}),p=X(b.startParam,c.startParam),q=X(b.endParam,c.endParam),r=X(b.timezoneParam,c.timezoneParam);p&&(o[p]=M.format()),q&&(o[q]=N.format()),c.timezone&&"local"!=c.timezone&&(o[r]=c.timezone),L.pushLoading(),a.ajax(a.extend({},sb,b,{data:o,success:function(b){b=b||[];var c=W(l,this,arguments);a.isArray(c)&&(b=c),d(b)},error:function(){W(m,this,arguments),d()},complete:function(){W(n,this,arguments),L.popLoading()}}))}else d()}}function h(a){var b=i(a);b&&(R.push(b),T++,f(b,S))}function i(b){var c,d,e=Ma.sourceNormalizers;if(a.isFunction(b)||a.isArray(b)?c={events:b}:"string"==typeof b?c={url:b}:"object"==typeof b&&(c=a.extend({},b)),c){for(c.className?"string"==typeof c.className&&(c.className=c.className.split(/\s+/)):c.className=[],a.isArray(c.events)&&(c.origArray=c.events,c.events=a.map(c.events,function(a){return s(a,c)})),d=0;d<e.length;d++)e[d].call(L,c);return c}}function j(b){R=a.grep(R,function(a){return!k(a,b)}),U=a.grep(U,function(a){return!k(a.source,b)}),O(U)}function k(a,b){return a&&b&&l(a)==l(b)}function l(a){return("object"==typeof a?a.origArray||a.googleCalendarId||a.url||a.events:null)||a}function m(a){a.start=L.moment(a.start),a.end?a.end=L.moment(a.end):a.end=null,y(a,n(a)),O(U)}function n(b){var c={};return a.each(b,function(a,b){o(a)&&void 0!==b&&V(b)&&(c[a]=b)}),c}function o(a){return!/^_|^(id|allDay|start|end)$/.test(a)}function p(a,b){var c,d,e,f=s(a);if(f){for(c=x(f),d=0;d<c.length;d++)e=c[d],e.source||(b&&(Q.events.push(e),e.source=Q),U.push(e));return O(U),c}return[]}function q(b){var c,d;for(null==b?b=function(){return!0}:a.isFunction(b)||(c=b+"",b=function(a){return a._id==c}),U=a.grep(U,b,!0),d=0;d<R.length;d++)a.isArray(R[d].events)&&(R[d].events=a.grep(R[d].events,b,!0));O(U)}function r(b){return a.isFunction(b)?a.grep(U,b):null!=b?(b+="",a.grep(U,function(a){return a._id==b})):U}function s(d,e){var f,g,h,i={};if(c.eventDataTransform&&(d=c.eventDataTransform(d)),e&&e.eventDataTransform&&(d=e.eventDataTransform(d)),a.extend(i,d),e&&(i.source=e),i._id=d._id||(void 0===d.id?"_fc"+tb++:d.id+""),d.className?"string"==typeof d.className?i.className=d.className.split(/\s+/):i.className=d.className:i.className=[],f=d.start||d.date,g=d.end,P(f)&&(f=b.duration(f)),P(g)&&(g=b.duration(g)),d.dow||b.isDuration(f)||b.isDuration(g))i.start=f?b.duration(f):null,i.end=g?b.duration(g):null,i._recurring=!0;else{if(f&&(f=L.moment(f),!f.isValid()))return!1;g&&(g=L.moment(g),g.isValid()||(g=null)),h=d.allDay,void 0===h&&(h=X(e?e.allDayDefault:void 0,c.allDayDefault)),t(f,g,h,i)}return i}function t(a,b,c,d){d.start=a,d.end=b,d.allDay=c,u(d),La(d)}function u(a){v(a),a.end&&!a.end.isAfter(a.start)&&(a.end=null),a.end||(c.forceEventDuration?a.end=L.getDefaultEventEnd(a.allDay,a.start):a.end=null)}function v(a){null==a.allDay&&(a.allDay=!(a.start.hasTime()||a.end&&a.end.hasTime())),a.allDay?(a.start.stripTime(),a.end&&a.end.stripTime()):(a.start.hasTime()||(a.start=L.rezoneDate(a.start)),a.end&&!a.end.hasTime()&&(a.end=L.rezoneDate(a.end)))}function w(b){var c;return b.end||(c=b.allDay,null==c&&(c=!b.start.hasTime()),b=a.extend({},b),b.end=L.getDefaultEventEnd(c,b.start)),b}function x(b,c,d){var e,f,g,h,i,j,k,l,m,n=[];if(c=c||M,d=d||N,b)if(b._recurring){if(f=b.dow)for(e={},g=0;g<f.length;g++)e[f[g]]=!0;for(h=c.clone().stripTime();h.isBefore(d);)(!e||e[h.day()])&&(i=b.start,j=b.end,k=h.clone(),l=null,i&&(k=k.time(i)),j&&(l=h.clone().time(j)),m=a.extend({},b),t(k,l,!i&&!j,m),n.push(m)),h.add(1,"days")}else n.push(b);return n}function y(b,c,d){function e(a,b){return d?H(a,b,d):c.allDay?G(a,b):F(a,b)}var f,g,h,i,j,k,l={};return c=c||{},c.start||(c.start=b.start.clone()),void 0===c.end&&(c.end=b.end?b.end.clone():null),null==c.allDay&&(c.allDay=b.allDay),u(c),f={start:b._start.clone(),end:b._end?b._end.clone():L.getDefaultEventEnd(b._allDay,b._start),allDay:c.allDay},u(f),g=null!==b._end&&null===c.end,h=e(c.start,f.start),c.end?(i=e(c.end,f.end),j=i.subtract(h)):j=null,a.each(c,function(a,b){o(a)&&void 0!==b&&(l[a]=b)}),k=z(r(b._id),g,c.allDay,h,j,l),{dateDelta:h,durationDelta:j,undo:k}}function z(b,c,d,e,f,g){var h=L.getIsAmbigTimezone(),i=[];return e&&!e.valueOf()&&(e=null),f&&!f.valueOf()&&(f=null),a.each(b,function(b,j){var k,l;k={start:j.start.clone(),end:j.end?j.end.clone():null,allDay:j.allDay},a.each(g,function(a){k[a]=j[a]}),l={start:j._start,end:j._end,allDay:d},u(l),c?l.end=null:f&&!l.end&&(l.end=L.getDefaultEventEnd(l.allDay,l.start)),e&&(l.start.add(e),l.end&&l.end.add(e)),f&&l.end.add(f),h&&!l.allDay&&(e||f)&&(l.start.stripZone(),l.end&&l.end.stripZone()),a.extend(j,g,l),La(j),i.push(function(){a.extend(j,k),La(j)})}),function(){for(var a=0;a<i.length;a++)i[a]()}}function A(b){var d,e=c.businessHours,f={className:"fc-nonbusiness",start:"09:00",end:"17:00",dow:[1,2,3,4,5],rendering:"inverse-background"},g=L.getView();return e&&(d=a.extend({},f,"object"==typeof e?e:{})),d?(b&&(d.start=null,d.end=null),x(s(d),g.start,g.end)):[]}function B(a,b){var d=b.source||{},e=X(b.constraint,d.constraint,c.eventConstraint),f=X(b.overlap,d.overlap,c.eventOverlap);return a=w(a),E(a,e,f,b)}function C(a){return E(a,c.selectConstraint,c.selectOverlap)}function D(b,c){var d,e;return c&&(d=a.extend({},c,b),e=x(s(d))[0]),e?B(b,e):(b=w(b),C(b))}function E(b,c,d,e){var f,g,h,i,j,k;if(b=a.extend({},b),b.start=b.start.clone().stripZone(),b.end=b.end.clone().stripZone(),null!=c){for(f=I(c),g=!1,i=0;i<f.length;i++)if(J(f[i],b)){g=!0;break}if(!g)return!1}for(h=L.getPeerEvents(e,b),i=0;i<h.length;i++)if(j=h[i],K(j,b)){if(d===!1)return!1;if("function"==typeof d&&!d(j,e))return!1;if(e){if(k=X(j.overlap,(j.source||{}).overlap),k===!1)return!1;if("function"==typeof k&&!k(e,j))return!1}}return!0}function I(a){return"businessHours"===a?A():"object"==typeof a?x(s(a)):r(a)}function J(a,b){var c=a.start.clone().stripZone(),d=L.getEventEnd(a).stripZone();return b.start>=c&&b.end<=d}function K(a,b){var c=a.start.clone().stripZone(),d=L.getEventEnd(a).stripZone();return b.start<d&&b.end>c}var L=this;L.isFetchNeeded=d,L.fetchEvents=e,L.addEventSource=h,L.removeEventSource=j,L.updateEvent=m,L.renderEvent=p,L.removeEvents=q,L.clientEvents=r,L.mutateEvent=y,L.normalizeEventRange=u,L.normalizeEventRangeTimes=v,L.ensureVisibleEventRange=w;var M,N,O=L.reportEvents,Q={events:[]},R=[Q],S=0,T=0,U=[];a.each((c.events?[c.events]:[]).concat(c.eventSources||[]),function(a,b){var c=i(b);c&&R.push(c)}),L.getBusinessHoursEvents=A,L.isEventRangeAllowed=B,L.isSelectionRangeAllowed=C,L.isExternalDropRangeAllowed=D,L.getEventCache=function(){return U}}function La(a){a._allDay=a.allDay,a._start=a.start.clone(),a._end=a.end?a.end.clone():null}var Ma=a.fullCalendar={version:"2.4.0"},Na=Ma.views={};a.fn.fullCalendar=function(b){var c=Array.prototype.slice.call(arguments,1),d=this;return this.each(function(e,f){var g,h=a(f),i=h.data("fullCalendar");"string"==typeof b?i&&a.isFunction(i[b])&&(g=i[b].apply(i,c),e||(d=g),"destroy"===b&&h.removeData("fullCalendar")):i||(i=new nb(h,b),h.data("fullCalendar",i),i.render())}),d};var Oa=["header","buttonText","buttonIcons","themeButtonIcons"];Ma.intersectionToSeg=E,Ma.applyAll=W,Ma.debounce=da,Ma.isInt=ba,Ma.htmlEscape=Y,Ma.cssToStr=$,Ma.proxy=ca,Ma.capitaliseFirstLetter=_,Ma.getClientRect=p,Ma.getContentRect=q,Ma.getScrollbarWidths=r;var Pa=null;Ma.intersectRects=w,Ma.parseFieldSpecs=A,Ma.compareByFieldSpecs=B,Ma.compareByFieldSpec=C,Ma.flexibleCompare=D,Ma.computeIntervalUnit=I,Ma.divideRangeByDuration=K,Ma.divideDurationByDuration=L,Ma.multiplyDuration=M,Ma.durationHasTime=N;var Qa=["sun","mon","tue","wed","thu","fri","sat"],Ra=["year","month","week","day","hour","minute","second","millisecond"];Ma.log=function(){var a=window.console;return a&&a.log?a.log.apply(a,arguments):void 0},Ma.warn=function(){var a=window.console;return a&&a.warn?a.warn.apply(a,arguments):Ma.log.apply(Ma,arguments)};var Sa,Ta,Ua,Va={}.hasOwnProperty,Wa=/^\s*\d{4}-\d\d$/,Xa=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/,Ya=b.fn,Za=a.extend({},Ya);Ma.moment=function(){return ea(arguments)},Ma.moment.utc=function(){var a=ea(arguments,!0);return a.hasTime()&&a.utc(),a},Ma.moment.parseZone=function(){return ea(arguments,!0,!0)},Ya.clone=function(){var a=Za.clone.apply(this,arguments);return ga(this,a),this._fullCalendar&&(a._fullCalendar=!0),a},Ya.week=Ya.weeks=function(a){var b=(this._locale||this._lang)._fullCalendar_weekCalc;return null==a&&"function"==typeof b?b(this):"ISO"===b?Za.isoWeek.apply(this,arguments):Za.week.apply(this,arguments)},Ya.time=function(a){if(!this._fullCalendar)return Za.time.apply(this,arguments);if(null==a)return b.duration({hours:this.hours(),minutes:this.minutes(),seconds:this.seconds(),milliseconds:this.milliseconds()});this._ambigTime=!1,b.isDuration(a)||b.isMoment(a)||(a=b.duration(a));var c=0;return b.isDuration(a)&&(c=24*Math.floor(a.asDays())),this.hours(c+a.hours()).minutes(a.minutes()).seconds(a.seconds()).milliseconds(a.milliseconds())},Ya.stripTime=function(){var a;return this._ambigTime||(a=this.toArray(),this.utc(),Ta(this,a.slice(0,3)),this._ambigTime=!0,this._ambigZone=!0),this},Ya.hasTime=function(){return!this._ambigTime},Ya.stripZone=function(){var a,b;return this._ambigZone||(a=this.toArray(),b=this._ambigTime,this.utc(),Ta(this,a),this._ambigTime=b||!1,this._ambigZone=!0),this},Ya.hasZone=function(){return!this._ambigZone},Ya.local=function(){var a=this.toArray(),b=this._ambigZone;return Za.local.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,b&&Ua(this,a),this},Ya.utc=function(){return Za.utc.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,this},a.each(["zone","utcOffset"],function(a,b){Za[b]&&(Ya[b]=function(a){return null!=a&&(this._ambigTime=!1,this._ambigZone=!1),Za[b].apply(this,arguments)})}),Ya.format=function(){return this._fullCalendar&&arguments[0]?ja(this,arguments[0]):this._ambigTime?ia(this,"YYYY-MM-DD"):this._ambigZone?ia(this,"YYYY-MM-DD[T]HH:mm:ss"):Za.format.apply(this,arguments)},Ya.toISOString=function(){return this._ambigTime?ia(this,"YYYY-MM-DD"):this._ambigZone?ia(this,"YYYY-MM-DD[T]HH:mm:ss"):Za.toISOString.apply(this,arguments)},Ya.isWithin=function(a,b){var c=fa([this,a,b]);return c[0]>=c[1]&&c[0]<c[2]},Ya.isSame=function(a,b){var c;return this._fullCalendar?b?(c=fa([this,a],!0),Za.isSame.call(c[0],c[1],b)):(a=Ma.moment.parseZone(a),Za.isSame.call(this,a)&&Boolean(this._ambigTime)===Boolean(a._ambigTime)&&Boolean(this._ambigZone)===Boolean(a._ambigZone)):Za.isSame.apply(this,arguments)},a.each(["isBefore","isAfter"],function(a,b){Ya[b]=function(a,c){var d;return this._fullCalendar?(d=fa([this,a]),Za[b].call(d[0],d[1],c)):Za[b].apply(this,arguments)}}),Sa="_d"in b()&&"updateOffset"in b,Ta=Sa?function(a,c){a._d.setTime(Date.UTC.apply(Date,c)),b.updateOffset(a,!1)}:ha,Ua=Sa?function(a,c){a._d.setTime(+new Date(c[0]||0,c[1]||0,c[2]||0,c[3]||0,c[4]||0,c[5]||0,c[6]||0)),b.updateOffset(a,!1)}:ha;var $a={t:function(a){return ia(a,"a").charAt(0)},T:function(a){return ia(a,"A").charAt(0)}};Ma.formatRange=ma;var _a={Y:"year",M:"month",D:"day",d:"day",A:"second",a:"second",T:"second",t:"second",H:"second",h:"second",m:"second",s:"second"},ab={};Ma.Class=ra,ra.extend=function(a){var b,c=this;return a=a||{},U(a,"constructor")&&(b=a.constructor),"function"!=typeof b&&(b=a.constructor=function(){c.apply(this,arguments)}),b.prototype=R(c.prototype),S(a,b.prototype),T(a,b.prototype),S(c,b),b},ra.mixin=function(a){S(a.prototype||a,this.prototype)};var bb=Ma.Emitter=ra.extend({callbackHash:null,on:function(a,b){return this.getCallbacks(a).add(b),this},off:function(a,b){return this.getCallbacks(a).remove(b),this},trigger:function(a){var b=Array.prototype.slice.call(arguments,1);return this.triggerWith(a,this,b),this},triggerWith:function(a,b,c){var d=this.getCallbacks(a);return d.fireWith(b,c),this},getCallbacks:function(b){var c;return this.callbackHash||(this.callbackHash={}),c=this.callbackHash[b],c||(c=this.callbackHash[b]=a.Callbacks()),c}}),cb=ra.extend({isHidden:!0,options:null,el:null,documentMousedownProxy:null,margin:10,constructor:function(a){this.options=a||{}},show:function(){this.isHidden&&(this.el||this.render(),this.el.show(),this.position(),this.isHidden=!1,this.trigger("show"))},hide:function(){this.isHidden||(this.el.hide(),this.isHidden=!0,this.trigger("hide"))},render:function(){var b=this,c=this.options;this.el=a('<div class="fc-popover"/>').addClass(c.className||"").css({top:0,left:0}).append(c.content).appendTo(c.parentEl),this.el.on("click",".fc-close",function(){b.hide()}),c.autoHide&&a(document).on("mousedown",this.documentMousedownProxy=ca(this,"documentMousedown"))},documentMousedown:function(b){this.el&&!a(b.target).closest(this.el).length&&this.hide()},removeElement:function(){this.hide(),this.el&&(this.el.remove(),this.el=null),a(document).off("mousedown",this.documentMousedownProxy)},position:function(){var b,c,d,e,f,g=this.options,h=this.el.offsetParent().offset(),i=this.el.outerWidth(),j=this.el.outerHeight(),k=a(window),l=n(this.el);e=g.top||0,f=void 0!==g.left?g.left:void 0!==g.right?g.right-i:0,l.is(window)||l.is(document)?(l=k,b=0,c=0):(d=l.offset(),b=d.top,c=d.left),b+=k.scrollTop(),c+=k.scrollLeft(),g.viewportConstrain!==!1&&(e=Math.min(e,b+l.outerHeight()-j-this.margin),e=Math.max(e,b+this.margin),f=Math.min(f,c+l.outerWidth()-i-this.margin),f=Math.max(f,c+this.margin)),this.el.css({top:e-h.top,left:f-h.left})},trigger:function(a){this.options[a]&&this.options[a].apply(this,Array.prototype.slice.call(arguments,1));
7
- }}),db=ra.extend({grid:null,rowCoords:null,colCoords:null,containerEl:null,bounds:null,constructor:function(a){this.grid=a},build:function(){this.grid.build(),this.rowCoords=this.grid.computeRowCoords(),this.colCoords=this.grid.computeColCoords(),this.computeBounds()},clear:function(){this.grid.clear(),this.rowCoords=null,this.colCoords=null},getCell:function(b,c){var d,e,f,g=this.rowCoords,h=g.length,i=this.colCoords,j=i.length,k=null,l=null;if(this.inBounds(b,c)){for(d=0;h>d;d++)if(e=g[d],c>=e.top&&c<e.bottom){k=d;break}for(d=0;j>d;d++)if(e=i[d],b>=e.left&&b<e.right){l=d;break}if(null!==k&&null!==l)return f=this.grid.getCell(k,l),f.grid=this.grid,a.extend(f,g[k],i[l]),f}return null},computeBounds:function(){this.bounds=this.containerEl?p(this.containerEl):null},inBounds:function(a,b){var c=this.bounds;return c?a>=c.left&&a<c.right&&b>=c.top&&b<c.bottom:!0}}),eb=ra.extend({coordMaps:null,constructor:function(a){this.coordMaps=a},build:function(){var a,b=this.coordMaps;for(a=0;a<b.length;a++)b[a].build()},getCell:function(a,b){var c,d=this.coordMaps,e=null;for(c=0;c<d.length&&!e;c++)e=d[c].getCell(a,b);return e},clear:function(){var a,b=this.coordMaps;for(a=0;a<b.length;a++)b[a].clear()}}),fb=Ma.DragListener=ra.extend({options:null,isListening:!1,isDragging:!1,originX:null,originY:null,mousemoveProxy:null,mouseupProxy:null,subjectEl:null,subjectHref:null,scrollEl:null,scrollBounds:null,scrollTopVel:null,scrollLeftVel:null,scrollIntervalId:null,scrollHandlerProxy:null,scrollSensitivity:30,scrollSpeed:200,scrollIntervalMs:50,constructor:function(a){a=a||{},this.options=a,this.subjectEl=a.subjectEl},mousedown:function(a){v(a)&&(a.preventDefault(),this.startListening(a),this.options.distance||this.startDrag(a))},startListening:function(b){var c;this.isListening||(b&&this.options.scroll&&(c=n(a(b.target)),c.is(window)||c.is(document)||(this.scrollEl=c,this.scrollHandlerProxy=da(ca(this,"scrollHandler"),100),this.scrollEl.on("scroll",this.scrollHandlerProxy))),a(document).on("mousemove",this.mousemoveProxy=ca(this,"mousemove")).on("mouseup",this.mouseupProxy=ca(this,"mouseup")).on("selectstart",this.preventDefault),b?(this.originX=b.pageX,this.originY=b.pageY):(this.originX=0,this.originY=0),this.isListening=!0,this.listenStart(b))},listenStart:function(a){this.trigger("listenStart",a)},mousemove:function(a){var b,c,d=a.pageX-this.originX,e=a.pageY-this.originY;this.isDragging||(b=this.options.distance||1,c=d*d+e*e,c>=b*b&&this.startDrag(a)),this.isDragging&&this.drag(d,e,a)},startDrag:function(a){this.isListening||this.startListening(),this.isDragging||(this.isDragging=!0,this.dragStart(a))},dragStart:function(a){var b=this.subjectEl;this.trigger("dragStart",a),(this.subjectHref=b?b.attr("href"):null)&&b.removeAttr("href")},drag:function(a,b,c){this.trigger("drag",a,b,c),this.updateScroll(c)},mouseup:function(a){this.stopListening(a)},stopDrag:function(a){this.isDragging&&(this.stopScrolling(),this.dragStop(a),this.isDragging=!1)},dragStop:function(a){var b=this;this.trigger("dragStop",a),setTimeout(function(){b.subjectHref&&b.subjectEl.attr("href",b.subjectHref)},0)},stopListening:function(b){this.stopDrag(b),this.isListening&&(this.scrollEl&&(this.scrollEl.off("scroll",this.scrollHandlerProxy),this.scrollHandlerProxy=null),a(document).off("mousemove",this.mousemoveProxy).off("mouseup",this.mouseupProxy).off("selectstart",this.preventDefault),this.mousemoveProxy=null,this.mouseupProxy=null,this.isListening=!1,this.listenStop(b))},listenStop:function(a){this.trigger("listenStop",a)},trigger:function(a){this.options[a]&&this.options[a].apply(this,Array.prototype.slice.call(arguments,1))},preventDefault:function(a){a.preventDefault()},computeScrollBounds:function(){var a=this.scrollEl;this.scrollBounds=a?o(a):null},updateScroll:function(a){var b,c,d,e,f=this.scrollSensitivity,g=this.scrollBounds,h=0,i=0;g&&(b=(f-(a.pageY-g.top))/f,c=(f-(g.bottom-a.pageY))/f,d=(f-(a.pageX-g.left))/f,e=(f-(g.right-a.pageX))/f,b>=0&&1>=b?h=b*this.scrollSpeed*-1:c>=0&&1>=c&&(h=c*this.scrollSpeed),d>=0&&1>=d?i=d*this.scrollSpeed*-1:e>=0&&1>=e&&(i=e*this.scrollSpeed)),this.setScrollVel(h,i)},setScrollVel:function(a,b){this.scrollTopVel=a,this.scrollLeftVel=b,this.constrainScrollVel(),!this.scrollTopVel&&!this.scrollLeftVel||this.scrollIntervalId||(this.scrollIntervalId=setInterval(ca(this,"scrollIntervalFunc"),this.scrollIntervalMs))},constrainScrollVel:function(){var a=this.scrollEl;this.scrollTopVel<0?a.scrollTop()<=0&&(this.scrollTopVel=0):this.scrollTopVel>0&&a.scrollTop()+a[0].clientHeight>=a[0].scrollHeight&&(this.scrollTopVel=0),this.scrollLeftVel<0?a.scrollLeft()<=0&&(this.scrollLeftVel=0):this.scrollLeftVel>0&&a.scrollLeft()+a[0].clientWidth>=a[0].scrollWidth&&(this.scrollLeftVel=0)},scrollIntervalFunc:function(){var a=this.scrollEl,b=this.scrollIntervalMs/1e3;this.scrollTopVel&&a.scrollTop(a.scrollTop()+this.scrollTopVel*b),this.scrollLeftVel&&a.scrollLeft(a.scrollLeft()+this.scrollLeftVel*b),this.constrainScrollVel(),this.scrollTopVel||this.scrollLeftVel||this.stopScrolling()},stopScrolling:function(){this.scrollIntervalId&&(clearInterval(this.scrollIntervalId),this.scrollIntervalId=null,this.scrollStop())},scrollHandler:function(){this.scrollIntervalId||this.scrollStop()},scrollStop:function(){}}),gb=fb.extend({coordMap:null,origCell:null,cell:null,coordAdjust:null,constructor:function(a,b){fb.prototype.constructor.call(this,b),this.coordMap=a},listenStart:function(a){var b,c,d,e=this.subjectEl;fb.prototype.listenStart.apply(this,arguments),this.computeCoords(),a?(c={left:a.pageX,top:a.pageY},d=c,e&&(b=o(e),d=x(d,b)),this.origCell=this.getCell(d.left,d.top),e&&this.options.subjectCenter&&(this.origCell&&(b=w(this.origCell,b)||b),d=y(b)),this.coordAdjust=z(d,c)):(this.origCell=null,this.coordAdjust=null)},computeCoords:function(){this.coordMap.build(),this.computeScrollBounds()},dragStart:function(a){var b;fb.prototype.dragStart.apply(this,arguments),b=this.getCell(a.pageX,a.pageY),b&&this.cellOver(b)},drag:function(a,b,c){var d;fb.prototype.drag.apply(this,arguments),d=this.getCell(c.pageX,c.pageY),sa(d,this.cell)||(this.cell&&this.cellOut(),d&&this.cellOver(d))},dragStop:function(){this.cellDone(),fb.prototype.dragStop.apply(this,arguments)},cellOver:function(a){this.cell=a,this.trigger("cellOver",a,sa(a,this.origCell),this.origCell)},cellOut:function(){this.cell&&(this.trigger("cellOut",this.cell),this.cellDone(),this.cell=null)},cellDone:function(){this.cell&&this.trigger("cellDone",this.cell)},listenStop:function(){fb.prototype.listenStop.apply(this,arguments),this.origCell=this.cell=null,this.coordMap.clear()},scrollStop:function(){fb.prototype.scrollStop.apply(this,arguments),this.computeCoords()},getCell:function(a,b){return this.coordAdjust&&(a+=this.coordAdjust.left,b+=this.coordAdjust.top),this.coordMap.getCell(a,b)}}),hb=ra.extend({options:null,sourceEl:null,el:null,parentEl:null,top0:null,left0:null,mouseY0:null,mouseX0:null,topDelta:null,leftDelta:null,mousemoveProxy:null,isFollowing:!1,isHidden:!1,isAnimating:!1,constructor:function(b,c){this.options=c=c||{},this.sourceEl=b,this.parentEl=c.parentEl?a(c.parentEl):b.parent()},start:function(b){this.isFollowing||(this.isFollowing=!0,this.mouseY0=b.pageY,this.mouseX0=b.pageX,this.topDelta=0,this.leftDelta=0,this.isHidden||this.updatePosition(),a(document).on("mousemove",this.mousemoveProxy=ca(this,"mousemove")))},stop:function(b,c){function d(){this.isAnimating=!1,e.removeElement(),this.top0=this.left0=null,c&&c()}var e=this,f=this.options.revertDuration;this.isFollowing&&!this.isAnimating&&(this.isFollowing=!1,a(document).off("mousemove",this.mousemoveProxy),b&&f&&!this.isHidden?(this.isAnimating=!0,this.el.animate({top:this.top0,left:this.left0},{duration:f,complete:d})):d())},getEl:function(){var a=this.el;return a||(this.sourceEl.width(),a=this.el=this.sourceEl.clone().css({position:"absolute",visibility:"",display:this.isHidden?"none":"",margin:0,right:"auto",bottom:"auto",width:this.sourceEl.width(),height:this.sourceEl.height(),opacity:this.options.opacity||"",zIndex:this.options.zIndex}).appendTo(this.parentEl)),a},removeElement:function(){this.el&&(this.el.remove(),this.el=null)},updatePosition:function(){var a,b;this.getEl(),null===this.top0&&(this.sourceEl.width(),a=this.sourceEl.offset(),b=this.el.offsetParent().offset(),this.top0=a.top-b.top,this.left0=a.left-b.left),this.el.css({top:this.top0+this.topDelta,left:this.left0+this.leftDelta})},mousemove:function(a){this.topDelta=a.pageY-this.mouseY0,this.leftDelta=a.pageX-this.mouseX0,this.isHidden||this.updatePosition()},hide:function(){this.isHidden||(this.isHidden=!0,this.el&&this.el.hide())},show:function(){this.isHidden&&(this.isHidden=!1,this.updatePosition(),this.getEl().show())}}),ib=ra.extend({view:null,isRTL:null,cellHtml:"<td/>",constructor:function(a){this.view=a,this.isRTL=a.opt("isRTL")},rowHtml:function(a,b){var c,d,e=this.getHtmlRenderer("cell",a),f="";for(b=b||0,c=0;c<this.colCnt;c++)d=this.getCell(b,c),f+=e(d);return f=this.bookendCells(f,a,b),"<tr>"+f+"</tr>"},bookendCells:function(a,b,c){var d=this.getHtmlRenderer("intro",b)(c||0),e=this.getHtmlRenderer("outro",b)(c||0),f=this.isRTL?e:d,g=this.isRTL?d:e;return"string"==typeof a?f+a+g:a.prepend(f).append(g)},getHtmlRenderer:function(a,b){var c,d,e,f,g=this.view;return c=a+"Html",b&&(d=b+_(a)+"Html"),d&&(f=g[d])?e=g:d&&(f=this[d])?e=this:(f=g[c])?e=g:(f=this[c])&&(e=this),"function"==typeof f?function(){return f.apply(e,arguments)||""}:function(){return f||""}}}),jb=Ma.Grid=ib.extend({start:null,end:null,rowCnt:0,colCnt:0,el:null,coordMap:null,elsByFill:null,externalDragStartProxy:null,colHeadFormat:null,eventTimeFormat:null,displayEventTime:null,displayEventEnd:null,cellDuration:null,largeUnit:null,constructor:function(){ib.apply(this,arguments),this.coordMap=new db(this),this.elsByFill={},this.externalDragStartProxy=ca(this,"externalDragStart")},computeColHeadFormat:function(){},computeEventTimeFormat:function(){return this.view.opt("smallTimeFormat")},computeDisplayEventTime:function(){return!0},computeDisplayEventEnd:function(){return!0},setRange:function(a){this.start=a.start.clone(),this.end=a.end.clone(),this.rangeUpdated(),this.processRangeOptions()},rangeUpdated:function(){},processRangeOptions:function(){var a,b,c=this.view;this.colHeadFormat=c.opt("columnFormat")||this.computeColHeadFormat(),this.eventTimeFormat=c.opt("eventTimeFormat")||c.opt("timeFormat")||this.computeEventTimeFormat(),a=c.opt("displayEventTime"),null==a&&(a=this.computeDisplayEventTime()),b=c.opt("displayEventEnd"),null==b&&(b=this.computeDisplayEventEnd()),this.displayEventTime=a,this.displayEventEnd=b},build:function(){},clear:function(){},rangeToSegs:function(a){},diffDates:function(a,b){return this.largeUnit?H(a,b,this.largeUnit):F(a,b)},getCell:function(b,c){var d;return null==c&&("number"==typeof b?(c=b%this.colCnt,b=Math.floor(b/this.colCnt)):(c=b.col,b=b.row)),d={row:b,col:c},a.extend(d,this.getRowData(b),this.getColData(c)),a.extend(d,this.computeCellRange(d)),d},computeCellRange:function(a){var b=this.computeCellDate(a);return{start:b,end:b.clone().add(this.cellDuration)}},computeCellDate:function(a){},getRowData:function(a){return{}},getColData:function(a){return{}},getRowEl:function(a){},getColEl:function(a){},getCellDayEl:function(a){return this.getColEl(a.col)||this.getRowEl(a.row)},computeRowCoords:function(){var a,b,c,d=[];for(a=0;a<this.rowCnt;a++)b=this.getRowEl(a),c=b.offset().top,d.push({top:c,bottom:c+b.outerHeight()});return d},computeColCoords:function(){var a,b,c,d=[];for(a=0;a<this.colCnt;a++)b=this.getColEl(a),c=b.offset().left,d.push({left:c,right:c+b.outerWidth()});return d},setElement:function(b){var c=this;this.el=b,b.on("mousedown",function(b){a(b.target).is(".fc-event-container *, .fc-more")||a(b.target).closest(".fc-popover").length||c.dayMousedown(b)}),this.bindSegHandlers(),this.bindGlobalHandlers()},removeElement:function(){this.unbindGlobalHandlers(),this.el.remove()},renderSkeleton:function(){},renderDates:function(){},unrenderDates:function(){},bindGlobalHandlers:function(){a(document).on("dragstart sortstart",this.externalDragStartProxy)},unbindGlobalHandlers:function(){a(document).off("dragstart sortstart",this.externalDragStartProxy)},dayMousedown:function(a){var b,c,d=this,e=this.view,f=e.opt("selectable"),i=new gb(this.coordMap,{scroll:e.opt("dragScroll"),dragStart:function(){e.unselect()},cellOver:function(a,e,h){h&&(b=e?a:null,f&&(c=d.computeSelection(h,a),c?d.renderSelection(c):g()))},cellOut:function(a){b=null,c=null,d.unrenderSelection(),h()},listenStop:function(a){b&&e.triggerDayClick(b,d.getCellDayEl(b),a),c&&e.reportSelection(c,a),h()}});i.mousedown(a)},renderRangeHelper:function(a,b){var c=this.fabricateHelperEvent(a,b);this.renderHelper(c,b)},fabricateHelperEvent:function(a,b){var c=b?R(b.event):{};return c.start=a.start.clone(),c.end=a.end?a.end.clone():null,c.allDay=null,this.view.calendar.normalizeEventRange(c),c.className=(c.className||[]).concat("fc-helper"),b||(c.editable=!1),c},renderHelper:function(a,b){},unrenderHelper:function(){},renderSelection:function(a){this.renderHighlight(this.selectionRangeToSegs(a))},unrenderSelection:function(){this.unrenderHighlight()},computeSelection:function(a,b){var c,d=[a.start,a.end,b.start,b.end];return d.sort(aa),c={start:d[0].clone(),end:d[3].clone()},this.view.calendar.isSelectionRangeAllowed(c)?c:null},selectionRangeToSegs:function(a){return this.rangeToSegs(a)},renderHighlight:function(a){this.renderFill("highlight",a)},unrenderHighlight:function(){this.unrenderFill("highlight")},highlightSegClasses:function(){return["fc-highlight"]},renderFill:function(a,b){},unrenderFill:function(a){var b=this.elsByFill[a];b&&(b.remove(),delete this.elsByFill[a])},renderFillSegEls:function(b,c){var d,e=this,f=this[b+"SegEl"],g="",h=[];if(c.length){for(d=0;d<c.length;d++)g+=this.fillSegHtml(b,c[d]);a(g).each(function(b,d){var g=c[b],i=a(d);f&&(i=f.call(e,g,i)),i&&(i=a(i),i.is(e.fillSegTag)&&(g.el=i,h.push(g)))})}return h},fillSegTag:"div",fillSegHtml:function(a,b){var c=this[a+"SegClasses"],d=this[a+"SegCss"],e=c?c.call(this,b):[],f=$(d?d.call(this,b):{});return"<"+this.fillSegTag+(e.length?' class="'+e.join(" ")+'"':"")+(f?' style="'+f+'"':"")+" />"},headHtml:function(){return'<div class="fc-row '+this.view.widgetHeaderClass+'"><table><thead>'+this.rowHtml("head")+"</thead></table></div>"},headCellHtml:function(a){var b=this.view,c=a.start;return'<th class="fc-day-header '+b.widgetHeaderClass+" fc-"+Qa[c.day()]+'">'+Y(c.format(this.colHeadFormat))+"</th>"},bgCellHtml:function(a){var b=this.view,c=a.start,d=this.getDayClasses(c);return d.unshift("fc-day",b.widgetContentClass),'<td class="'+d.join(" ")+'" data-date="'+c.format("YYYY-MM-DD")+'"></td>'},getDayClasses:function(a){var b=this.view,c=b.calendar.getNow().stripTime(),d=["fc-"+Qa[a.day()]];return 1==b.intervalDuration.as("months")&&a.month()!=b.intervalStart.month()&&d.push("fc-other-month"),a.isSame(c,"day")?d.push("fc-today",b.highlightStateClass):c>a?d.push("fc-past"):d.push("fc-future"),d}});jb.mixin({mousedOverSeg:null,isDraggingSeg:!1,isResizingSeg:!1,isDraggingExternal:!1,segs:null,renderEvents:function(a){var b,c,d=this.eventsToSegs(a),e=[],f=[];for(b=0;b<d.length;b++)c=d[b],ta(c.event)?e.push(c):f.push(c);e=this.renderBgSegs(e)||e,f=this.renderFgSegs(f)||f,this.segs=e.concat(f)},unrenderEvents:function(){this.triggerSegMouseout(),this.unrenderFgSegs(),this.unrenderBgSegs(),this.segs=null},getEventSegs:function(){return this.segs||[]},renderFgSegs:function(a){},unrenderFgSegs:function(){},renderFgSegEls:function(b,c){var d,e=this.view,f="",g=[];if(b.length){for(d=0;d<b.length;d++)f+=this.fgSegHtml(b[d],c);a(f).each(function(c,d){var f=b[c],h=e.resolveEventEl(f.event,a(d));h&&(h.data("fc-seg",f),f.el=h,g.push(f))})}return g},fgSegHtml:function(a,b){},renderBgSegs:function(a){return this.renderFill("bgEvent",a)},unrenderBgSegs:function(){this.unrenderFill("bgEvent")},bgEventSegEl:function(a,b){return this.view.resolveEventEl(a.event,b)},bgEventSegClasses:function(a){var b=a.event,c=b.source||{};return["fc-bgevent"].concat(b.className,c.className||[])},bgEventSegCss:function(a){var b=this.view,c=a.event,d=c.source||{};return{"background-color":c.backgroundColor||c.color||d.backgroundColor||d.color||b.opt("eventBackgroundColor")||b.opt("eventColor")}},businessHoursSegClasses:function(a){return["fc-nonbusiness","fc-bgevent"]},bindSegHandlers:function(){var b=this,c=this.view;a.each({mouseenter:function(a,c){b.triggerSegMouseover(a,c)},mouseleave:function(a,c){b.triggerSegMouseout(a,c)},click:function(a,b){return c.trigger("eventClick",this,a.event,b)},mousedown:function(d,e){a(e.target).is(".fc-resizer")&&c.isEventResizable(d.event)?b.segResizeMousedown(d,e,a(e.target).is(".fc-start-resizer")):c.isEventDraggable(d.event)&&b.segDragMousedown(d,e)}},function(c,d){b.el.on(c,".fc-event-container > *",function(c){var e=a(this).data("fc-seg");return!e||b.isDraggingSeg||b.isResizingSeg?void 0:d.call(this,e,c)})})},triggerSegMouseover:function(a,b){this.mousedOverSeg||(this.mousedOverSeg=a,this.view.trigger("eventMouseover",a.el[0],a.event,b))},triggerSegMouseout:function(a,b){b=b||{},this.mousedOverSeg&&(a=a||this.mousedOverSeg,this.mousedOverSeg=null,this.view.trigger("eventMouseout",a.el[0],a.event,b))},segDragMousedown:function(a,b){var c,d=this,e=this.view,f=e.calendar,i=a.el,j=a.event,k=new hb(a.el,{parentEl:e.el,opacity:e.opt("dragOpacity"),revertDuration:e.opt("dragRevertDuration"),zIndex:2}),l=new gb(e.coordMap,{distance:5,scroll:e.opt("dragScroll"),subjectEl:i,subjectCenter:!0,listenStart:function(a){k.hide(),k.start(a)},dragStart:function(b){d.triggerSegMouseout(a,b),d.segDragStart(a,b),e.hideEvent(j)},cellOver:function(b,h,i){a.cell&&(i=a.cell),c=d.computeEventDrop(i,b,j),c&&!f.isEventRangeAllowed(c,j)&&(g(),c=null),c&&e.renderDrag(c,a)?k.hide():k.show(),h&&(c=null)},cellOut:function(){e.unrenderDrag(),k.show(),c=null},cellDone:function(){h()},dragStop:function(b){k.stop(!c,function(){e.unrenderDrag(),e.showEvent(j),d.segDragStop(a,b),c&&e.reportEventDrop(j,c,this.largeUnit,i,b)})},listenStop:function(){k.stop()}});l.mousedown(b)},segDragStart:function(a,b){this.isDraggingSeg=!0,this.view.trigger("eventDragStart",a.el[0],a.event,b,{})},segDragStop:function(a,b){this.isDraggingSeg=!1,this.view.trigger("eventDragStop",a.el[0],a.event,b,{})},computeEventDrop:function(a,b,c){var d,e,f=this.view.calendar,g=a.start,h=b.start;return g.hasTime()===h.hasTime()?(d=this.diffDates(h,g),c.allDay&&N(d)?(e={start:c.start.clone(),end:f.getEventEnd(c),allDay:!1},f.normalizeEventRangeTimes(e)):e={start:c.start.clone(),end:c.end?c.end.clone():null,allDay:c.allDay},e.start.add(d),e.end&&e.end.add(d)):e={start:h.clone(),end:null,allDay:!h.hasTime()},e},applyDragOpacity:function(a){var b=this.view.opt("dragOpacity");null!=b&&a.each(function(a,c){c.style.opacity=b})},externalDragStart:function(b,c){var d,e,f=this.view;f.opt("droppable")&&(d=a((c?c.item:null)||b.target),e=f.opt("dropAccept"),(a.isFunction(e)?e.call(d[0],d):d.is(e))&&(this.isDraggingExternal||this.listenToExternalDrag(d,b,c)))},listenToExternalDrag:function(a,b,c){var d,e,f=this,i=ya(a);d=new gb(this.coordMap,{listenStart:function(){f.isDraggingExternal=!0},cellOver:function(a){e=f.computeExternalDrop(a,i),e?f.renderDrag(e):g()},cellOut:function(){e=null,f.unrenderDrag(),h()},dragStop:function(){f.unrenderDrag(),h(),e&&f.view.reportExternalDrop(i,e,a,b,c)},listenStop:function(){f.isDraggingExternal=!1}}),d.startDrag(b)},computeExternalDrop:function(a,b){var c={start:a.start.clone(),end:null};return b.startTime&&!c.start.hasTime()&&c.start.time(b.startTime),b.duration&&(c.end=c.start.clone().add(b.duration)),this.view.calendar.isExternalDropRangeAllowed(c,b.eventProps)?c:null},renderDrag:function(a,b){},unrenderDrag:function(){},segResizeMousedown:function(a,b,c){var d,e,f=this,i=this.view,j=i.calendar,k=a.el,l=a.event,m=j.getEventEnd(l);d=new gb(this.coordMap,{distance:5,scroll:i.opt("dragScroll"),subjectEl:k,dragStart:function(b){f.triggerSegMouseout(a,b),f.segResizeStart(a,b)},cellOver:function(b,d,h){e=c?f.computeEventStartResize(h,b,l):f.computeEventEndResize(h,b,l),e&&(j.isEventRangeAllowed(e,l)?e.start.isSame(l.start)&&e.end.isSame(m)&&(e=null):(g(),e=null)),e&&(i.hideEvent(l),f.renderEventResize(e,a))},cellOut:function(){e=null},cellDone:function(){f.unrenderEventResize(),i.showEvent(l),h()},dragStop:function(b){f.segResizeStop(a,b),e&&i.reportEventResize(l,e,this.largeUnit,k,b)}}),d.mousedown(b)},segResizeStart:function(a,b){this.isResizingSeg=!0,this.view.trigger("eventResizeStart",a.el[0],a.event,b,{})},segResizeStop:function(a,b){this.isResizingSeg=!1,this.view.trigger("eventResizeStop",a.el[0],a.event,b,{})},computeEventStartResize:function(a,b,c){return this.computeEventResize("start",a,b,c)},computeEventEndResize:function(a,b,c){return this.computeEventResize("end",a,b,c)},computeEventResize:function(a,b,c,d){var e,f,g=this.view.calendar,h=this.diffDates(c[a],b[a]);return e={start:d.start.clone(),end:g.getEventEnd(d),allDay:d.allDay},e.allDay&&N(h)&&(e.allDay=!1,g.normalizeEventRangeTimes(e)),e[a].add(h),e.start.isBefore(e.end)||(f=d.allDay?g.defaultAllDayEventDuration:g.defaultTimedEventDuration,this.cellDuration&&this.cellDuration<f&&(f=this.cellDuration),"start"==a?e.start=e.end.clone().subtract(f):e.end=e.start.clone().add(f)),e},renderEventResize:function(a,b){},unrenderEventResize:function(){},getEventTimeText:function(a,b,c){return null==b&&(b=this.eventTimeFormat),null==c&&(c=this.displayEventEnd),this.displayEventTime&&a.start.hasTime()?c&&a.end?this.view.formatRange(a,b):a.start.format(b):""},getSegClasses:function(a,b,c){var d=a.event,e=["fc-event",a.isStart?"fc-start":"fc-not-start",a.isEnd?"fc-end":"fc-not-end"].concat(d.className,d.source?d.source.className:[]);return b&&e.push("fc-draggable"),c&&e.push("fc-resizable"),e},getEventSkinCss:function(a){var b=this.view,c=a.source||{},d=a.color,e=c.color,f=b.opt("eventColor");return{"background-color":a.backgroundColor||d||c.backgroundColor||e||b.opt("eventBackgroundColor")||f,"border-color":a.borderColor||d||c.borderColor||e||b.opt("eventBorderColor")||f,color:a.textColor||c.textColor||b.opt("eventTextColor")}},eventsToSegs:function(a,b){var c,d=this.eventsToRanges(a),e=[];for(c=0;c<d.length;c++)e.push.apply(e,this.eventRangeToSegs(d[c],b));return e},eventsToRanges:function(b){var c=this,d=wa(b),e=[];return a.each(d,function(a,b){b.length&&e.push.apply(e,ua(b[0])?c.eventsToInverseRanges(b):c.eventsToNormalRanges(b))}),e},eventsToNormalRanges:function(a){var b,c,d,e,f=this.view.calendar,g=[];for(b=0;b<a.length;b++)c=a[b],d=c.start.clone().stripZone(),e=f.getEventEnd(c).stripZone(),g.push({event:c,start:d,end:e,eventStartMS:+d,eventDurationMS:e-d});return g},eventsToInverseRanges:function(a){var b,c,d=this.view,e=d.start.clone().stripZone(),f=d.end.clone().stripZone(),g=this.eventsToNormalRanges(a),h=[],i=a[0],j=e;for(g.sort(xa),b=0;b<g.length;b++)c=g[b],c.start>j&&h.push({event:i,start:j,end:c.start}),j=c.end;return f>j&&h.push({event:i,start:j,end:f}),h},eventRangeToSegs:function(a,b){var c,d,e;for(a=this.view.calendar.ensureVisibleEventRange(a),c=b?b(a):this.rangeToSegs(a),d=0;d<c.length;d++)e=c[d],e.event=a.event,e.eventStartMS=a.eventStartMS,e.eventDurationMS=a.eventDurationMS;return c},sortSegs:function(a){a.sort(ca(this,"compareSegs"))},compareSegs:function(a,b){return a.eventStartMS-b.eventStartMS||b.eventDurationMS-a.eventDurationMS||b.event.allDay-a.event.allDay||B(a.event,b.event,this.view.eventOrderSpecs)}}),Ma.dataAttrPrefix="";var kb=jb.extend({numbersVisible:!1,bottomCoordPadding:0,breakOnWeeks:null,cellDates:null,dayToCellOffsets:null,rowEls:null,dayEls:null,helperEls:null,constructor:function(){jb.apply(this,arguments),this.cellDuration=b.duration(1,"day")},renderDates:function(a){var b,c,d,e=this.view,f=this.rowCnt,g=this.colCnt,h=f*g,i="";for(b=0;f>b;b++)i+=this.dayRowHtml(b,a);for(this.el.html(i),this.rowEls=this.el.find(".fc-row"),this.dayEls=this.el.find(".fc-day"),c=0;h>c;c++)d=this.getCell(c),e.trigger("dayRender",null,d.start,this.dayEls.eq(c))},unrenderDates:function(){this.removeSegPopover()},renderBusinessHours:function(){var a=this.view.calendar.getBusinessHoursEvents(!0),b=this.eventsToSegs(a);this.renderFill("businessHours",b,"bgevent")},dayRowHtml:function(a,b){var c=this.view,d=["fc-row","fc-week",c.widgetContentClass];return b&&d.push("fc-rigid"),'<div class="'+d.join(" ")+'"><div class="fc-bg"><table>'+this.rowHtml("day",a)+'</table></div><div class="fc-content-skeleton"><table>'+(this.numbersVisible?"<thead>"+this.rowHtml("number",a)+"</thead>":"")+"</table></div></div>"},dayCellHtml:function(a){return this.bgCellHtml(a)},computeColHeadFormat:function(){return this.rowCnt>1?"ddd":this.colCnt>1?this.view.opt("dayOfMonthFormat"):"dddd"},computeEventTimeFormat:function(){return this.view.opt("extraSmallTimeFormat")},computeDisplayEventEnd:function(){return 1==this.colCnt},rangeUpdated:function(){var a,b,c,d;if(this.updateCellDates(),a=this.cellDates,this.breakOnWeeks){for(b=a[0].day(),d=1;d<a.length&&a[d].day()!=b;d++);c=Math.ceil(a.length/d)}else c=1,d=a.length;this.rowCnt=c,this.colCnt=d},updateCellDates:function(){for(var a=this.view,b=this.start.clone(),c=[],d=-1,e=[];b.isBefore(this.end);)a.isHiddenDay(b)?e.push(d+.5):(d++,e.push(d),c.push(b.clone())),b.add(1,"days");this.cellDates=c,this.dayToCellOffsets=e},computeCellDate:function(a){var b=this.colCnt,c=a.row*b+(this.isRTL?b-a.col-1:a.col);return this.cellDates[c].clone()},getRowEl:function(a){return this.rowEls.eq(a)},getColEl:function(a){return this.dayEls.eq(a)},getCellDayEl:function(a){return this.dayEls.eq(a.row*this.colCnt+a.col)},computeRowCoords:function(){var a=jb.prototype.computeRowCoords.call(this);return a[a.length-1].bottom+=this.bottomCoordPadding,a},rangeToSegs:function(a){var b,c,d,e,f,g,h,i,j,k,l=this.isRTL,m=this.rowCnt,n=this.colCnt,o=[];for(a=this.view.computeDayRange(a),b=this.dateToCellOffset(a.start),c=this.dateToCellOffset(a.end.subtract(1,"days")),d=0;m>d;d++)e=d*n,f=e+n-1,i=Math.max(e,b),j=Math.min(f,c),i=Math.ceil(i),j=Math.floor(j),j>=i&&(g=i===b,h=j===c,i-=e,j-=e,k={row:d,isStart:g,isEnd:h},l?(k.leftCol=n-j-1,k.rightCol=n-i-1):(k.leftCol=i,k.rightCol=j),o.push(k));return o},dateToCellOffset:function(a){var b=this.dayToCellOffsets,c=a.diff(this.start,"days");return 0>c?b[0]-1:c>=b.length?b[b.length-1]+1:b[c]},renderDrag:function(a,b){return this.renderHighlight(this.eventRangeToSegs(a)),b&&!b.el.closest(this.el).length?(this.renderRangeHelper(a,b),this.applyDragOpacity(this.helperEls),!0):void 0},unrenderDrag:function(){this.unrenderHighlight(),this.unrenderHelper()},renderEventResize:function(a,b){this.renderHighlight(this.eventRangeToSegs(a)),this.renderRangeHelper(a,b)},unrenderEventResize:function(){this.unrenderHighlight(),this.unrenderHelper()},renderHelper:function(b,c){var d,e=[],f=this.eventsToSegs([b]);f=this.renderFgSegEls(f),d=this.renderSegRows(f),this.rowEls.each(function(b,f){var g,h=a(f),i=a('<div class="fc-helper-skeleton"><table/></div>');g=c&&c.row===b?c.el.position().top:h.find(".fc-content-skeleton tbody").position().top,i.css("top",g).find("table").append(d[b].tbodyEl),h.append(i),e.push(i[0])}),this.helperEls=a(e)},unrenderHelper:function(){this.helperEls&&(this.helperEls.remove(),this.helperEls=null)},fillSegTag:"td",renderFill:function(b,c,d){var e,f,g,h=[];for(c=this.renderFillSegEls(b,c),e=0;e<c.length;e++)f=c[e],g=this.renderFillRow(b,f,d),this.rowEls.eq(f.row).append(g),h.push(g[0]);return this.elsByFill[b]=a(h),c},renderFillRow:function(b,c,d){var e,f,g=this.colCnt,h=c.leftCol,i=c.rightCol+1;return d=d||b.toLowerCase(),e=a('<div class="fc-'+d+'-skeleton"><table><tr/></table></div>'),f=e.find("tr"),h>0&&f.append('<td colspan="'+h+'"/>'),f.append(c.el.attr("colspan",i-h)),g>i&&f.append('<td colspan="'+(g-i)+'"/>'),this.bookendCells(f,b),e}});kb.mixin({rowStructs:null,unrenderEvents:function(){this.removeSegPopover(),jb.prototype.unrenderEvents.apply(this,arguments)},getEventSegs:function(){return jb.prototype.getEventSegs.call(this).concat(this.popoverSegs||[])},renderBgSegs:function(b){var c=a.grep(b,function(a){return a.event.allDay});return jb.prototype.renderBgSegs.call(this,c)},renderFgSegs:function(b){var c;return b=this.renderFgSegEls(b),c=this.rowStructs=this.renderSegRows(b),this.rowEls.each(function(b,d){a(d).find(".fc-content-skeleton > table").append(c[b].tbodyEl)}),b},unrenderFgSegs:function(){for(var a,b=this.rowStructs||[];a=b.pop();)a.tbodyEl.remove();this.rowStructs=null},renderSegRows:function(a){var b,c,d=[];for(b=this.groupSegRows(a),c=0;c<b.length;c++)d.push(this.renderSegRow(c,b[c]));return d},fgSegHtml:function(a,b){var c,d,e=this.view,f=a.event,g=e.isEventDraggable(f),h=!b&&f.allDay&&a.isStart&&e.isEventResizableFromStart(f),i=!b&&f.allDay&&a.isEnd&&e.isEventResizableFromEnd(f),j=this.getSegClasses(a,g,h||i),k=$(this.getEventSkinCss(f)),l="";return j.unshift("fc-day-grid-event","fc-h-event"),a.isStart&&(c=this.getEventTimeText(f),c&&(l='<span class="fc-time">'+Y(c)+"</span>")),d='<span class="fc-title">'+(Y(f.title||"")||"&nbsp;")+"</span>",'<a class="'+j.join(" ")+'"'+(f.url?' href="'+Y(f.url)+'"':"")+(k?' style="'+k+'"':"")+'><div class="fc-content">'+(this.isRTL?d+" "+l:l+" "+d)+"</div>"+(h?'<div class="fc-resizer fc-start-resizer" />':"")+(i?'<div class="fc-resizer fc-end-resizer" />':"")+"</a>"},renderSegRow:function(b,c){function d(b){for(;b>g;)k=(r[e-1]||[])[g],k?k.attr("rowspan",parseInt(k.attr("rowspan")||1,10)+1):(k=a("<td/>"),h.append(k)),q[e][g]=k,r[e][g]=k,g++}var e,f,g,h,i,j,k,l=this.colCnt,m=this.buildSegLevels(c),n=Math.max(1,m.length),o=a("<tbody/>"),p=[],q=[],r=[];for(e=0;n>e;e++){if(f=m[e],g=0,h=a("<tr/>"),p.push([]),q.push([]),r.push([]),f)for(i=0;i<f.length;i++){for(j=f[i],d(j.leftCol),k=a('<td class="fc-event-container"/>').append(j.el),j.leftCol!=j.rightCol?k.attr("colspan",j.rightCol-j.leftCol+1):r[e][g]=k;g<=j.rightCol;)q[e][g]=k,p[e][g]=j,g++;h.append(k)}d(l),this.bookendCells(h,"eventSkeleton"),o.append(h)}return{row:b,tbodyEl:o,cellMatrix:q,segMatrix:p,segLevels:m,segs:c}},buildSegLevels:function(a){var b,c,d,e=[];for(this.sortSegs(a),b=0;b<a.length;b++){for(c=a[b],d=0;d<e.length&&za(c,e[d]);d++);c.level=d,(e[d]||(e[d]=[])).push(c)}for(d=0;d<e.length;d++)e[d].sort(Aa);return e},groupSegRows:function(a){var b,c=[];for(b=0;b<this.rowCnt;b++)c.push([]);for(b=0;b<a.length;b++)c[a[b].row].push(a[b]);return c}}),kb.mixin({segPopover:null,popoverSegs:null,removeSegPopover:function(){this.segPopover&&this.segPopover.hide()},limitRows:function(a){var b,c,d=this.rowStructs||[];for(b=0;b<d.length;b++)this.unlimitRow(b),c=a?"number"==typeof a?a:this.computeRowLevelLimit(b):!1,c!==!1&&this.limitRow(b,c)},computeRowLevelLimit:function(b){function c(b,c){f=Math.max(f,a(c).outerHeight())}var d,e,f,g=this.rowEls.eq(b),h=g.height(),i=this.rowStructs[b].tbodyEl.children();for(d=0;d<i.length;d++)if(e=i.eq(d).removeClass("fc-limited"),f=0,e.find("> td > :first-child").each(c),e.position().top+f>h)return d;return!1},limitRow:function(b,c){function d(d){for(;d>x;)e=u.getCell(b,x),k=u.getCellSegs(e,c),k.length&&(n=g[c-1][x],t=u.renderMoreLink(e,k),s=a("<div/>").append(t),n.append(s),w.push(s[0])),x++}var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u=this,v=this.rowStructs[b],w=[],x=0;if(c&&c<v.segLevels.length){for(f=v.segLevels[c-1],g=v.cellMatrix,h=v.tbodyEl.children().slice(c).addClass("fc-limited").get(),i=0;i<f.length;i++){for(j=f[i],d(j.leftCol),m=[],l=0;x<=j.rightCol;)e=this.getCell(b,x),k=this.getCellSegs(e,c),m.push(k),l+=k.length,x++;if(l){for(n=g[c-1][j.leftCol],o=n.attr("rowspan")||1,p=[],q=0;q<m.length;q++)r=a('<td class="fc-more-cell"/>').attr("rowspan",o),k=m[q],e=this.getCell(b,j.leftCol+q),t=this.renderMoreLink(e,[j].concat(k)),s=a("<div/>").append(t),r.append(s),p.push(r[0]),w.push(r[0]);n.addClass("fc-limited").after(a(p)),h.push(n[0])}}d(this.colCnt),v.moreEls=a(w),v.limitedEls=a(h)}},
8
- unlimitRow:function(a){var b=this.rowStructs[a];b.moreEls&&(b.moreEls.remove(),b.moreEls=null),b.limitedEls&&(b.limitedEls.removeClass("fc-limited"),b.limitedEls=null)},renderMoreLink:function(b,c){var d=this,e=this.view;return a('<a class="fc-more"/>').text(this.getMoreLinkText(c.length)).on("click",function(f){var g=e.opt("eventLimitClick"),h=b.start,i=a(this),j=d.getCellDayEl(b),k=d.getCellSegs(b),l=d.resliceDaySegs(k,h),m=d.resliceDaySegs(c,h);"function"==typeof g&&(g=e.trigger("eventLimitClick",null,{date:h,dayEl:j,moreEl:i,segs:l,hiddenSegs:m},f)),"popover"===g?d.showSegPopover(b,i,l):"string"==typeof g&&e.calendar.zoomTo(h,g)})},showSegPopover:function(a,b,c){var d,e,f=this,g=this.view,h=b.parent();d=1==this.rowCnt?g.el:this.rowEls.eq(a.row),e={className:"fc-more-popover",content:this.renderSegPopoverContent(a,c),parentEl:this.el,top:d.offset().top,autoHide:!0,viewportConstrain:g.opt("popoverViewportConstrain"),hide:function(){f.segPopover.removeElement(),f.segPopover=null,f.popoverSegs=null}},this.isRTL?e.right=h.offset().left+h.outerWidth()+1:e.left=h.offset().left-1,this.segPopover=new cb(e),this.segPopover.show()},renderSegPopoverContent:function(b,c){var d,e=this.view,f=e.opt("theme"),g=b.start.format(e.opt("dayPopoverFormat")),h=a('<div class="fc-header '+e.widgetHeaderClass+'"><span class="fc-close '+(f?"ui-icon ui-icon-closethick":"fc-icon fc-icon-x")+'"></span><span class="fc-title">'+Y(g)+'</span><div class="fc-clear"/></div><div class="fc-body '+e.widgetContentClass+'"><div class="fc-event-container"></div></div>'),i=h.find(".fc-event-container");for(c=this.renderFgSegEls(c,!0),this.popoverSegs=c,d=0;d<c.length;d++)c[d].cell=b,i.append(c[d].el);return h},resliceDaySegs:function(b,c){var d=a.map(b,function(a){return a.event}),e=c.clone().stripTime(),f=e.clone().add(1,"days"),g={start:e,end:f};return b=this.eventsToSegs(d,function(a){var b=E(a,g);return b?[b]:[]}),this.sortSegs(b),b},getMoreLinkText:function(a){var b=this.view.opt("eventLimitText");return"function"==typeof b?b(a):"+"+a+" "+b},getCellSegs:function(a,b){for(var c,d=this.rowStructs[a.row].segMatrix,e=b||0,f=[];e<d.length;)c=d[e][a.col],c&&f.push(c),e++;return f}});var lb=jb.extend({slotDuration:null,snapDuration:null,minTime:null,maxTime:null,colDates:null,labelFormat:null,labelInterval:null,dayEls:null,slatEls:null,slatTops:null,helperEl:null,businessHourSegs:null,constructor:function(){jb.apply(this,arguments),this.processOptions()},renderDates:function(){this.el.html(this.renderHtml()),this.dayEls=this.el.find(".fc-day"),this.slatEls=this.el.find(".fc-slats tr")},renderBusinessHours:function(){var a=this.view.calendar.getBusinessHoursEvents();this.businessHourSegs=this.renderFill("businessHours",this.eventsToSegs(a),"bgevent")},renderHtml:function(){return'<div class="fc-bg"><table>'+this.rowHtml("slotBg")+'</table></div><div class="fc-slats"><table>'+this.slatRowHtml()+"</table></div>"},slotBgCellHtml:function(a){return this.bgCellHtml(a)},slatRowHtml:function(){for(var a,c,d,e=this.view,f=this.isRTL,g="",h=b.duration(+this.minTime);h<this.maxTime;)a=this.start.clone().time(h),c=ba(L(h,this.labelInterval)),d='<td class="fc-axis fc-time '+e.widgetContentClass+'" '+e.axisStyleAttr()+">"+(c?"<span>"+Y(a.format(this.labelFormat))+"</span>":"")+"</td>",g+="<tr "+(c?"":'class="fc-minor"')+">"+(f?"":d)+'<td class="'+e.widgetContentClass+'"/>'+(f?d:"")+"</tr>",h.add(this.slotDuration);return g},processOptions:function(){var c,d=this.view,e=d.opt("slotDuration"),f=d.opt("snapDuration");e=b.duration(e),f=f?b.duration(f):e,this.slotDuration=e,this.snapDuration=f,this.cellDuration=f,this.minTime=b.duration(d.opt("minTime")),this.maxTime=b.duration(d.opt("maxTime")),c=d.opt("slotLabelFormat"),a.isArray(c)&&(c=c[c.length-1]),this.labelFormat=c||d.opt("axisFormat")||d.opt("smallTimeFormat"),c=d.opt("slotLabelInterval"),this.labelInterval=c?b.duration(c):this.computeLabelInterval(e)},computeLabelInterval:function(a){var c,d,e;for(c=yb.length-1;c>=0;c--)if(d=b.duration(yb[c]),e=L(d,a),ba(e)&&e>1)return d;return b.duration(a)},computeColHeadFormat:function(){return this.colCnt>1?this.view.opt("dayOfMonthFormat"):"dddd"},computeEventTimeFormat:function(){return this.view.opt("noMeridiemTimeFormat")},computeDisplayEventEnd:function(){return!0},rangeUpdated:function(){var a,b=this.view,c=[];for(a=this.start.clone();a.isBefore(this.end);)c.push(a.clone()),a.add(1,"day"),a=b.skipHiddenDays(a);this.isRTL&&c.reverse(),this.colDates=c,this.colCnt=c.length,this.rowCnt=Math.ceil((this.maxTime-this.minTime)/this.snapDuration)},computeCellDate:function(a){var b=this.colDates[a.col],c=this.computeSnapTime(a.row);return b=this.view.calendar.rezoneDate(b),b.time(c),b},getColEl:function(a){return this.dayEls.eq(a)},computeSnapTime:function(a){return b.duration(this.minTime+this.snapDuration*a)},rangeToSegs:function(a){var b,c,d,e,f=this.colCnt,g=[];for(a={start:a.start.clone().stripZone(),end:a.end.clone().stripZone()},c=0;f>c;c++)d=this.colDates[c],e={start:d.clone().time(this.minTime),end:d.clone().time(this.maxTime)},b=E(a,e),b&&(b.col=c,g.push(b));return g},updateSize:function(a){this.computeSlatTops(),a&&this.updateSegVerticals()},computeRowCoords:function(){var a,b,c=this.el.offset().top,d=[];for(a=0;a<this.rowCnt;a++)b={top:c+this.computeTimeTop(this.computeSnapTime(a))},a>0&&(d[a-1].bottom=b.top),d.push(b);return b.bottom=b.top+this.computeTimeTop(this.computeSnapTime(a)),d},computeDateTop:function(a,c){return this.computeTimeTop(b.duration(a.clone().stripZone()-c.clone().stripTime()))},computeTimeTop:function(a){var b,c,d,e,f=(a-this.minTime)/this.slotDuration;return f=Math.max(0,f),f=Math.min(this.slatEls.length,f),b=Math.floor(f),c=f-b,d=this.slatTops[b],c?(e=this.slatTops[b+1],d+(e-d)*c):d},computeSlatTops:function(){var b,c=[];this.slatEls.each(function(d,e){b=a(e).position().top,c.push(b)}),c.push(b+this.slatEls.last().outerHeight()),this.slatTops=c},renderDrag:function(a,b){return b?(this.renderRangeHelper(a,b),this.applyDragOpacity(this.helperEl),!0):void this.renderHighlight(this.eventRangeToSegs(a))},unrenderDrag:function(){this.unrenderHelper(),this.unrenderHighlight()},renderEventResize:function(a,b){this.renderRangeHelper(a,b)},unrenderEventResize:function(){this.unrenderHelper()},renderHelper:function(b,c){var d,e,f,g,h=this.eventsToSegs([b]);for(h=this.renderFgSegEls(h),d=this.renderSegTable(h),e=0;e<h.length;e++)f=h[e],c&&c.col===f.col&&(g=c.el,f.el.css({left:g.css("left"),right:g.css("right"),"margin-left":g.css("margin-left"),"margin-right":g.css("margin-right")}));this.helperEl=a('<div class="fc-helper-skeleton"/>').append(d).appendTo(this.el)},unrenderHelper:function(){this.helperEl&&(this.helperEl.remove(),this.helperEl=null)},renderSelection:function(a){this.view.opt("selectHelper")?this.renderRangeHelper(a):this.renderHighlight(this.selectionRangeToSegs(a))},unrenderSelection:function(){this.unrenderHelper(),this.unrenderHighlight()},renderFill:function(b,c,d){var e,f,g,h,i,j,k,l,m,n;if(c.length){for(c=this.renderFillSegEls(b,c),e=this.groupSegCols(c),d=d||b.toLowerCase(),f=a('<div class="fc-'+d+'-skeleton"><table><tr/></table></div>'),g=f.find("tr"),h=0;h<e.length;h++)if(i=e[h],j=a("<td/>").appendTo(g),i.length)for(k=a('<div class="fc-'+d+'-container"/>').appendTo(j),l=this.colDates[h],m=0;m<i.length;m++)n=i[m],k.append(n.el.css({top:this.computeDateTop(n.start,l),bottom:-this.computeDateTop(n.end,l)}));this.bookendCells(g,b),this.el.append(f),this.elsByFill[b]=f}return c}});lb.mixin({eventSkeletonEl:null,renderFgSegs:function(b){return b=this.renderFgSegEls(b),this.el.append(this.eventSkeletonEl=a('<div class="fc-content-skeleton"/>').append(this.renderSegTable(b))),b},unrenderFgSegs:function(a){this.eventSkeletonEl&&(this.eventSkeletonEl.remove(),this.eventSkeletonEl=null)},renderSegTable:function(b){var c,d,e,f,g,h,i=a("<table><tr/></table>"),j=i.find("tr");for(c=this.groupSegCols(b),this.computeSegVerticals(b),f=0;f<c.length;f++){for(g=c[f],this.placeSlotSegs(g),h=a('<div class="fc-event-container"/>'),d=0;d<g.length;d++)e=g[d],e.el.css(this.generateSegPositionCss(e)),e.bottom-e.top<30&&e.el.addClass("fc-short"),h.append(e.el);j.append(a("<td/>").append(h))}return this.bookendCells(j,"eventSkeleton"),i},placeSlotSegs:function(a){var b,c,d;if(this.sortSegs(a),b=Ba(a),Ca(b),c=b[0]){for(d=0;d<c.length;d++)Da(c[d]);for(d=0;d<c.length;d++)this.computeSlotSegCoords(c[d],0,0)}},computeSlotSegCoords:function(a,b,c){var d,e=a.forwardSegs;if(void 0===a.forwardCoord)for(e.length?(this.sortForwardSlotSegs(e),this.computeSlotSegCoords(e[0],b+1,c),a.forwardCoord=e[0].backwardCoord):a.forwardCoord=1,a.backwardCoord=a.forwardCoord-(a.forwardCoord-c)/(b+1),d=0;d<e.length;d++)this.computeSlotSegCoords(e[d],0,a.forwardCoord)},updateSegVerticals:function(){var a,b=(this.segs||[]).concat(this.businessHourSegs||[]);for(this.computeSegVerticals(b),a=0;a<b.length;a++)b[a].el.css(this.generateSegVerticalCss(b[a]))},computeSegVerticals:function(a){var b,c;for(b=0;b<a.length;b++)c=a[b],c.top=this.computeDateTop(c.start,c.start),c.bottom=this.computeDateTop(c.end,c.start)},fgSegHtml:function(a,b){var c,d,e,f=this.view,g=a.event,h=f.isEventDraggable(g),i=!b&&a.isStart&&f.isEventResizableFromStart(g),j=!b&&a.isEnd&&f.isEventResizableFromEnd(g),k=this.getSegClasses(a,h,i||j),l=$(this.getEventSkinCss(g));return k.unshift("fc-time-grid-event","fc-v-event"),f.isMultiDayEvent(g)?(a.isStart||a.isEnd)&&(c=this.getEventTimeText(a),d=this.getEventTimeText(a,"LT"),e=this.getEventTimeText(a,null,!1)):(c=this.getEventTimeText(g),d=this.getEventTimeText(g,"LT"),e=this.getEventTimeText(g,null,!1)),'<a class="'+k.join(" ")+'"'+(g.url?' href="'+Y(g.url)+'"':"")+(l?' style="'+l+'"':"")+'><div class="fc-content">'+(c?'<div class="fc-time" data-start="'+Y(e)+'" data-full="'+Y(d)+'"><span>'+Y(c)+"</span></div>":"")+(g.title?'<div class="fc-title">'+Y(g.title)+"</div>":"")+'</div><div class="fc-bg"/>'+(j?'<div class="fc-resizer fc-end-resizer" />':"")+"</a>"},generateSegPositionCss:function(a){var b,c,d=this.view.opt("slotEventOverlap"),e=a.backwardCoord,f=a.forwardCoord,g=this.generateSegVerticalCss(a);return d&&(f=Math.min(1,e+2*(f-e))),this.isRTL?(b=1-f,c=e):(b=e,c=1-f),g.zIndex=a.level+1,g.left=100*b+"%",g.right=100*c+"%",d&&a.forwardPressure&&(g[this.isRTL?"marginLeft":"marginRight"]=20),g},generateSegVerticalCss:function(a){return{top:a.top,bottom:-a.bottom}},groupSegCols:function(a){var b,c=[];for(b=0;b<this.colCnt;b++)c.push([]);for(b=0;b<a.length;b++)c[a[b].col].push(a[b]);return c},sortForwardSlotSegs:function(a){a.sort(ca(this,"compareForwardSlotSegs"))},compareForwardSlotSegs:function(a,b){return b.forwardPressure-a.forwardPressure||(a.backwardCoord||0)-(b.backwardCoord||0)||this.compareSegs(a,b)}});var mb=Ma.View=ra.extend({type:null,name:null,title:null,calendar:null,options:null,coordMap:null,el:null,displaying:null,isSkeletonRendered:!1,isEventsRendered:!1,start:null,end:null,intervalStart:null,intervalEnd:null,intervalDuration:null,intervalUnit:null,isRTL:!1,isSelected:!1,eventOrderSpecs:null,scrollerEl:null,scrollTop:null,widgetHeaderClass:null,widgetContentClass:null,highlightStateClass:null,nextDayThreshold:null,isHiddenDayHash:null,documentMousedownProxy:null,constructor:function(a,c,d,e){this.calendar=a,this.type=this.name=c,this.options=d,this.intervalDuration=e||b.duration(1,"day"),this.nextDayThreshold=b.duration(this.opt("nextDayThreshold")),this.initThemingProps(),this.initHiddenDays(),this.isRTL=this.opt("isRTL"),this.eventOrderSpecs=A(this.opt("eventOrder")),this.documentMousedownProxy=ca(this,"documentMousedown"),this.initialize()},initialize:function(){},opt:function(a){return this.options[a]},trigger:function(a,b){var c=this.calendar;return c.trigger.apply(c,[a,b||this].concat(Array.prototype.slice.call(arguments,2),[this]))},setDate:function(a){this.setRange(this.computeRange(a))},setRange:function(b){a.extend(this,b),this.updateTitle()},computeRange:function(a){var b,c,d=I(this.intervalDuration),e=a.clone().startOf(d),f=e.clone().add(this.intervalDuration);return/year|month|week|day/.test(d)?(e.stripTime(),f.stripTime()):(e.hasTime()||(e=this.calendar.rezoneDate(e)),f.hasTime()||(f=this.calendar.rezoneDate(f))),b=e.clone(),b=this.skipHiddenDays(b),c=f.clone(),c=this.skipHiddenDays(c,-1,!0),{intervalUnit:d,intervalStart:e,intervalEnd:f,start:b,end:c}},computePrevDate:function(a){return this.massageCurrentDate(a.clone().startOf(this.intervalUnit).subtract(this.intervalDuration),-1)},computeNextDate:function(a){return this.massageCurrentDate(a.clone().startOf(this.intervalUnit).add(this.intervalDuration))},massageCurrentDate:function(a,b){return this.intervalDuration.as("days")<=1&&this.isHiddenDay(a)&&(a=this.skipHiddenDays(a,b),a.startOf("day")),a},updateTitle:function(){this.title=this.computeTitle()},computeTitle:function(){return this.formatRange({start:this.intervalStart,end:this.intervalEnd},this.opt("titleFormat")||this.computeTitleFormat(),this.opt("titleRangeSeparator"))},computeTitleFormat:function(){return"year"==this.intervalUnit?"YYYY":"month"==this.intervalUnit?this.opt("monthYearFormat"):this.intervalDuration.as("days")>1?"ll":"LL"},formatRange:function(a,b,c){var d=a.end;return d.hasTime()||(d=d.clone().subtract(1)),ma(a.start,d,b,c,this.opt("isRTL"))},setElement:function(a){this.el=a,this.bindGlobalHandlers()},removeElement:function(){this.clear(),this.isSkeletonRendered&&(this.unrenderSkeleton(),this.isSkeletonRendered=!1),this.unbindGlobalHandlers(),this.el.remove()},display:function(b){var c=this,d=null;return this.displaying&&(d=this.queryScroll()),this.clear().then(function(){return c.displaying=a.when(c.displayView(b)).then(function(){c.forceScroll(c.computeInitialScroll(d)),c.triggerRender()})})},clear:function(){var b=this,c=this.displaying;return c?c.then(function(){return b.displaying=null,b.clearEvents(),b.clearView()}):a.when()},displayView:function(a){this.isSkeletonRendered||(this.renderSkeleton(),this.isSkeletonRendered=!0),this.setDate(a),this.render&&this.render(),this.renderDates(),this.updateSize(),this.renderBusinessHours()},clearView:function(){this.unselect(),this.triggerUnrender(),this.unrenderBusinessHours(),this.unrenderDates(),this.destroy&&this.destroy()},renderSkeleton:function(){},unrenderSkeleton:function(){},renderDates:function(){},unrenderDates:function(){},renderBusinessHours:function(){},unrenderBusinessHours:function(){},triggerRender:function(){this.trigger("viewRender",this,this,this.el)},triggerUnrender:function(){this.trigger("viewDestroy",this,this,this.el)},bindGlobalHandlers:function(){a(document).on("mousedown",this.documentMousedownProxy)},unbindGlobalHandlers:function(){a(document).off("mousedown",this.documentMousedownProxy)},initThemingProps:function(){var a=this.opt("theme")?"ui":"fc";this.widgetHeaderClass=a+"-widget-header",this.widgetContentClass=a+"-widget-content",this.highlightStateClass=a+"-state-highlight"},updateSize:function(a){var b;a&&(b=this.queryScroll()),this.updateHeight(a),this.updateWidth(a),a&&this.setScroll(b)},updateWidth:function(a){},updateHeight:function(a){var b=this.calendar;this.setHeight(b.getSuggestedViewHeight(),b.isHeightAuto())},setHeight:function(a,b){},computeScrollerHeight:function(a){var b,c,d=this.scrollerEl;return b=this.el.add(d),b.css({position:"relative",left:-1}),c=this.el.outerHeight()-d.height(),b.css({position:"",left:""}),a-c},computeInitialScroll:function(a){return 0},queryScroll:function(){return this.scrollerEl?this.scrollerEl.scrollTop():void 0},setScroll:function(a){return this.scrollerEl?this.scrollerEl.scrollTop(a):void 0},forceScroll:function(a){var b=this;this.setScroll(a),setTimeout(function(){b.setScroll(a)},0)},displayEvents:function(a){var b=this.queryScroll();this.clearEvents(),this.renderEvents(a),this.isEventsRendered=!0,this.setScroll(b),this.triggerEventRender()},clearEvents:function(){this.isEventsRendered&&(this.triggerEventUnrender(),this.destroyEvents&&this.destroyEvents(),this.unrenderEvents(),this.isEventsRendered=!1)},renderEvents:function(a){},unrenderEvents:function(){},triggerEventRender:function(){this.renderedEventSegEach(function(a){this.trigger("eventAfterRender",a.event,a.event,a.el)}),this.trigger("eventAfterAllRender")},triggerEventUnrender:function(){this.renderedEventSegEach(function(a){this.trigger("eventDestroy",a.event,a.event,a.el)})},resolveEventEl:function(b,c){var d=this.trigger("eventRender",b,b,c);return d===!1?c=null:d&&d!==!0&&(c=a(d)),c},showEvent:function(a){this.renderedEventSegEach(function(a){a.el.css("visibility","")},a)},hideEvent:function(a){this.renderedEventSegEach(function(a){a.el.css("visibility","hidden")},a)},renderedEventSegEach:function(a,b){var c,d=this.getEventSegs();for(c=0;c<d.length;c++)b&&d[c].event._id!==b._id||d[c].el&&a.call(this,d[c])},getEventSegs:function(){return[]},isEventDraggable:function(a){var b=a.source||{};return X(a.startEditable,b.startEditable,this.opt("eventStartEditable"),a.editable,b.editable,this.opt("editable"))},reportEventDrop:function(a,b,c,d,e){var f=this.calendar,g=f.mutateEvent(a,b,c),h=function(){g.undo(),f.reportEventChange()};this.triggerEventDrop(a,g.dateDelta,h,d,e),f.reportEventChange()},triggerEventDrop:function(a,b,c,d,e){this.trigger("eventDrop",d[0],a,b,c,e,{})},reportExternalDrop:function(b,c,d,e,f){var g,h,i=b.eventProps;i&&(g=a.extend({},i,c),h=this.calendar.renderEvent(g,b.stick)[0]),this.triggerExternalDrop(h,c,d,e,f)},triggerExternalDrop:function(a,b,c,d,e){this.trigger("drop",c[0],b.start,d,e),a&&this.trigger("eventReceive",null,a)},renderDrag:function(a,b){},unrenderDrag:function(){},isEventResizableFromStart:function(a){return this.opt("eventResizableFromStart")&&this.isEventResizable(a)},isEventResizableFromEnd:function(a){return this.isEventResizable(a)},isEventResizable:function(a){var b=a.source||{};return X(a.durationEditable,b.durationEditable,this.opt("eventDurationEditable"),a.editable,b.editable,this.opt("editable"))},reportEventResize:function(a,b,c,d,e){var f=this.calendar,g=f.mutateEvent(a,b,c),h=function(){g.undo(),f.reportEventChange()};this.triggerEventResize(a,g.durationDelta,h,d,e),f.reportEventChange()},triggerEventResize:function(a,b,c,d,e){this.trigger("eventResize",d[0],a,b,c,e,{})},select:function(a,b){this.unselect(b),this.renderSelection(a),this.reportSelection(a,b)},renderSelection:function(a){},reportSelection:function(a,b){this.isSelected=!0,this.triggerSelect(a,b)},triggerSelect:function(a,b){this.trigger("select",null,a.start,a.end,b)},unselect:function(a){this.isSelected&&(this.isSelected=!1,this.destroySelection&&this.destroySelection(),this.unrenderSelection(),this.trigger("unselect",null,a))},unrenderSelection:function(){},documentMousedown:function(b){var c;this.isSelected&&this.opt("unselectAuto")&&v(b)&&(c=this.opt("unselectCancel"),c&&a(b.target).closest(c).length||this.unselect(b))},triggerDayClick:function(a,b,c){this.trigger("dayClick",b,a.start,c)},initHiddenDays:function(){var b,c=this.opt("hiddenDays")||[],d=[],e=0;for(this.opt("weekends")===!1&&c.push(0,6),b=0;7>b;b++)(d[b]=-1!==a.inArray(b,c))||e++;if(!e)throw"invalid hiddenDays";this.isHiddenDayHash=d},isHiddenDay:function(a){return b.isMoment(a)&&(a=a.day()),this.isHiddenDayHash[a]},skipHiddenDays:function(a,b,c){var d=a.clone();for(b=b||1;this.isHiddenDayHash[(d.day()+(c?b:0)+7)%7];)d.add(b,"days");return d},computeDayRange:function(a){var b,c=a.start.clone().stripTime(),d=a.end,e=null;return d&&(e=d.clone().stripTime(),b=+d.time(),b&&b>=this.nextDayThreshold&&e.add(1,"days")),(!d||c>=e)&&(e=c.clone().add(1,"days")),{start:c,end:e}},isMultiDayEvent:function(a){var b=this.computeDayRange(a);return b.end.diff(b.start,"days")>1}}),nb=Ma.Calendar=ra.extend({dirDefaults:null,langDefaults:null,overrides:null,options:null,viewSpecCache:null,view:null,header:null,loadingLevel:0,constructor:Ga,initialize:function(){},initOptions:function(a){var b,e,f,g;a=d(a),b=a.lang,e=ob[b],e||(b=nb.defaults.lang,e=ob[b]||{}),f=X(a.isRTL,e.isRTL,nb.defaults.isRTL),g=f?nb.rtlDefaults:{},this.dirDefaults=g,this.langDefaults=e,this.overrides=a,this.options=c([nb.defaults,g,e,a]),Ha(this.options),this.viewSpecCache={}},getViewSpec:function(a){var b=this.viewSpecCache;return b[a]||(b[a]=this.buildViewSpec(a))},getUnitViewSpec:function(b){var c,d,e;if(-1!=a.inArray(b,Ra))for(c=this.header.getViewsWithButtons(),a.each(Ma.views,function(a){c.push(a)}),d=0;d<c.length;d++)if(e=this.getViewSpec(c[d]),e&&e.singleUnit==b)return e},buildViewSpec:function(a){for(var d,e,f,g,h=this.overrides.views||{},i=[],j=[],k=[],l=a;l;)d=Na[l],e=h[l],l=null,"function"==typeof d&&(d={"class":d}),d&&(i.unshift(d),j.unshift(d.defaults||{}),f=f||d.duration,l=l||d.type),e&&(k.unshift(e),f=f||e.duration,l=l||e.type);return d=Q(i),d.type=a,d["class"]?(f&&(f=b.duration(f),f.valueOf()&&(d.duration=f,g=I(f),1===f.as(g)&&(d.singleUnit=g,k.unshift(h[g]||{})))),d.defaults=c(j),d.overrides=c(k),this.buildViewSpecOptions(d),this.buildViewSpecButtonText(d,a),d):!1},buildViewSpecOptions:function(a){a.options=c([nb.defaults,a.defaults,this.dirDefaults,this.langDefaults,this.overrides,a.overrides]),Ha(a.options)},buildViewSpecButtonText:function(a,b){function c(c){var d=c.buttonText||{};return d[b]||(a.singleUnit?d[a.singleUnit]:null)}a.buttonTextOverride=c(this.overrides)||a.overrides.buttonText,a.buttonTextDefault=c(this.langDefaults)||c(this.dirDefaults)||a.defaults.buttonText||c(nb.defaults)||(a.duration?this.humanizeDuration(a.duration):null)||b},instantiateView:function(a){var b=this.getViewSpec(a);return new b["class"](this,a,b.options,b.duration)},isValidViewType:function(a){return Boolean(this.getViewSpec(a))},pushLoading:function(){this.loadingLevel++||this.trigger("loading",null,!0,this.view)},popLoading:function(){--this.loadingLevel||this.trigger("loading",null,!1,this.view)},buildSelectRange:function(a,b){return a=this.moment(a),b=b?this.moment(b):a.hasTime()?a.clone().add(this.defaultTimedEventDuration):a.clone().add(this.defaultAllDayEventDuration),{start:a,end:b}}});nb.mixin(bb),nb.defaults={titleRangeSeparator:" — ",monthYearFormat:"MMMM YYYY",defaultTimedEventDuration:"02:00:00",defaultAllDayEventDuration:{days:1},forceEventDuration:!1,nextDayThreshold:"09:00:00",defaultView:"month",aspectRatio:1.35,header:{left:"title",center:"",right:"today prev,next"},weekends:!0,weekNumbers:!1,weekNumberTitle:"W",weekNumberCalculation:"local",scrollTime:"06:00:00",lazyFetching:!0,startParam:"start",endParam:"end",timezoneParam:"timezone",timezone:!1,isRTL:!1,buttonText:{prev:"prev",next:"next",prevYear:"prev year",nextYear:"next year",year:"year",today:"today",month:"month",week:"week",day:"day"},buttonIcons:{prev:"left-single-arrow",next:"right-single-arrow",prevYear:"left-double-arrow",nextYear:"right-double-arrow"},theme:!1,themeButtonIcons:{prev:"circle-triangle-w",next:"circle-triangle-e",prevYear:"seek-prev",nextYear:"seek-next"},dragOpacity:.75,dragRevertDuration:500,dragScroll:!0,unselectAuto:!0,dropAccept:"*",eventOrder:"title",eventLimit:!1,eventLimitText:"more",eventLimitClick:"popover",dayPopoverFormat:"LL",handleWindowResize:!0,windowResizeDelay:200},nb.englishDefaults={dayPopoverFormat:"dddd, MMMM D"},nb.rtlDefaults={header:{left:"next,prev today",center:"",right:"title"},buttonIcons:{prev:"right-single-arrow",next:"left-single-arrow",prevYear:"right-double-arrow",nextYear:"left-double-arrow"},themeButtonIcons:{prev:"circle-triangle-e",next:"circle-triangle-w",nextYear:"seek-prev",prevYear:"seek-next"}};var ob=Ma.langs={};Ma.datepickerLang=function(b,c,d){var e=ob[b]||(ob[b]={});e.isRTL=d.isRTL,e.weekNumberTitle=d.weekHeader,a.each(pb,function(a,b){e[a]=b(d)}),a.datepicker&&(a.datepicker.regional[c]=a.datepicker.regional[b]=d,a.datepicker.regional.en=a.datepicker.regional[""],a.datepicker.setDefaults(d))},Ma.lang=function(b,d){var e,f;e=ob[b]||(ob[b]={}),d&&(e=ob[b]=c([e,d])),f=Ia(b),a.each(qb,function(a,b){null==e[a]&&(e[a]=b(f,e))}),nb.defaults.lang=b};var pb={buttonText:function(a){return{prev:Z(a.prevText),next:Z(a.nextText),today:Z(a.currentText)}},monthYearFormat:function(a){return a.showMonthAfterYear?"YYYY["+a.yearSuffix+"] MMMM":"MMMM YYYY["+a.yearSuffix+"]"}},qb={dayOfMonthFormat:function(a,b){var c=a.longDateFormat("l");return c=c.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g,""),b.isRTL?c+=" ddd":c="ddd "+c,c},mediumTimeFormat:function(a){return a.longDateFormat("LT").replace(/\s*a$/i,"a")},smallTimeFormat:function(a){return a.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"a")},extraSmallTimeFormat:function(a){return a.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"t")},hourFormat:function(a){return a.longDateFormat("LT").replace(":mm","").replace(/(\Wmm)$/,"").replace(/\s*a$/i,"a")},noMeridiemTimeFormat:function(a){return a.longDateFormat("LT").replace(/\s*a$/i,"")}},rb={smallDayDateFormat:function(a){return a.isRTL?"D dd":"dd D"},weekFormat:function(a){return a.isRTL?"w[ "+a.weekNumberTitle+"]":"["+a.weekNumberTitle+" ]w"},smallWeekFormat:function(a){return a.isRTL?"w["+a.weekNumberTitle+"]":"["+a.weekNumberTitle+"]w"}};Ma.lang("en",nb.englishDefaults),Ma.sourceNormalizers=[],Ma.sourceFetchers=[];var sb={dataType:"json",cache:!1},tb=1;nb.prototype.getPeerEvents=function(a,b){var c,d,e=this.getEventCache(),f=[];for(c=0;c<e.length;c++)d=e[c],a&&a._id===d._id||f.push(d);return f};var ub=mb.extend({dayGrid:null,dayNumbersVisible:!1,weekNumbersVisible:!1,weekNumberWidth:null,headRowEl:null,initialize:function(){this.dayGrid=new kb(this),this.coordMap=this.dayGrid.coordMap},setRange:function(a){mb.prototype.setRange.call(this,a),this.dayGrid.breakOnWeeks=/year|month|week/.test(this.intervalUnit),this.dayGrid.setRange(a)},computeRange:function(a){var b=mb.prototype.computeRange.call(this,a);return/year|month/.test(b.intervalUnit)&&(b.start.startOf("week"),b.start=this.skipHiddenDays(b.start),b.end.weekday()&&(b.end.add(1,"week").startOf("week"),b.end=this.skipHiddenDays(b.end,-1,!0))),b},renderDates:function(){this.dayNumbersVisible=this.dayGrid.rowCnt>1,this.weekNumbersVisible=this.opt("weekNumbers"),this.dayGrid.numbersVisible=this.dayNumbersVisible||this.weekNumbersVisible,this.el.addClass("fc-basic-view").html(this.renderHtml()),this.headRowEl=this.el.find("thead .fc-row"),this.scrollerEl=this.el.find(".fc-day-grid-container"),this.dayGrid.coordMap.containerEl=this.scrollerEl,this.dayGrid.setElement(this.el.find(".fc-day-grid")),this.dayGrid.renderDates(this.hasRigidRows())},unrenderDates:function(){this.dayGrid.unrenderDates(),this.dayGrid.removeElement()},renderBusinessHours:function(){this.dayGrid.renderBusinessHours()},renderHtml:function(){return'<table><thead class="fc-head"><tr><td class="'+this.widgetHeaderClass+'">'+this.dayGrid.headHtml()+'</td></tr></thead><tbody class="fc-body"><tr><td class="'+this.widgetContentClass+'"><div class="fc-day-grid-container"><div class="fc-day-grid"/></div></td></tr></tbody></table>'},headIntroHtml:function(){return this.weekNumbersVisible?'<th class="fc-week-number '+this.widgetHeaderClass+'" '+this.weekNumberStyleAttr()+"><span>"+Y(this.opt("weekNumberTitle"))+"</span></th>":void 0},numberIntroHtml:function(a){return this.weekNumbersVisible?'<td class="fc-week-number" '+this.weekNumberStyleAttr()+"><span>"+this.dayGrid.getCell(a,0).start.format("w")+"</span></td>":void 0},dayIntroHtml:function(){return this.weekNumbersVisible?'<td class="fc-week-number '+this.widgetContentClass+'" '+this.weekNumberStyleAttr()+"></td>":void 0},introHtml:function(){return this.weekNumbersVisible?'<td class="fc-week-number" '+this.weekNumberStyleAttr()+"></td>":void 0},numberCellHtml:function(a){var b,c=a.start;return this.dayNumbersVisible?(b=this.dayGrid.getDayClasses(c),b.unshift("fc-day-number"),'<td class="'+b.join(" ")+'" data-date="'+c.format()+'">'+c.date()+"</td>"):"<td/>"},weekNumberStyleAttr:function(){return null!==this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},hasRigidRows:function(){var a=this.opt("eventLimit");return a&&"number"!=typeof a},updateWidth:function(){this.weekNumbersVisible&&(this.weekNumberWidth=k(this.el.find(".fc-week-number")))},setHeight:function(a,b){var c,d=this.opt("eventLimit");m(this.scrollerEl),f(this.headRowEl),this.dayGrid.removeSegPopover(),d&&"number"==typeof d&&this.dayGrid.limitRows(d),c=this.computeScrollerHeight(a),this.setGridHeight(c,b),d&&"number"!=typeof d&&this.dayGrid.limitRows(d),!b&&l(this.scrollerEl,c)&&(e(this.headRowEl,r(this.scrollerEl)),c=this.computeScrollerHeight(a),this.scrollerEl.height(c))},setGridHeight:function(a,b){b?j(this.dayGrid.rowEls):i(this.dayGrid.rowEls,a,!0)},renderEvents:function(a){this.dayGrid.renderEvents(a),this.updateHeight()},getEventSegs:function(){return this.dayGrid.getEventSegs()},unrenderEvents:function(){this.dayGrid.unrenderEvents()},renderDrag:function(a,b){return this.dayGrid.renderDrag(a,b)},unrenderDrag:function(){this.dayGrid.unrenderDrag()},renderSelection:function(a){this.dayGrid.renderSelection(a)},unrenderSelection:function(){this.dayGrid.unrenderSelection()}}),vb=ub.extend({computeRange:function(a){var b,c=ub.prototype.computeRange.call(this,a);return this.isFixedWeeks()&&(b=Math.ceil(c.end.diff(c.start,"weeks",!0)),c.end.add(6-b,"weeks")),c},setGridHeight:function(a,b){b=b||"variable"===this.opt("weekMode"),b&&(a*=this.rowCnt/6),i(this.dayGrid.rowEls,a,!b)},isFixedWeeks:function(){var a=this.opt("weekMode");return a?"fixed"===a:this.opt("fixedWeekCount")}});Na.basic={"class":ub},Na.basicDay={type:"basic",duration:{days:1}},Na.basicWeek={type:"basic",duration:{weeks:1}},Na.month={"class":vb,duration:{months:1},defaults:{fixedWeekCount:!0}};var wb=mb.extend({timeGrid:null,dayGrid:null,axisWidth:null,noScrollRowEls:null,bottomRuleEl:null,bottomRuleHeight:null,initialize:function(){this.timeGrid=new lb(this),this.opt("allDaySlot")?(this.dayGrid=new kb(this),this.coordMap=new eb([this.dayGrid.coordMap,this.timeGrid.coordMap])):this.coordMap=this.timeGrid.coordMap},setRange:function(a){mb.prototype.setRange.call(this,a),this.timeGrid.setRange(a),this.dayGrid&&this.dayGrid.setRange(a)},renderDates:function(){this.el.addClass("fc-agenda-view").html(this.renderHtml()),this.scrollerEl=this.el.find(".fc-time-grid-container"),this.timeGrid.coordMap.containerEl=this.scrollerEl,this.timeGrid.setElement(this.el.find(".fc-time-grid")),this.timeGrid.renderDates(),this.bottomRuleEl=a('<hr class="fc-divider '+this.widgetHeaderClass+'"/>').appendTo(this.timeGrid.el),this.dayGrid&&(this.dayGrid.setElement(this.el.find(".fc-day-grid")),this.dayGrid.renderDates(),this.dayGrid.bottomCoordPadding=this.dayGrid.el.next("hr").outerHeight()),this.noScrollRowEls=this.el.find(".fc-row:not(.fc-scroller *)")},unrenderDates:function(){this.timeGrid.unrenderDates(),this.timeGrid.removeElement(),this.dayGrid&&(this.dayGrid.unrenderDates(),this.dayGrid.removeElement())},renderBusinessHours:function(){this.timeGrid.renderBusinessHours(),this.dayGrid&&this.dayGrid.renderBusinessHours()},renderHtml:function(){return'<table><thead class="fc-head"><tr><td class="'+this.widgetHeaderClass+'">'+this.timeGrid.headHtml()+'</td></tr></thead><tbody class="fc-body"><tr><td class="'+this.widgetContentClass+'">'+(this.dayGrid?'<div class="fc-day-grid"/><hr class="fc-divider '+this.widgetHeaderClass+'"/>':"")+'<div class="fc-time-grid-container"><div class="fc-time-grid"/></div></td></tr></tbody></table>'},headIntroHtml:function(){var a,b;return this.opt("weekNumbers")?(a=this.timeGrid.getCell(0).start,b=a.format(this.opt("smallWeekFormat")),'<th class="fc-axis fc-week-number '+this.widgetHeaderClass+'" '+this.axisStyleAttr()+"><span>"+Y(b)+"</span></th>"):'<th class="fc-axis '+this.widgetHeaderClass+'" '+this.axisStyleAttr()+"></th>"},dayIntroHtml:function(){return'<td class="fc-axis '+this.widgetContentClass+'" '+this.axisStyleAttr()+"><span>"+(this.opt("allDayHtml")||Y(this.opt("allDayText")))+"</span></td>"},slotBgIntroHtml:function(){return'<td class="fc-axis '+this.widgetContentClass+'" '+this.axisStyleAttr()+"></td>";
9
  },introHtml:function(){return'<td class="fc-axis" '+this.axisStyleAttr()+"></td>"},axisStyleAttr:function(){return null!==this.axisWidth?'style="width:'+this.axisWidth+'px"':""},updateSize:function(a){this.timeGrid.updateSize(a),mb.prototype.updateSize.call(this,a)},updateWidth:function(){this.axisWidth=k(this.el.find(".fc-axis"))},setHeight:function(a,b){var c,d;null===this.bottomRuleHeight&&(this.bottomRuleHeight=this.bottomRuleEl.outerHeight()),this.bottomRuleEl.hide(),this.scrollerEl.css("overflow",""),m(this.scrollerEl),f(this.noScrollRowEls),this.dayGrid&&(this.dayGrid.removeSegPopover(),c=this.opt("eventLimit"),c&&"number"!=typeof c&&(c=xb),c&&this.dayGrid.limitRows(c)),b||(d=this.computeScrollerHeight(a),l(this.scrollerEl,d)?(e(this.noScrollRowEls,r(this.scrollerEl)),d=this.computeScrollerHeight(a),this.scrollerEl.height(d)):(this.scrollerEl.height(d).css("overflow","hidden"),this.bottomRuleEl.show()))},computeInitialScroll:function(){var a=b.duration(this.opt("scrollTime")),c=this.timeGrid.computeTimeTop(a);return c=Math.ceil(c),c&&c++,c},renderEvents:function(a){var b,c,d=[],e=[],f=[];for(c=0;c<a.length;c++)a[c].allDay?d.push(a[c]):e.push(a[c]);b=this.timeGrid.renderEvents(e),this.dayGrid&&(f=this.dayGrid.renderEvents(d)),this.updateHeight()},getEventSegs:function(){return this.timeGrid.getEventSegs().concat(this.dayGrid?this.dayGrid.getEventSegs():[])},unrenderEvents:function(){this.timeGrid.unrenderEvents(),this.dayGrid&&this.dayGrid.unrenderEvents()},renderDrag:function(a,b){return a.start.hasTime()?this.timeGrid.renderDrag(a,b):this.dayGrid?this.dayGrid.renderDrag(a,b):void 0},unrenderDrag:function(){this.timeGrid.unrenderDrag(),this.dayGrid&&this.dayGrid.unrenderDrag()},renderSelection:function(a){a.start.hasTime()||a.end.hasTime()?this.timeGrid.renderSelection(a):this.dayGrid&&this.dayGrid.renderSelection(a)},unrenderSelection:function(){this.timeGrid.unrenderSelection(),this.dayGrid&&this.dayGrid.unrenderSelection()}}),xb=5,yb=[{hours:1},{minutes:30},{minutes:15},{seconds:30},{seconds:15}];return Na.agenda={"class":wb,defaults:{allDaySlot:!0,allDayText:"all-day",slotDuration:"00:30:00",minTime:"00:00:00",maxTime:"24:00:00",slotEventOverlap:!0}},Na.agendaDay={type:"agenda",duration:{days:1}},Na.agendaWeek={type:"agenda",duration:{weeks:1}},Ma});
1
+ /*!
2
+ * FullCalendar v2.4.0
3
+ * Docs & License: http://fullcalendar.io/
4
+ * (c) 2015 Adam Shaw
5
+ */
6
+ !function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):"object"==typeof exports?module.exports=a(require("jquery"),require("moment")):a(jQuery,moment)}(function(a,b){function c(a){return Q(a,Oa)}function d(b){var c,d={views:b.views||{}};return a.each(b,function(b,e){"views"!=b&&(a.isPlainObject(e)&&!/(time|duration|interval)$/i.test(b)&&-1==a.inArray(b,Oa)?(c=null,a.each(e,function(a,e){/^(month|week|day|default|basic(Week|Day)?|agenda(Week|Day)?)$/.test(a)?(d.views[a]||(d.views[a]={}),d.views[a][b]=e):(c||(c={}),c[a]=e)}),c&&(d[b]=c)):d[b]=e)}),d}function e(a,b){b.left&&a.css({"border-left-width":1,"margin-left":b.left-1}),b.right&&a.css({"border-right-width":1,"margin-right":b.right-1})}function f(a){a.css({"margin-left":"","margin-right":"","border-left-width":"","border-right-width":""})}function g(){a("body").addClass("fc-not-allowed")}function h(){a("body").removeClass("fc-not-allowed")}function i(b,c,d){var e=Math.floor(c/b.length),f=Math.floor(c-e*(b.length-1)),g=[],h=[],i=[],k=0;j(b),b.each(function(c,d){var j=c===b.length-1?f:e,l=a(d).outerHeight(!0);j>l?(g.push(d),h.push(l),i.push(a(d).height())):k+=l}),d&&(c-=k,e=Math.floor(c/g.length),f=Math.floor(c-e*(g.length-1))),a(g).each(function(b,c){var d=b===g.length-1?f:e,j=h[b],k=i[b],l=d-(j-k);d>j&&a(c).height(l)})}function j(a){a.height("")}function k(b){var c=0;return b.find("> *").each(function(b,d){var e=a(d).outerWidth();e>c&&(c=e)}),c++,b.width(c),c}function l(a,b){return a.height(b).addClass("fc-scroller"),a[0].scrollHeight-1>a[0].clientHeight?!0:(m(a),!1)}function m(a){a.height("").removeClass("fc-scroller")}function n(b){var c=b.css("position"),d=b.parents().filter(function(){var b=a(this);return/(auto|scroll)/.test(b.css("overflow")+b.css("overflow-y")+b.css("overflow-x"))}).eq(0);return"fixed"!==c&&d.length?d:a(b[0].ownerDocument||document)}function o(a){var b=a.offset();return{left:b.left,right:b.left+a.outerWidth(),top:b.top,bottom:b.top+a.outerHeight()}}function p(a){var b=a.offset(),c=r(a),d=b.left+u(a,"border-left-width")+c.left,e=b.top+u(a,"border-top-width")+c.top;return{left:d,right:d+a[0].clientWidth,top:e,bottom:e+a[0].clientHeight}}function q(a){var b=a.offset(),c=b.left+u(a,"border-left-width")+u(a,"padding-left"),d=b.top+u(a,"border-top-width")+u(a,"padding-top");return{left:c,right:c+a.width(),top:d,bottom:d+a.height()}}function r(a){var b=a.innerWidth()-a[0].clientWidth,c={left:0,right:0,top:0,bottom:a.innerHeight()-a[0].clientHeight};return s()&&"rtl"==a.css("direction")?c.left=b:c.right=b,c}function s(){return null===Pa&&(Pa=t()),Pa}function t(){var b=a("<div><div/></div>").css({position:"absolute",top:-1e3,left:0,border:0,padding:0,overflow:"scroll",direction:"rtl"}).appendTo("body"),c=b.children(),d=c.offset().left>b.offset().left;return b.remove(),d}function u(a,b){return parseFloat(a.css(b))||0}function v(a){return 1==a.which&&!a.ctrlKey}function w(a,b){var c={left:Math.max(a.left,b.left),right:Math.min(a.right,b.right),top:Math.max(a.top,b.top),bottom:Math.min(a.bottom,b.bottom)};return c.left<c.right&&c.top<c.bottom?c:!1}function x(a,b){return{left:Math.min(Math.max(a.left,b.left),b.right),top:Math.min(Math.max(a.top,b.top),b.bottom)}}function y(a){return{left:(a.left+a.right)/2,top:(a.top+a.bottom)/2}}function z(a,b){return{left:a.left-b.left,top:a.top-b.top}}function A(b){var c,d,e=[],f=[];for("string"==typeof b?f=b.split(/\s*,\s*/):"function"==typeof b?f=[b]:a.isArray(b)&&(f=b),c=0;c<f.length;c++)d=f[c],"string"==typeof d?e.push("-"==d.charAt(0)?{field:d.substring(1),order:-1}:{field:d,order:1}):"function"==typeof d&&e.push({func:d});return e}function B(a,b,c){var d,e;for(d=0;d<c.length;d++)if(e=C(a,b,c[d]))return e;return 0}function C(a,b,c){return c.func?c.func(a,b):D(a[c.field],b[c.field])*(c.order||1)}function D(b,c){return b||c?null==c?-1:null==b?1:"string"===a.type(b)||"string"===a.type(c)?String(b).localeCompare(String(c)):b-c:0}function E(a,b){var c,d,e,f,g=a.start,h=a.end,i=b.start,j=b.end;return h>i&&j>g?(g>=i?(c=g.clone(),e=!0):(c=i.clone(),e=!1),j>=h?(d=h.clone(),f=!0):(d=j.clone(),f=!1),{start:c,end:d,isStart:e,isEnd:f}):void 0}function F(a,c){return b.duration({days:a.clone().stripTime().diff(c.clone().stripTime(),"days"),ms:a.time()-c.time()})}function G(a,c){return b.duration({days:a.clone().stripTime().diff(c.clone().stripTime(),"days")})}function H(a,c,d){return b.duration(Math.round(a.diff(c,d,!0)),d)}function I(a,b){var c,d,e;for(c=0;c<Ra.length&&(d=Ra[c],e=J(d,a,b),!(e>=1&&ba(e)));c++);return d}function J(a,c,d){return null!=d?d.diff(c,a,!0):b.isDuration(c)?c.as(a):c.end.diff(c.start,a,!0)}function K(a,b,c){var d;return N(c)?(b-a)/c:(d=c.asMonths(),Math.abs(d)>=1&&ba(d)?b.diff(a,"months",!0)/d:b.diff(a,"days",!0)/c.asDays())}function L(a,b){var c,d;return N(a)||N(b)?a/b:(c=a.asMonths(),d=b.asMonths(),Math.abs(c)>=1&&ba(c)&&Math.abs(d)>=1&&ba(d)?c/d:a.asDays()/b.asDays())}function M(a,c){var d;return N(a)?b.duration(a*c):(d=a.asMonths(),Math.abs(d)>=1&&ba(d)?b.duration({months:d*c}):b.duration({days:a.asDays()*c}))}function N(a){return Boolean(a.hours()||a.minutes()||a.seconds()||a.milliseconds())}function O(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function P(a){return/^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(a)}function Q(a,b){var c,d,e,f,g,h,i={};if(b)for(c=0;c<b.length;c++){for(d=b[c],e=[],f=a.length-1;f>=0;f--)if(g=a[f][d],"object"==typeof g)e.unshift(g);else if(void 0!==g){i[d]=g;break}e.length&&(i[d]=Q(e))}for(c=a.length-1;c>=0;c--){h=a[c];for(d in h)d in i||(i[d]=h[d])}return i}function R(a){var b=function(){};return b.prototype=a,new b}function S(a,b){for(var c in a)U(a,c)&&(b[c]=a[c])}function T(a,b){var c,d,e=["constructor","toString","valueOf"];for(c=0;c<e.length;c++)d=e[c],a[d]!==Object.prototype[d]&&(b[d]=a[d])}function U(a,b){return Va.call(a,b)}function V(b){return/undefined|null|boolean|number|string/.test(a.type(b))}function W(b,c,d){if(a.isFunction(b)&&(b=[b]),b){var e,f;for(e=0;e<b.length;e++)f=b[e].apply(c,d)||f;return f}}function X(){for(var a=0;a<arguments.length;a++)if(void 0!==arguments[a])return arguments[a]}function Y(a){return(a+"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/'/g,"&#039;").replace(/"/g,"&quot;").replace(/\n/g,"<br />")}function Z(a){return a.replace(/&.*?;/g,"")}function $(b){var c=[];return a.each(b,function(a,b){null!=b&&c.push(a+":"+b)}),c.join(";")}function _(a){return a.charAt(0).toUpperCase()+a.slice(1)}function aa(a,b){return a-b}function ba(a){return a%1===0}function ca(a,b){var c=a[b];return function(){return c.apply(a,arguments)}}function da(a,b){var c,d,e,f,g=function(){var h=+new Date-f;b>h&&h>0?c=setTimeout(g,b-h):(c=null,a.apply(e,d),c||(e=d=null))};return function(){e=this,d=arguments,f=+new Date,c||(c=setTimeout(g,b))}}function ea(c,d,e){var f,g,h,i,j=c[0],k=1==c.length&&"string"==typeof j;return b.isMoment(j)?(i=b.apply(null,c),ga(j,i)):O(j)||void 0===j?i=b.apply(null,c):(f=!1,g=!1,k?Wa.test(j)?(j+="-01",c=[j],f=!0,g=!0):(h=Xa.exec(j))&&(f=!h[5],g=!0):a.isArray(j)&&(g=!0),i=d||f?b.utc.apply(b,c):b.apply(null,c),f?(i._ambigTime=!0,i._ambigZone=!0):e&&(g?i._ambigZone=!0:k&&(i.utcOffset?i.utcOffset(j):i.zone(j)))),i._fullCalendar=!0,i}function fa(a,c){var d,e,f=!1,g=!1,h=a.length,i=[];for(d=0;h>d;d++)e=a[d],b.isMoment(e)||(e=Ma.moment.parseZone(e)),f=f||e._ambigTime,g=g||e._ambigZone,i.push(e);for(d=0;h>d;d++)e=i[d],c||!f||e._ambigTime?g&&!e._ambigZone&&(i[d]=e.clone().stripZone()):i[d]=e.clone().stripTime();return i}function ga(a,b){a._ambigTime?b._ambigTime=!0:b._ambigTime&&(b._ambigTime=!1),a._ambigZone?b._ambigZone=!0:b._ambigZone&&(b._ambigZone=!1)}function ha(a,b){a.year(b[0]||0).month(b[1]||0).date(b[2]||0).hours(b[3]||0).minutes(b[4]||0).seconds(b[5]||0).milliseconds(b[6]||0)}function ia(a,b){return Za.format.call(a,b)}function ja(a,b){return ka(a,pa(b))}function ka(a,b){var c,d="";for(c=0;c<b.length;c++)d+=la(a,b[c]);return d}function la(a,b){var c,d;return"string"==typeof b?b:(c=b.token)?$a[c]?$a[c](a):ia(a,c):b.maybe&&(d=ka(a,b.maybe),d.match(/[1-9]/))?d:""}function ma(a,b,c,d,e){var f;return a=Ma.moment.parseZone(a),b=Ma.moment.parseZone(b),f=(a.localeData||a.lang).call(a),c=f.longDateFormat(c)||c,d=d||" - ",na(a,b,pa(c),d,e)}function na(a,b,c,d,e){var f,g,h,i,j="",k="",l="",m="",n="";for(g=0;g<c.length&&(f=oa(a,b,c[g]),f!==!1);g++)j+=f;for(h=c.length-1;h>g&&(f=oa(a,b,c[h]),f!==!1);h--)k=f+k;for(i=g;h>=i;i++)l+=la(a,c[i]),m+=la(b,c[i]);return(l||m)&&(n=e?m+d+l:l+d+m),j+n+k}function oa(a,b,c){var d,e;return"string"==typeof c?c:(d=c.token)&&(e=_a[d.charAt(0)],e&&a.isSame(b,e))?ia(a,d):!1}function pa(a){return a in ab?ab[a]:ab[a]=qa(a)}function qa(a){for(var b,c=[],d=/\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g;b=d.exec(a);)b[1]?c.push(b[1]):b[2]?c.push({maybe:qa(b[2])}):b[3]?c.push({token:b[3]}):b[5]&&c.push(b[5]);return c}function ra(){}function sa(a,b){return a||b?a&&b?a.grid===b.grid&&a.row===b.row&&a.col===b.col:!1:!0}function ta(a){var b=va(a);return"background"===b||"inverse-background"===b}function ua(a){return"inverse-background"===va(a)}function va(a){return X((a.source||{}).rendering,a.rendering)}function wa(a){var b,c,d={};for(b=0;b<a.length;b++)c=a[b],(d[c._id]||(d[c._id]=[])).push(c);return d}function xa(a,b){return a.eventStartMS-b.eventStartMS}function ya(c){var d,e,f,g,h=Ma.dataAttrPrefix;return h&&(h+="-"),d=c.data(h+"event")||null,d&&(d="object"==typeof d?a.extend({},d):{},e=d.start,null==e&&(e=d.time),f=d.duration,g=d.stick,delete d.start,delete d.time,delete d.duration,delete d.stick),null==e&&(e=c.data(h+"start")),null==e&&(e=c.data(h+"time")),null==f&&(f=c.data(h+"duration")),null==g&&(g=c.data(h+"stick")),e=null!=e?b.duration(e):null,f=null!=f?b.duration(f):null,g=Boolean(g),{eventProps:d,startTime:e,duration:f,stick:g}}function za(a,b){var c,d;for(c=0;c<b.length;c++)if(d=b[c],d.leftCol<=a.rightCol&&d.rightCol>=a.leftCol)return!0;return!1}function Aa(a,b){return a.leftCol-b.leftCol}function Ba(a){var b,c,d,e=[];for(b=0;b<a.length;b++){for(c=a[b],d=0;d<e.length&&Ea(c,e[d]).length;d++);c.level=d,(e[d]||(e[d]=[])).push(c)}return e}function Ca(a){var b,c,d,e,f;for(b=0;b<a.length;b++)for(c=a[b],d=0;d<c.length;d++)for(e=c[d],e.forwardSegs=[],f=b+1;f<a.length;f++)Ea(e,a[f],e.forwardSegs)}function Da(a){var b,c,d=a.forwardSegs,e=0;if(void 0===a.forwardPressure){for(b=0;b<d.length;b++)c=d[b],Da(c),e=Math.max(e,1+c.forwardPressure);a.forwardPressure=e}}function Ea(a,b,c){c=c||[];for(var d=0;d<b.length;d++)Fa(a,b[d])&&c.push(b[d]);return c}function Fa(a,b){return a.bottom>b.top&&a.top<b.bottom}function Ga(c,d){function e(){U?h()&&(k(),i()):f()}function f(){V=O.theme?"ui":"fc",c.addClass("fc"),O.isRTL?c.addClass("fc-rtl"):c.addClass("fc-ltr"),O.theme?c.addClass("ui-widget"):c.addClass("fc-unthemed"),U=a("<div class='fc-view-container'/>").prependTo(c),S=N.header=new Ja(N,O),T=S.render(),T&&c.prepend(T),i(O.defaultView),O.handleWindowResize&&(Y=da(m,O.windowResizeDelay),a(window).resize(Y))}function g(){W&&W.removeElement(),S.removeElement(),U.remove(),c.removeClass("fc fc-ltr fc-rtl fc-unthemed ui-widget"),Y&&a(window).unbind("resize",Y)}function h(){return c.is(":visible")}function i(b){ca++,W&&b&&W.type!==b&&(S.deactivateButton(W.type),H(),W.removeElement(),W=N.view=null),!W&&b&&(W=N.view=ba[b]||(ba[b]=N.instantiateView(b)),W.setElement(a("<div class='fc-view fc-"+b+"-view' />").appendTo(U)),S.activateButton(b)),W&&(Z=W.massageCurrentDate(Z),W.displaying&&Z.isWithin(W.intervalStart,W.intervalEnd)||h()&&(H(),W.display(Z),I(),u(),v(),q())),I(),ca--}function j(a){return h()?(a&&l(),ca++,W.updateSize(!0),ca--,!0):void 0}function k(){h()&&l()}function l(){X="number"==typeof O.contentHeight?O.contentHeight:"number"==typeof O.height?O.height-(T?T.outerHeight(!0):0):Math.round(U.width()/Math.max(O.aspectRatio,.5))}function m(a){!ca&&a.target===window&&W.start&&j(!0)&&W.trigger("windowResize",aa)}function n(){p(),r()}function o(){h()&&(H(),W.displayEvents(ea),I())}function p(){H(),W.clearEvents(),I()}function q(){!O.lazyFetching||$(W.start,W.end)?r():o()}function r(){_(W.start,W.end)}function s(a){ea=a,o()}function t(){o()}function u(){S.updateTitle(W.title)}function v(){var a=N.getNow();a.isWithin(W.intervalStart,W.intervalEnd)?S.disableButton("today"):S.enableButton("today")}function w(a,b){W.select(N.buildSelectRange.apply(N,arguments))}function x(){W&&W.unselect()}function y(){Z=W.computePrevDate(Z),i()}function z(){Z=W.computeNextDate(Z),i()}function A(){Z.add(-1,"years"),i()}function B(){Z.add(1,"years"),i()}function C(){Z=N.getNow(),i()}function D(a){Z=N.moment(a),i()}function E(a){Z.add(b.duration(a)),i()}function F(a,b){var c;b=b||"day",c=N.getViewSpec(b)||N.getUnitViewSpec(b),Z=a,i(c?c.type:null)}function G(){return Z.clone()}function H(){U.css({width:"100%",height:U.height(),overflow:"hidden"})}function I(){U.css({width:"",height:"",overflow:""})}function J(){return N}function K(){return W}function L(a,b){return void 0===b?O[a]:void(("height"==a||"contentHeight"==a||"aspectRatio"==a)&&(O[a]=b,j(!0)))}function M(a,b){var c=Array.prototype.slice.call(arguments,2);return b=b||aa,this.triggerWith(a,b,c),O[a]?O[a].apply(b,c):void 0}var N=this;N.initOptions(d||{});var O=this.options;N.render=e,N.destroy=g,N.refetchEvents=n,N.reportEvents=s,N.reportEventChange=t,N.rerenderEvents=o,N.changeView=i,N.select=w,N.unselect=x,N.prev=y,N.next=z,N.prevYear=A,N.nextYear=B,N.today=C,N.gotoDate=D,N.incrementDate=E,N.zoomTo=F,N.getDate=G,N.getCalendar=J,N.getView=K,N.option=L,N.trigger=M;var P=R(Ia(O.lang));if(O.monthNames&&(P._months=O.monthNames),O.monthNamesShort&&(P._monthsShort=O.monthNamesShort),O.dayNames&&(P._weekdays=O.dayNames),O.dayNamesShort&&(P._weekdaysShort=O.dayNamesShort),null!=O.firstDay){var Q=R(P._week);Q.dow=O.firstDay,P._week=Q}P._fullCalendar_weekCalc=function(a){return"function"==typeof a?a:"local"===a?a:"iso"===a||"ISO"===a?"ISO":void 0}(O.weekNumberCalculation),N.defaultAllDayEventDuration=b.duration(O.defaultAllDayEventDuration),N.defaultTimedEventDuration=b.duration(O.defaultTimedEventDuration),N.moment=function(){var a;return"local"===O.timezone?(a=Ma.moment.apply(null,arguments),a.hasTime()&&a.local()):a="UTC"===O.timezone?Ma.moment.utc.apply(null,arguments):Ma.moment.parseZone.apply(null,arguments),"_locale"in a?a._locale=P:a._lang=P,a},N.getIsAmbigTimezone=function(){return"local"!==O.timezone&&"UTC"!==O.timezone},N.rezoneDate=function(a){return N.moment(a.toArray())},N.getNow=function(){var a=O.now;return"function"==typeof a&&(a=a()),N.moment(a)},N.getEventEnd=function(a){return a.end?a.end.clone():N.getDefaultEventEnd(a.allDay,a.start)},N.getDefaultEventEnd=function(a,b){var c=b.clone();return a?c.stripTime().add(N.defaultAllDayEventDuration):c.add(N.defaultTimedEventDuration),N.getIsAmbigTimezone()&&c.stripZone(),c},N.humanizeDuration=function(a){return(a.locale||a.lang).call(a,O.lang).humanize()},Ka.call(N,O);var S,T,U,V,W,X,Y,Z,$=N.isFetchNeeded,_=N.fetchEvents,aa=c[0],ba={},ca=0,ea=[];Z=null!=O.defaultDate?N.moment(O.defaultDate):N.getNow(),N.getSuggestedViewHeight=function(){return void 0===X&&k(),X},N.isHeightAuto=function(){return"auto"===O.contentHeight||"auto"===O.height},N.initialize()}function Ha(b){a.each(rb,function(a,c){null==b[a]&&(b[a]=c(b))})}function Ia(a){var c=b.localeData||b.langData;return c.call(b,a)||c.call(b,"en")}function Ja(b,c){function d(){var b=c.header;return n=c.theme?"ui":"fc",b?o=a("<div class='fc-toolbar'/>").append(f("left")).append(f("right")).append(f("center")).append('<div class="fc-clear"/>'):void 0}function e(){o.remove(),o=a()}function f(d){var e=a('<div class="fc-'+d+'"/>'),f=c.header[d];return f&&a.each(f.split(" "),function(d){var f,g=a(),h=!0;a.each(this.split(","),function(d,e){var f,i,j,k,l,m,o,q,r,s;"title"==e?(g=g.add(a("<h2>&nbsp;</h2>")),h=!1):((f=(b.options.customButtons||{})[e])?(j=function(a){f.click&&f.click.call(s[0],a)},k="",l=f.text):(i=b.getViewSpec(e))?(j=function(){b.changeView(e)},p.push(e),k=i.buttonTextOverride,l=i.buttonTextDefault):b[e]&&(j=function(){b[e]()},k=(b.overrides.buttonText||{})[e],l=c.buttonText[e]),j&&(m=f?f.themeIcon:c.themeButtonIcons[e],o=f?f.icon:c.buttonIcons[e],q=k?Y(k):m&&c.theme?"<span class='ui-icon ui-icon-"+m+"'></span>":o&&!c.theme?"<span class='fc-icon fc-icon-"+o+"'></span>":Y(l),r=["fc-"+e+"-button",n+"-button",n+"-state-default"],s=a('<button type="button" class="'+r.join(" ")+'">'+q+"</button>").click(function(a){s.hasClass(n+"-state-disabled")||(j(a),(s.hasClass(n+"-state-active")||s.hasClass(n+"-state-disabled"))&&s.removeClass(n+"-state-hover"))}).mousedown(function(){s.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-down")}).mouseup(function(){s.removeClass(n+"-state-down")}).hover(function(){s.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-hover")},function(){s.removeClass(n+"-state-hover").removeClass(n+"-state-down")}),g=g.add(s)))}),h&&g.first().addClass(n+"-corner-left").end().last().addClass(n+"-corner-right").end(),g.length>1?(f=a("<div/>"),h&&f.addClass("fc-button-group"),f.append(g),e.append(f)):e.append(g)}),e}function g(a){o.find("h2").text(a)}function h(a){o.find(".fc-"+a+"-button").addClass(n+"-state-active")}function i(a){o.find(".fc-"+a+"-button").removeClass(n+"-state-active")}function j(a){o.find(".fc-"+a+"-button").attr("disabled","disabled").addClass(n+"-state-disabled")}function k(a){o.find(".fc-"+a+"-button").removeAttr("disabled").removeClass(n+"-state-disabled")}function l(){return p}var m=this;m.render=d,m.removeElement=e,m.updateTitle=g,m.activateButton=h,m.deactivateButton=i,m.disableButton=j,m.enableButton=k,m.getViewsWithButtons=l;var n,o=a(),p=[]}function Ka(c){function d(a,b){return!M||a.clone().stripZone()<M.clone().stripZone()||b.clone().stripZone()>N.clone().stripZone()}function e(a,b){M=a,N=b,U=[];var c=++S,d=R.length;T=d;for(var e=0;d>e;e++)f(R[e],c)}function f(b,c){g(b,function(d){var e,f,g,h=a.isArray(b.events);if(c==S){if(d)for(e=0;e<d.length;e++)f=d[e],g=h?f:s(f,b),g&&U.push.apply(U,x(g));T--,T||O(U)}})}function g(b,d){var e,f,h=Ma.sourceFetchers;for(e=0;e<h.length;e++){if(f=h[e].call(L,b,M.clone(),N.clone(),c.timezone,d),f===!0)return;if("object"==typeof f)return void g(f,d)}var i=b.events;if(i)a.isFunction(i)?(L.pushLoading(),i.call(L,M.clone(),N.clone(),c.timezone,function(a){d(a),L.popLoading()})):a.isArray(i)?d(i):d();else{var j=b.url;if(j){var k,l=b.success,m=b.error,n=b.complete;k=a.isFunction(b.data)?b.data():b.data;var o=a.extend({},k||{}),p=X(b.startParam,c.startParam),q=X(b.endParam,c.endParam),r=X(b.timezoneParam,c.timezoneParam);p&&(o[p]=M.format()),q&&(o[q]=N.format()),c.timezone&&"local"!=c.timezone&&(o[r]=c.timezone),L.pushLoading(),a.ajax(a.extend({},sb,b,{data:o,success:function(b){b=b||[];var c=W(l,this,arguments);a.isArray(c)&&(b=c),d(b)},error:function(){W(m,this,arguments),d()},complete:function(){W(n,this,arguments),L.popLoading()}}))}else d()}}function h(a){var b=i(a);b&&(R.push(b),T++,f(b,S))}function i(b){var c,d,e=Ma.sourceNormalizers;if(a.isFunction(b)||a.isArray(b)?c={events:b}:"string"==typeof b?c={url:b}:"object"==typeof b&&(c=a.extend({},b)),c){for(c.className?"string"==typeof c.className&&(c.className=c.className.split(/\s+/)):c.className=[],a.isArray(c.events)&&(c.origArray=c.events,c.events=a.map(c.events,function(a){return s(a,c)})),d=0;d<e.length;d++)e[d].call(L,c);return c}}function j(b){R=a.grep(R,function(a){return!k(a,b)}),U=a.grep(U,function(a){return!k(a.source,b)}),O(U)}function k(a,b){return a&&b&&l(a)==l(b)}function l(a){return("object"==typeof a?a.origArray||a.googleCalendarId||a.url||a.events:null)||a}function m(a){a.start=L.moment(a.start),a.end?a.end=L.moment(a.end):a.end=null,y(a,n(a)),O(U)}function n(b){var c={};return a.each(b,function(a,b){o(a)&&void 0!==b&&V(b)&&(c[a]=b)}),c}function o(a){return!/^_|^(id|allDay|start|end)$/.test(a)}function p(a,b){var c,d,e,f=s(a);if(f){for(c=x(f),d=0;d<c.length;d++)e=c[d],e.source||(b&&(Q.events.push(e),e.source=Q),U.push(e));return O(U),c}return[]}function q(b){var c,d;for(null==b?b=function(){return!0}:a.isFunction(b)||(c=b+"",b=function(a){return a._id==c}),U=a.grep(U,b,!0),d=0;d<R.length;d++)a.isArray(R[d].events)&&(R[d].events=a.grep(R[d].events,b,!0));O(U)}function r(b){return a.isFunction(b)?a.grep(U,b):null!=b?(b+="",a.grep(U,function(a){return a._id==b})):U}function s(d,e){var f,g,h,i={};if(c.eventDataTransform&&(d=c.eventDataTransform(d)),e&&e.eventDataTransform&&(d=e.eventDataTransform(d)),a.extend(i,d),e&&(i.source=e),i._id=d._id||(void 0===d.id?"_fc"+tb++:d.id+""),d.className?"string"==typeof d.className?i.className=d.className.split(/\s+/):i.className=d.className:i.className=[],f=d.start||d.date,g=d.end,P(f)&&(f=b.duration(f)),P(g)&&(g=b.duration(g)),d.dow||b.isDuration(f)||b.isDuration(g))i.start=f?b.duration(f):null,i.end=g?b.duration(g):null,i._recurring=!0;else{if(f&&(f=L.moment(f),!f.isValid()))return!1;g&&(g=L.moment(g),g.isValid()||(g=null)),h=d.allDay,void 0===h&&(h=X(e?e.allDayDefault:void 0,c.allDayDefault)),t(f,g,h,i)}return i}function t(a,b,c,d){d.start=a,d.end=b,d.allDay=c,u(d),La(d)}function u(a){v(a),a.end&&!a.end.isAfter(a.start)&&(a.end=null),a.end||(c.forceEventDuration?a.end=L.getDefaultEventEnd(a.allDay,a.start):a.end=null)}function v(a){null==a.allDay&&(a.allDay=!(a.start.hasTime()||a.end&&a.end.hasTime())),a.allDay?(a.start.stripTime(),a.end&&a.end.stripTime()):(a.start.hasTime()||(a.start=L.rezoneDate(a.start)),a.end&&!a.end.hasTime()&&(a.end=L.rezoneDate(a.end)))}function w(b){var c;return b.end||(c=b.allDay,null==c&&(c=!b.start.hasTime()),b=a.extend({},b),b.end=L.getDefaultEventEnd(c,b.start)),b}function x(b,c,d){var e,f,g,h,i,j,k,l,m,n=[];if(c=c||M,d=d||N,b)if(b._recurring){if(f=b.dow)for(e={},g=0;g<f.length;g++)e[f[g]]=!0;for(h=c.clone().stripTime();h.isBefore(d);)(!e||e[h.day()])&&(i=b.start,j=b.end,k=h.clone(),l=null,i&&(k=k.time(i)),j&&(l=h.clone().time(j)),m=a.extend({},b),t(k,l,!i&&!j,m),n.push(m)),h.add(1,"days")}else n.push(b);return n}function y(b,c,d){function e(a,b){return d?H(a,b,d):c.allDay?G(a,b):F(a,b)}var f,g,h,i,j,k,l={};return c=c||{},c.start||(c.start=b.start.clone()),void 0===c.end&&(c.end=b.end?b.end.clone():null),null==c.allDay&&(c.allDay=b.allDay),u(c),f={start:b._start.clone(),end:b._end?b._end.clone():L.getDefaultEventEnd(b._allDay,b._start),allDay:c.allDay},u(f),g=null!==b._end&&null===c.end,h=e(c.start,f.start),c.end?(i=e(c.end,f.end),j=i.subtract(h)):j=null,a.each(c,function(a,b){o(a)&&void 0!==b&&(l[a]=b)}),k=z(r(b._id),g,c.allDay,h,j,l),{dateDelta:h,durationDelta:j,undo:k}}function z(b,c,d,e,f,g){var h=L.getIsAmbigTimezone(),i=[];return e&&!e.valueOf()&&(e=null),f&&!f.valueOf()&&(f=null),a.each(b,function(b,j){var k,l;k={start:j.start.clone(),end:j.end?j.end.clone():null,allDay:j.allDay},a.each(g,function(a){k[a]=j[a]}),l={start:j._start,end:j._end,allDay:d},u(l),c?l.end=null:f&&!l.end&&(l.end=L.getDefaultEventEnd(l.allDay,l.start)),e&&(l.start.add(e),l.end&&l.end.add(e)),f&&l.end.add(f),h&&!l.allDay&&(e||f)&&(l.start.stripZone(),l.end&&l.end.stripZone()),a.extend(j,g,l),La(j),i.push(function(){a.extend(j,k),La(j)})}),function(){for(var a=0;a<i.length;a++)i[a]()}}function A(b){var d,e=c.businessHours,f={className:"fc-nonbusiness",start:"09:00",end:"17:00",dow:[1,2,3,4,5],rendering:"inverse-background"},g=L.getView();return e&&(d=a.extend({},f,"object"==typeof e?e:{})),d?(b&&(d.start=null,d.end=null),x(s(d),g.start,g.end)):[]}function B(a,b){var d=b.source||{},e=X(b.constraint,d.constraint,c.eventConstraint),f=X(b.overlap,d.overlap,c.eventOverlap);return a=w(a),E(a,e,f,b)}function C(a){return E(a,c.selectConstraint,c.selectOverlap)}function D(b,c){var d,e;return c&&(d=a.extend({},c,b),e=x(s(d))[0]),e?B(b,e):(b=w(b),C(b))}function E(b,c,d,e){var f,g,h,i,j,k;if(b=a.extend({},b),b.start=b.start.clone().stripZone(),b.end=b.end.clone().stripZone(),null!=c){for(f=I(c),g=!1,i=0;i<f.length;i++)if(J(f[i],b)){g=!0;break}if(!g)return!1}for(h=L.getPeerEvents(e,b),i=0;i<h.length;i++)if(j=h[i],K(j,b)){if(d===!1)return!1;if("function"==typeof d&&!d(j,e))return!1;if(e){if(k=X(j.overlap,(j.source||{}).overlap),k===!1)return!1;if("function"==typeof k&&!k(e,j))return!1}}return!0}function I(a){return"businessHours"===a?A():"object"==typeof a?x(s(a)):r(a)}function J(a,b){var c=a.start.clone().stripZone(),d=L.getEventEnd(a).stripZone();return b.start>=c&&b.end<=d}function K(a,b){var c=a.start.clone().stripZone(),d=L.getEventEnd(a).stripZone();return b.start<d&&b.end>c}var L=this;L.isFetchNeeded=d,L.fetchEvents=e,L.addEventSource=h,L.removeEventSource=j,L.updateEvent=m,L.renderEvent=p,L.removeEvents=q,L.clientEvents=r,L.mutateEvent=y,L.normalizeEventRange=u,L.normalizeEventRangeTimes=v,L.ensureVisibleEventRange=w;var M,N,O=L.reportEvents,Q={events:[]},R=[Q],S=0,T=0,U=[];a.each((c.events?[c.events]:[]).concat(c.eventSources||[]),function(a,b){var c=i(b);c&&R.push(c)}),L.getBusinessHoursEvents=A,L.isEventRangeAllowed=B,L.isSelectionRangeAllowed=C,L.isExternalDropRangeAllowed=D,L.getEventCache=function(){return U}}function La(a){a._allDay=a.allDay,a._start=a.start.clone(),a._end=a.end?a.end.clone():null}var Ma=a.fullCalendar={version:"2.4.0"},Na=Ma.views={};a.fn.fullCalendar=function(b){var c=Array.prototype.slice.call(arguments,1),d=this;return this.each(function(e,f){var g,h=a(f),i=h.data("fullCalendar");"string"==typeof b?i&&a.isFunction(i[b])&&(g=i[b].apply(i,c),e||(d=g),"destroy"===b&&h.removeData("fullCalendar")):i||(i=new nb(h,b),h.data("fullCalendar",i),i.render())}),d};var Oa=["header","buttonText","buttonIcons","themeButtonIcons"];Ma.intersectionToSeg=E,Ma.applyAll=W,Ma.debounce=da,Ma.isInt=ba,Ma.htmlEscape=Y,Ma.cssToStr=$,Ma.proxy=ca,Ma.capitaliseFirstLetter=_,Ma.getClientRect=p,Ma.getContentRect=q,Ma.getScrollbarWidths=r;var Pa=null;Ma.intersectRects=w,Ma.parseFieldSpecs=A,Ma.compareByFieldSpecs=B,Ma.compareByFieldSpec=C,Ma.flexibleCompare=D,Ma.computeIntervalUnit=I,Ma.divideRangeByDuration=K,Ma.divideDurationByDuration=L,Ma.multiplyDuration=M,Ma.durationHasTime=N;var Qa=["sun","mon","tue","wed","thu","fri","sat"],Ra=["year","month","week","day","hour","minute","second","millisecond"];Ma.log=function(){var a=window.console;return a&&a.log?a.log.apply(a,arguments):void 0},Ma.warn=function(){var a=window.console;return a&&a.warn?a.warn.apply(a,arguments):Ma.log.apply(Ma,arguments)};var Sa,Ta,Ua,Va={}.hasOwnProperty,Wa=/^\s*\d{4}-\d\d$/,Xa=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/,Ya=b.fn,Za=a.extend({},Ya);Ma.moment=function(){return ea(arguments)},Ma.moment.utc=function(){var a=ea(arguments,!0);return a.hasTime()&&a.utc(),a},Ma.moment.parseZone=function(){return ea(arguments,!0,!0)},Ya.clone=function(){var a=Za.clone.apply(this,arguments);return ga(this,a),this._fullCalendar&&(a._fullCalendar=!0),a},Ya.week=Ya.weeks=function(a){var b=(this._locale||this._lang)._fullCalendar_weekCalc;return null==a&&"function"==typeof b?b(this):"ISO"===b?Za.isoWeek.apply(this,arguments):Za.week.apply(this,arguments)},Ya.time=function(a){if(!this._fullCalendar)return Za.time.apply(this,arguments);if(null==a)return b.duration({hours:this.hours(),minutes:this.minutes(),seconds:this.seconds(),milliseconds:this.milliseconds()});this._ambigTime=!1,b.isDuration(a)||b.isMoment(a)||(a=b.duration(a));var c=0;return b.isDuration(a)&&(c=24*Math.floor(a.asDays())),this.hours(c+a.hours()).minutes(a.minutes()).seconds(a.seconds()).milliseconds(a.milliseconds())},Ya.stripTime=function(){var a;return this._ambigTime||(a=this.toArray(),this.utc(),Ta(this,a.slice(0,3)),this._ambigTime=!0,this._ambigZone=!0),this},Ya.hasTime=function(){return!this._ambigTime},Ya.stripZone=function(){var a,b;return this._ambigZone||(a=this.toArray(),b=this._ambigTime,this.utc(),Ta(this,a),this._ambigTime=b||!1,this._ambigZone=!0),this},Ya.hasZone=function(){return!this._ambigZone},Ya.local=function(){var a=this.toArray(),b=this._ambigZone;return Za.local.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,b&&Ua(this,a),this},Ya.utc=function(){return Za.utc.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,this},a.each(["zone","utcOffset"],function(a,b){Za[b]&&(Ya[b]=function(a){return null!=a&&(this._ambigTime=!1,this._ambigZone=!1),Za[b].apply(this,arguments)})}),Ya.format=function(){return this._fullCalendar&&arguments[0]?ja(this,arguments[0]):this._ambigTime?ia(this,"YYYY-MM-DD"):this._ambigZone?ia(this,"YYYY-MM-DD[T]HH:mm:ss"):Za.format.apply(this,arguments)},Ya.toISOString=function(){return this._ambigTime?ia(this,"YYYY-MM-DD"):this._ambigZone?ia(this,"YYYY-MM-DD[T]HH:mm:ss"):Za.toISOString.apply(this,arguments)},Ya.isWithin=function(a,b){var c=fa([this,a,b]);return c[0]>=c[1]&&c[0]<c[2]},Ya.isSame=function(a,b){var c;return this._fullCalendar?b?(c=fa([this,a],!0),Za.isSame.call(c[0],c[1],b)):(a=Ma.moment.parseZone(a),Za.isSame.call(this,a)&&Boolean(this._ambigTime)===Boolean(a._ambigTime)&&Boolean(this._ambigZone)===Boolean(a._ambigZone)):Za.isSame.apply(this,arguments)},a.each(["isBefore","isAfter"],function(a,b){Ya[b]=function(a,c){var d;return this._fullCalendar?(d=fa([this,a]),Za[b].call(d[0],d[1],c)):Za[b].apply(this,arguments)}}),Sa="_d"in b()&&"updateOffset"in b,Ta=Sa?function(a,c){a._d.setTime(Date.UTC.apply(Date,c)),b.updateOffset(a,!1)}:ha,Ua=Sa?function(a,c){a._d.setTime(+new Date(c[0]||0,c[1]||0,c[2]||0,c[3]||0,c[4]||0,c[5]||0,c[6]||0)),b.updateOffset(a,!1)}:ha;var $a={t:function(a){return ia(a,"a").charAt(0)},T:function(a){return ia(a,"A").charAt(0)}};Ma.formatRange=ma;var _a={Y:"year",M:"month",D:"day",d:"day",A:"second",a:"second",T:"second",t:"second",H:"second",h:"second",m:"second",s:"second"},ab={};Ma.Class=ra,ra.extend=function(a){var b,c=this;return a=a||{},U(a,"constructor")&&(b=a.constructor),"function"!=typeof b&&(b=a.constructor=function(){c.apply(this,arguments)}),b.prototype=R(c.prototype),S(a,b.prototype),T(a,b.prototype),S(c,b),b},ra.mixin=function(a){S(a.prototype||a,this.prototype)};var bb=Ma.Emitter=ra.extend({callbackHash:null,on:function(a,b){return this.getCallbacks(a).add(b),this},off:function(a,b){return this.getCallbacks(a).remove(b),this},trigger:function(a){var b=Array.prototype.slice.call(arguments,1);return this.triggerWith(a,this,b),this},triggerWith:function(a,b,c){var d=this.getCallbacks(a);return d.fireWith(b,c),this},getCallbacks:function(b){var c;return this.callbackHash||(this.callbackHash={}),c=this.callbackHash[b],c||(c=this.callbackHash[b]=a.Callbacks()),c}}),cb=ra.extend({isHidden:!0,options:null,el:null,documentMousedownProxy:null,margin:10,constructor:function(a){this.options=a||{}},show:function(){this.isHidden&&(this.el||this.render(),this.el.show(),this.position(),this.isHidden=!1,this.trigger("show"))},hide:function(){this.isHidden||(this.el.hide(),this.isHidden=!0,this.trigger("hide"))},render:function(){var b=this,c=this.options;this.el=a('<div class="fc-popover"/>').addClass(c.className||"").css({top:0,left:0}).append(c.content).appendTo(c.parentEl),this.el.on("click",".fc-close",function(){b.hide()}),c.autoHide&&a(document).on("mousedown",this.documentMousedownProxy=ca(this,"documentMousedown"))},documentMousedown:function(b){this.el&&!a(b.target).closest(this.el).length&&this.hide()},removeElement:function(){this.hide(),this.el&&(this.el.remove(),this.el=null),a(document).off("mousedown",this.documentMousedownProxy)},position:function(){var b,c,d,e,f,g=this.options,h=this.el.offsetParent().offset(),i=this.el.outerWidth(),j=this.el.outerHeight(),k=a(window),l=n(this.el);e=g.top||0,f=void 0!==g.left?g.left:void 0!==g.right?g.right-i:0,l.is(window)||l.is(document)?(l=k,b=0,c=0):(d=l.offset(),b=d.top,c=d.left),b+=k.scrollTop(),c+=k.scrollLeft(),g.viewportConstrain!==!1&&(e=Math.min(e,b+l.outerHeight()-j-this.margin),e=Math.max(e,b+this.margin),f=Math.min(f,c+l.outerWidth()-i-this.margin),f=Math.max(f,c+this.margin)),this.el.css({top:e-h.top,left:f-h.left})},trigger:function(a){this.options[a]&&this.options[a].apply(this,Array.prototype.slice.call(arguments,1));
7
+ }}),db=ra.extend({grid:null,rowCoords:null,colCoords:null,containerEl:null,bounds:null,constructor:function(a){this.grid=a},build:function(){this.grid.build(),this.rowCoords=this.grid.computeRowCoords(),this.colCoords=this.grid.computeColCoords(),this.computeBounds()},clear:function(){this.grid.clear(),this.rowCoords=null,this.colCoords=null},getCell:function(b,c){var d,e,f,g=this.rowCoords,h=g.length,i=this.colCoords,j=i.length,k=null,l=null;if(this.inBounds(b,c)){for(d=0;h>d;d++)if(e=g[d],c>=e.top&&c<e.bottom){k=d;break}for(d=0;j>d;d++)if(e=i[d],b>=e.left&&b<e.right){l=d;break}if(null!==k&&null!==l)return f=this.grid.getCell(k,l),f.grid=this.grid,a.extend(f,g[k],i[l]),f}return null},computeBounds:function(){this.bounds=this.containerEl?p(this.containerEl):null},inBounds:function(a,b){var c=this.bounds;return c?a>=c.left&&a<c.right&&b>=c.top&&b<c.bottom:!0}}),eb=ra.extend({coordMaps:null,constructor:function(a){this.coordMaps=a},build:function(){var a,b=this.coordMaps;for(a=0;a<b.length;a++)b[a].build()},getCell:function(a,b){var c,d=this.coordMaps,e=null;for(c=0;c<d.length&&!e;c++)e=d[c].getCell(a,b);return e},clear:function(){var a,b=this.coordMaps;for(a=0;a<b.length;a++)b[a].clear()}}),fb=Ma.DragListener=ra.extend({options:null,isListening:!1,isDragging:!1,originX:null,originY:null,mousemoveProxy:null,mouseupProxy:null,subjectEl:null,subjectHref:null,scrollEl:null,scrollBounds:null,scrollTopVel:null,scrollLeftVel:null,scrollIntervalId:null,scrollHandlerProxy:null,scrollSensitivity:30,scrollSpeed:200,scrollIntervalMs:50,constructor:function(a){a=a||{},this.options=a,this.subjectEl=a.subjectEl},mousedown:function(a){v(a)&&(a.preventDefault(),this.startListening(a),this.options.distance||this.startDrag(a))},startListening:function(b){var c;this.isListening||(b&&this.options.scroll&&(c=n(a(b.target)),c.is(window)||c.is(document)||(this.scrollEl=c,this.scrollHandlerProxy=da(ca(this,"scrollHandler"),100),this.scrollEl.on("scroll",this.scrollHandlerProxy))),a(document).on("mousemove",this.mousemoveProxy=ca(this,"mousemove")).on("mouseup",this.mouseupProxy=ca(this,"mouseup")).on("selectstart",this.preventDefault),b?(this.originX=b.pageX,this.originY=b.pageY):(this.originX=0,this.originY=0),this.isListening=!0,this.listenStart(b))},listenStart:function(a){this.trigger("listenStart",a)},mousemove:function(a){var b,c,d=a.pageX-this.originX,e=a.pageY-this.originY;this.isDragging||(b=this.options.distance||1,c=d*d+e*e,c>=b*b&&this.startDrag(a)),this.isDragging&&this.drag(d,e,a)},startDrag:function(a){this.isListening||this.startListening(),this.isDragging||(this.isDragging=!0,this.dragStart(a))},dragStart:function(a){var b=this.subjectEl;this.trigger("dragStart",a),(this.subjectHref=b?b.attr("href"):null)&&b.removeAttr("href")},drag:function(a,b,c){this.trigger("drag",a,b,c),this.updateScroll(c)},mouseup:function(a){this.stopListening(a)},stopDrag:function(a){this.isDragging&&(this.stopScrolling(),this.dragStop(a),this.isDragging=!1)},dragStop:function(a){var b=this;this.trigger("dragStop",a),setTimeout(function(){b.subjectHref&&b.subjectEl.attr("href",b.subjectHref)},0)},stopListening:function(b){this.stopDrag(b),this.isListening&&(this.scrollEl&&(this.scrollEl.off("scroll",this.scrollHandlerProxy),this.scrollHandlerProxy=null),a(document).off("mousemove",this.mousemoveProxy).off("mouseup",this.mouseupProxy).off("selectstart",this.preventDefault),this.mousemoveProxy=null,this.mouseupProxy=null,this.isListening=!1,this.listenStop(b))},listenStop:function(a){this.trigger("listenStop",a)},trigger:function(a){this.options[a]&&this.options[a].apply(this,Array.prototype.slice.call(arguments,1))},preventDefault:function(a){a.preventDefault()},computeScrollBounds:function(){var a=this.scrollEl;this.scrollBounds=a?o(a):null},updateScroll:function(a){var b,c,d,e,f=this.scrollSensitivity,g=this.scrollBounds,h=0,i=0;g&&(b=(f-(a.pageY-g.top))/f,c=(f-(g.bottom-a.pageY))/f,d=(f-(a.pageX-g.left))/f,e=(f-(g.right-a.pageX))/f,b>=0&&1>=b?h=b*this.scrollSpeed*-1:c>=0&&1>=c&&(h=c*this.scrollSpeed),d>=0&&1>=d?i=d*this.scrollSpeed*-1:e>=0&&1>=e&&(i=e*this.scrollSpeed)),this.setScrollVel(h,i)},setScrollVel:function(a,b){this.scrollTopVel=a,this.scrollLeftVel=b,this.constrainScrollVel(),!this.scrollTopVel&&!this.scrollLeftVel||this.scrollIntervalId||(this.scrollIntervalId=setInterval(ca(this,"scrollIntervalFunc"),this.scrollIntervalMs))},constrainScrollVel:function(){var a=this.scrollEl;this.scrollTopVel<0?a.scrollTop()<=0&&(this.scrollTopVel=0):this.scrollTopVel>0&&a.scrollTop()+a[0].clientHeight>=a[0].scrollHeight&&(this.scrollTopVel=0),this.scrollLeftVel<0?a.scrollLeft()<=0&&(this.scrollLeftVel=0):this.scrollLeftVel>0&&a.scrollLeft()+a[0].clientWidth>=a[0].scrollWidth&&(this.scrollLeftVel=0)},scrollIntervalFunc:function(){var a=this.scrollEl,b=this.scrollIntervalMs/1e3;this.scrollTopVel&&a.scrollTop(a.scrollTop()+this.scrollTopVel*b),this.scrollLeftVel&&a.scrollLeft(a.scrollLeft()+this.scrollLeftVel*b),this.constrainScrollVel(),this.scrollTopVel||this.scrollLeftVel||this.stopScrolling()},stopScrolling:function(){this.scrollIntervalId&&(clearInterval(this.scrollIntervalId),this.scrollIntervalId=null,this.scrollStop())},scrollHandler:function(){this.scrollIntervalId||this.scrollStop()},scrollStop:function(){}}),gb=fb.extend({coordMap:null,origCell:null,cell:null,coordAdjust:null,constructor:function(a,b){fb.prototype.constructor.call(this,b),this.coordMap=a},listenStart:function(a){var b,c,d,e=this.subjectEl;fb.prototype.listenStart.apply(this,arguments),this.computeCoords(),a?(c={left:a.pageX,top:a.pageY},d=c,e&&(b=o(e),d=x(d,b)),this.origCell=this.getCell(d.left,d.top),e&&this.options.subjectCenter&&(this.origCell&&(b=w(this.origCell,b)||b),d=y(b)),this.coordAdjust=z(d,c)):(this.origCell=null,this.coordAdjust=null)},computeCoords:function(){this.coordMap.build(),this.computeScrollBounds()},dragStart:function(a){var b;fb.prototype.dragStart.apply(this,arguments),b=this.getCell(a.pageX,a.pageY),b&&this.cellOver(b)},drag:function(a,b,c){var d;fb.prototype.drag.apply(this,arguments),d=this.getCell(c.pageX,c.pageY),sa(d,this.cell)||(this.cell&&this.cellOut(),d&&this.cellOver(d))},dragStop:function(){this.cellDone(),fb.prototype.dragStop.apply(this,arguments)},cellOver:function(a){this.cell=a,this.trigger("cellOver",a,sa(a,this.origCell),this.origCell)},cellOut:function(){this.cell&&(this.trigger("cellOut",this.cell),this.cellDone(),this.cell=null)},cellDone:function(){this.cell&&this.trigger("cellDone",this.cell)},listenStop:function(){fb.prototype.listenStop.apply(this,arguments),this.origCell=this.cell=null,this.coordMap.clear()},scrollStop:function(){fb.prototype.scrollStop.apply(this,arguments),this.computeCoords()},getCell:function(a,b){return this.coordAdjust&&(a+=this.coordAdjust.left,b+=this.coordAdjust.top),this.coordMap.getCell(a,b)}}),hb=ra.extend({options:null,sourceEl:null,el:null,parentEl:null,top0:null,left0:null,mouseY0:null,mouseX0:null,topDelta:null,leftDelta:null,mousemoveProxy:null,isFollowing:!1,isHidden:!1,isAnimating:!1,constructor:function(b,c){this.options=c=c||{},this.sourceEl=b,this.parentEl=c.parentEl?a(c.parentEl):b.parent()},start:function(b){this.isFollowing||(this.isFollowing=!0,this.mouseY0=b.pageY,this.mouseX0=b.pageX,this.topDelta=0,this.leftDelta=0,this.isHidden||this.updatePosition(),a(document).on("mousemove",this.mousemoveProxy=ca(this,"mousemove")))},stop:function(b,c){function d(){this.isAnimating=!1,e.removeElement(),this.top0=this.left0=null,c&&c()}var e=this,f=this.options.revertDuration;this.isFollowing&&!this.isAnimating&&(this.isFollowing=!1,a(document).off("mousemove",this.mousemoveProxy),b&&f&&!this.isHidden?(this.isAnimating=!0,this.el.animate({top:this.top0,left:this.left0},{duration:f,complete:d})):d())},getEl:function(){var a=this.el;return a||(this.sourceEl.width(),a=this.el=this.sourceEl.clone().css({position:"absolute",visibility:"",display:this.isHidden?"none":"",margin:0,right:"auto",bottom:"auto",width:this.sourceEl.width(),height:this.sourceEl.height(),opacity:this.options.opacity||"",zIndex:this.options.zIndex}).appendTo(this.parentEl)),a},removeElement:function(){this.el&&(this.el.remove(),this.el=null)},updatePosition:function(){var a,b;this.getEl(),null===this.top0&&(this.sourceEl.width(),a=this.sourceEl.offset(),b=this.el.offsetParent().offset(),this.top0=a.top-b.top,this.left0=a.left-b.left),this.el.css({top:this.top0+this.topDelta,left:this.left0+this.leftDelta})},mousemove:function(a){this.topDelta=a.pageY-this.mouseY0,this.leftDelta=a.pageX-this.mouseX0,this.isHidden||this.updatePosition()},hide:function(){this.isHidden||(this.isHidden=!0,this.el&&this.el.hide())},show:function(){this.isHidden&&(this.isHidden=!1,this.updatePosition(),this.getEl().show())}}),ib=ra.extend({view:null,isRTL:null,cellHtml:"<td/>",constructor:function(a){this.view=a,this.isRTL=a.opt("isRTL")},rowHtml:function(a,b){var c,d,e=this.getHtmlRenderer("cell",a),f="";for(b=b||0,c=0;c<this.colCnt;c++)d=this.getCell(b,c),f+=e(d);return f=this.bookendCells(f,a,b),"<tr>"+f+"</tr>"},bookendCells:function(a,b,c){var d=this.getHtmlRenderer("intro",b)(c||0),e=this.getHtmlRenderer("outro",b)(c||0),f=this.isRTL?e:d,g=this.isRTL?d:e;return"string"==typeof a?f+a+g:a.prepend(f).append(g)},getHtmlRenderer:function(a,b){var c,d,e,f,g=this.view;return c=a+"Html",b&&(d=b+_(a)+"Html"),d&&(f=g[d])?e=g:d&&(f=this[d])?e=this:(f=g[c])?e=g:(f=this[c])&&(e=this),"function"==typeof f?function(){return f.apply(e,arguments)||""}:function(){return f||""}}}),jb=Ma.Grid=ib.extend({start:null,end:null,rowCnt:0,colCnt:0,el:null,coordMap:null,elsByFill:null,externalDragStartProxy:null,colHeadFormat:null,eventTimeFormat:null,displayEventTime:null,displayEventEnd:null,cellDuration:null,largeUnit:null,constructor:function(){ib.apply(this,arguments),this.coordMap=new db(this),this.elsByFill={},this.externalDragStartProxy=ca(this,"externalDragStart")},computeColHeadFormat:function(){},computeEventTimeFormat:function(){return this.view.opt("smallTimeFormat")},computeDisplayEventTime:function(){return!0},computeDisplayEventEnd:function(){return!0},setRange:function(a){this.start=a.start.clone(),this.end=a.end.clone(),this.rangeUpdated(),this.processRangeOptions()},rangeUpdated:function(){},processRangeOptions:function(){var a,b,c=this.view;this.colHeadFormat=c.opt("columnFormat")||this.computeColHeadFormat(),this.eventTimeFormat=c.opt("eventTimeFormat")||c.opt("timeFormat")||this.computeEventTimeFormat(),a=c.opt("displayEventTime"),null==a&&(a=this.computeDisplayEventTime()),b=c.opt("displayEventEnd"),null==b&&(b=this.computeDisplayEventEnd()),this.displayEventTime=a,this.displayEventEnd=b},build:function(){},clear:function(){},rangeToSegs:function(a){},diffDates:function(a,b){return this.largeUnit?H(a,b,this.largeUnit):F(a,b)},getCell:function(b,c){var d;return null==c&&("number"==typeof b?(c=b%this.colCnt,b=Math.floor(b/this.colCnt)):(c=b.col,b=b.row)),d={row:b,col:c},a.extend(d,this.getRowData(b),this.getColData(c)),a.extend(d,this.computeCellRange(d)),d},computeCellRange:function(a){var b=this.computeCellDate(a);return{start:b,end:b.clone().add(this.cellDuration)}},computeCellDate:function(a){},getRowData:function(a){return{}},getColData:function(a){return{}},getRowEl:function(a){},getColEl:function(a){},getCellDayEl:function(a){return this.getColEl(a.col)||this.getRowEl(a.row)},computeRowCoords:function(){var a,b,c,d=[];for(a=0;a<this.rowCnt;a++)b=this.getRowEl(a),c=b.offset().top,d.push({top:c,bottom:c+b.outerHeight()});return d},computeColCoords:function(){var a,b,c,d=[];for(a=0;a<this.colCnt;a++)b=this.getColEl(a),c=b.offset().left,d.push({left:c,right:c+b.outerWidth()});return d},setElement:function(b){var c=this;this.el=b,b.on("mousedown",function(b){a(b.target).is(".fc-event-container *, .fc-more")||a(b.target).closest(".fc-popover").length||c.dayMousedown(b)}),this.bindSegHandlers(),this.bindGlobalHandlers()},removeElement:function(){this.unbindGlobalHandlers(),this.el.remove()},renderSkeleton:function(){},renderDates:function(){},unrenderDates:function(){},bindGlobalHandlers:function(){a(document).on("dragstart sortstart",this.externalDragStartProxy)},unbindGlobalHandlers:function(){a(document).off("dragstart sortstart",this.externalDragStartProxy)},dayMousedown:function(a){var b,c,d=this,e=this.view,f=e.opt("selectable"),i=new gb(this.coordMap,{scroll:e.opt("dragScroll"),dragStart:function(){e.unselect()},cellOver:function(a,e,h){h&&(b=e?a:null,f&&(c=d.computeSelection(h,a),c?d.renderSelection(c):g()))},cellOut:function(a){b=null,c=null,d.unrenderSelection(),h()},listenStop:function(a){b&&e.triggerDayClick(b,d.getCellDayEl(b),a),c&&e.reportSelection(c,a),h()}});i.mousedown(a)},renderRangeHelper:function(a,b){var c=this.fabricateHelperEvent(a,b);this.renderHelper(c,b)},fabricateHelperEvent:function(a,b){var c=b?R(b.event):{};return c.start=a.start.clone(),c.end=a.end?a.end.clone():null,c.allDay=null,this.view.calendar.normalizeEventRange(c),c.className=(c.className||[]).concat("fc-helper"),b||(c.editable=!1),c},renderHelper:function(a,b){},unrenderHelper:function(){},renderSelection:function(a){this.renderHighlight(this.selectionRangeToSegs(a))},unrenderSelection:function(){this.unrenderHighlight()},computeSelection:function(a,b){var c,d=[a.start,a.end,b.start,b.end];return d.sort(aa),c={start:d[0].clone(),end:d[3].clone()},this.view.calendar.isSelectionRangeAllowed(c)?c:null},selectionRangeToSegs:function(a){return this.rangeToSegs(a)},renderHighlight:function(a){this.renderFill("highlight",a)},unrenderHighlight:function(){this.unrenderFill("highlight")},highlightSegClasses:function(){return["fc-highlight"]},renderFill:function(a,b){},unrenderFill:function(a){var b=this.elsByFill[a];b&&(b.remove(),delete this.elsByFill[a])},renderFillSegEls:function(b,c){var d,e=this,f=this[b+"SegEl"],g="",h=[];if(c.length){for(d=0;d<c.length;d++)g+=this.fillSegHtml(b,c[d]);a(g).each(function(b,d){var g=c[b],i=a(d);f&&(i=f.call(e,g,i)),i&&(i=a(i),i.is(e.fillSegTag)&&(g.el=i,h.push(g)))})}return h},fillSegTag:"div",fillSegHtml:function(a,b){var c=this[a+"SegClasses"],d=this[a+"SegCss"],e=c?c.call(this,b):[],f=$(d?d.call(this,b):{});return"<"+this.fillSegTag+(e.length?' class="'+e.join(" ")+'"':"")+(f?' style="'+f+'"':"")+" />"},headHtml:function(){return'<div class="fc-row '+this.view.widgetHeaderClass+'"><table><thead>'+this.rowHtml("head")+"</thead></table></div>"},headCellHtml:function(a){var b=this.view,c=a.start;return'<th class="fc-day-header '+b.widgetHeaderClass+" fc-"+Qa[c.day()]+'">'+Y(c.format(this.colHeadFormat))+"</th>"},bgCellHtml:function(a){var b=this.view,c=a.start,d=this.getDayClasses(c);return d.unshift("fc-day",b.widgetContentClass),'<td class="'+d.join(" ")+'" data-date="'+c.format("YYYY-MM-DD")+'"></td>'},getDayClasses:function(a){var b=this.view,c=b.calendar.getNow().stripTime(),d=["fc-"+Qa[a.day()]];return 1==b.intervalDuration.as("months")&&a.month()!=b.intervalStart.month()&&d.push("fc-other-month"),a.isSame(c,"day")?d.push("fc-today",b.highlightStateClass):c>a?d.push("fc-past"):d.push("fc-future"),d}});jb.mixin({mousedOverSeg:null,isDraggingSeg:!1,isResizingSeg:!1,isDraggingExternal:!1,segs:null,renderEvents:function(a){var b,c,d=this.eventsToSegs(a),e=[],f=[];for(b=0;b<d.length;b++)c=d[b],ta(c.event)?e.push(c):f.push(c);e=this.renderBgSegs(e)||e,f=this.renderFgSegs(f)||f,this.segs=e.concat(f)},unrenderEvents:function(){this.triggerSegMouseout(),this.unrenderFgSegs(),this.unrenderBgSegs(),this.segs=null},getEventSegs:function(){return this.segs||[]},renderFgSegs:function(a){},unrenderFgSegs:function(){},renderFgSegEls:function(b,c){var d,e=this.view,f="",g=[];if(b.length){for(d=0;d<b.length;d++)f+=this.fgSegHtml(b[d],c);a(f).each(function(c,d){var f=b[c],h=e.resolveEventEl(f.event,a(d));h&&(h.data("fc-seg",f),f.el=h,g.push(f))})}return g},fgSegHtml:function(a,b){},renderBgSegs:function(a){return this.renderFill("bgEvent",a)},unrenderBgSegs:function(){this.unrenderFill("bgEvent")},bgEventSegEl:function(a,b){return this.view.resolveEventEl(a.event,b)},bgEventSegClasses:function(a){var b=a.event,c=b.source||{};return["fc-bgevent"].concat(b.className,c.className||[])},bgEventSegCss:function(a){var b=this.view,c=a.event,d=c.source||{};return{"background-color":c.backgroundColor||c.color||d.backgroundColor||d.color||b.opt("eventBackgroundColor")||b.opt("eventColor")}},businessHoursSegClasses:function(a){return["fc-nonbusiness","fc-bgevent"]},bindSegHandlers:function(){var b=this,c=this.view;a.each({mouseenter:function(a,c){b.triggerSegMouseover(a,c)},mouseleave:function(a,c){b.triggerSegMouseout(a,c)},click:function(a,b){return c.trigger("eventClick",this,a.event,b)},mousedown:function(d,e){a(e.target).is(".fc-resizer")&&c.isEventResizable(d.event)?b.segResizeMousedown(d,e,a(e.target).is(".fc-start-resizer")):c.isEventDraggable(d.event)&&b.segDragMousedown(d,e)}},function(c,d){b.el.on(c,".fc-event-container > *",function(c){var e=a(this).data("fc-seg");return!e||b.isDraggingSeg||b.isResizingSeg?void 0:d.call(this,e,c)})})},triggerSegMouseover:function(a,b){this.mousedOverSeg||(this.mousedOverSeg=a,this.view.trigger("eventMouseover",a.el[0],a.event,b))},triggerSegMouseout:function(a,b){b=b||{},this.mousedOverSeg&&(a=a||this.mousedOverSeg,this.mousedOverSeg=null,this.view.trigger("eventMouseout",a.el[0],a.event,b))},segDragMousedown:function(a,b){var c,d=this,e=this.view,f=e.calendar,i=a.el,j=a.event,k=new hb(a.el,{parentEl:e.el,opacity:e.opt("dragOpacity"),revertDuration:e.opt("dragRevertDuration"),zIndex:2}),l=new gb(e.coordMap,{distance:5,scroll:e.opt("dragScroll"),subjectEl:i,subjectCenter:!0,listenStart:function(a){k.hide(),k.start(a)},dragStart:function(b){d.triggerSegMouseout(a,b),d.segDragStart(a,b),e.hideEvent(j)},cellOver:function(b,h,i){a.cell&&(i=a.cell),c=d.computeEventDrop(i,b,j),c&&!f.isEventRangeAllowed(c,j)&&(g(),c=null),c&&e.renderDrag(c,a)?k.hide():k.show(),h&&(c=null)},cellOut:function(){e.unrenderDrag(),k.show(),c=null},cellDone:function(){h()},dragStop:function(b){k.stop(!c,function(){e.unrenderDrag(),e.showEvent(j),d.segDragStop(a,b),c&&e.reportEventDrop(j,c,this.largeUnit,i,b)})},listenStop:function(){k.stop()}});l.mousedown(b)},segDragStart:function(a,b){this.isDraggingSeg=!0,this.view.trigger("eventDragStart",a.el[0],a.event,b,{})},segDragStop:function(a,b){this.isDraggingSeg=!1,this.view.trigger("eventDragStop",a.el[0],a.event,b,{})},computeEventDrop:function(a,b,c){var d,e,f=this.view.calendar,g=a.start,h=b.start;return g.hasTime()===h.hasTime()?(d=this.diffDates(h,g),c.allDay&&N(d)?(e={start:c.start.clone(),end:f.getEventEnd(c),allDay:!1},f.normalizeEventRangeTimes(e)):e={start:c.start.clone(),end:c.end?c.end.clone():null,allDay:c.allDay},e.start.add(d),e.end&&e.end.add(d)):e={start:h.clone(),end:null,allDay:!h.hasTime()},e},applyDragOpacity:function(a){var b=this.view.opt("dragOpacity");null!=b&&a.each(function(a,c){c.style.opacity=b})},externalDragStart:function(b,c){var d,e,f=this.view;f.opt("droppable")&&(d=a((c?c.item:null)||b.target),e=f.opt("dropAccept"),(a.isFunction(e)?e.call(d[0],d):d.is(e))&&(this.isDraggingExternal||this.listenToExternalDrag(d,b,c)))},listenToExternalDrag:function(a,b,c){var d,e,f=this,i=ya(a);d=new gb(this.coordMap,{listenStart:function(){f.isDraggingExternal=!0},cellOver:function(a){e=f.computeExternalDrop(a,i),e?f.renderDrag(e):g()},cellOut:function(){e=null,f.unrenderDrag(),h()},dragStop:function(){f.unrenderDrag(),h(),e&&f.view.reportExternalDrop(i,e,a,b,c)},listenStop:function(){f.isDraggingExternal=!1}}),d.startDrag(b)},computeExternalDrop:function(a,b){var c={start:a.start.clone(),end:null};return b.startTime&&!c.start.hasTime()&&c.start.time(b.startTime),b.duration&&(c.end=c.start.clone().add(b.duration)),this.view.calendar.isExternalDropRangeAllowed(c,b.eventProps)?c:null},renderDrag:function(a,b){},unrenderDrag:function(){},segResizeMousedown:function(a,b,c){var d,e,f=this,i=this.view,j=i.calendar,k=a.el,l=a.event,m=j.getEventEnd(l);d=new gb(this.coordMap,{distance:5,scroll:i.opt("dragScroll"),subjectEl:k,dragStart:function(b){f.triggerSegMouseout(a,b),f.segResizeStart(a,b)},cellOver:function(b,d,h){e=c?f.computeEventStartResize(h,b,l):f.computeEventEndResize(h,b,l),e&&(j.isEventRangeAllowed(e,l)?e.start.isSame(l.start)&&e.end.isSame(m)&&(e=null):(g(),e=null)),e&&(i.hideEvent(l),f.renderEventResize(e,a))},cellOut:function(){e=null},cellDone:function(){f.unrenderEventResize(),i.showEvent(l),h()},dragStop:function(b){f.segResizeStop(a,b),e&&i.reportEventResize(l,e,this.largeUnit,k,b)}}),d.mousedown(b)},segResizeStart:function(a,b){this.isResizingSeg=!0,this.view.trigger("eventResizeStart",a.el[0],a.event,b,{})},segResizeStop:function(a,b){this.isResizingSeg=!1,this.view.trigger("eventResizeStop",a.el[0],a.event,b,{})},computeEventStartResize:function(a,b,c){return this.computeEventResize("start",a,b,c)},computeEventEndResize:function(a,b,c){return this.computeEventResize("end",a,b,c)},computeEventResize:function(a,b,c,d){var e,f,g=this.view.calendar,h=this.diffDates(c[a],b[a]);return e={start:d.start.clone(),end:g.getEventEnd(d),allDay:d.allDay},e.allDay&&N(h)&&(e.allDay=!1,g.normalizeEventRangeTimes(e)),e[a].add(h),e.start.isBefore(e.end)||(f=d.allDay?g.defaultAllDayEventDuration:g.defaultTimedEventDuration,this.cellDuration&&this.cellDuration<f&&(f=this.cellDuration),"start"==a?e.start=e.end.clone().subtract(f):e.end=e.start.clone().add(f)),e},renderEventResize:function(a,b){},unrenderEventResize:function(){},getEventTimeText:function(a,b,c){return null==b&&(b=this.eventTimeFormat),null==c&&(c=this.displayEventEnd),this.displayEventTime&&a.start.hasTime()?c&&a.end?this.view.formatRange(a,b):a.start.format(b):""},getSegClasses:function(a,b,c){var d=a.event,e=["fc-event",a.isStart?"fc-start":"fc-not-start",a.isEnd?"fc-end":"fc-not-end"].concat(d.className,d.source?d.source.className:[]);return b&&e.push("fc-draggable"),c&&e.push("fc-resizable"),e},getEventSkinCss:function(a){var b=this.view,c=a.source||{},d=a.color,e=c.color,f=b.opt("eventColor");return{"background-color":a.backgroundColor||d||c.backgroundColor||e||b.opt("eventBackgroundColor")||f,"border-color":a.borderColor||d||c.borderColor||e||b.opt("eventBorderColor")||f,color:a.textColor||c.textColor||b.opt("eventTextColor")}},eventsToSegs:function(a,b){var c,d=this.eventsToRanges(a),e=[];for(c=0;c<d.length;c++)e.push.apply(e,this.eventRangeToSegs(d[c],b));return e},eventsToRanges:function(b){var c=this,d=wa(b),e=[];return a.each(d,function(a,b){b.length&&e.push.apply(e,ua(b[0])?c.eventsToInverseRanges(b):c.eventsToNormalRanges(b))}),e},eventsToNormalRanges:function(a){var b,c,d,e,f=this.view.calendar,g=[];for(b=0;b<a.length;b++)c=a[b],d=c.start.clone().stripZone(),e=f.getEventEnd(c).stripZone(),g.push({event:c,start:d,end:e,eventStartMS:+d,eventDurationMS:e-d});return g},eventsToInverseRanges:function(a){var b,c,d=this.view,e=d.start.clone().stripZone(),f=d.end.clone().stripZone(),g=this.eventsToNormalRanges(a),h=[],i=a[0],j=e;for(g.sort(xa),b=0;b<g.length;b++)c=g[b],c.start>j&&h.push({event:i,start:j,end:c.start}),j=c.end;return f>j&&h.push({event:i,start:j,end:f}),h},eventRangeToSegs:function(a,b){var c,d,e;for(a=this.view.calendar.ensureVisibleEventRange(a),c=b?b(a):this.rangeToSegs(a),d=0;d<c.length;d++)e=c[d],e.event=a.event,e.eventStartMS=a.eventStartMS,e.eventDurationMS=a.eventDurationMS;return c},sortSegs:function(a){a.sort(ca(this,"compareSegs"))},compareSegs:function(a,b){return a.eventStartMS-b.eventStartMS||b.eventDurationMS-a.eventDurationMS||b.event.allDay-a.event.allDay||B(a.event,b.event,this.view.eventOrderSpecs)}}),Ma.dataAttrPrefix="";var kb=jb.extend({numbersVisible:!1,bottomCoordPadding:0,breakOnWeeks:null,cellDates:null,dayToCellOffsets:null,rowEls:null,dayEls:null,helperEls:null,constructor:function(){jb.apply(this,arguments),this.cellDuration=b.duration(1,"day")},renderDates:function(a){var b,c,d,e=this.view,f=this.rowCnt,g=this.colCnt,h=f*g,i="";for(b=0;f>b;b++)i+=this.dayRowHtml(b,a);for(this.el.html(i),this.rowEls=this.el.find(".fc-row"),this.dayEls=this.el.find(".fc-day"),c=0;h>c;c++)d=this.getCell(c),e.trigger("dayRender",null,d.start,this.dayEls.eq(c))},unrenderDates:function(){this.removeSegPopover()},renderBusinessHours:function(){var a=this.view.calendar.getBusinessHoursEvents(!0),b=this.eventsToSegs(a);this.renderFill("businessHours",b,"bgevent")},dayRowHtml:function(a,b){var c=this.view,d=["fc-row","fc-week",c.widgetContentClass];return b&&d.push("fc-rigid"),'<div class="'+d.join(" ")+'"><div class="fc-bg"><table>'+this.rowHtml("day",a)+'</table></div><div class="fc-content-skeleton"><table>'+(this.numbersVisible?"<thead>"+this.rowHtml("number",a)+"</thead>":"")+"</table></div></div>"},dayCellHtml:function(a){return this.bgCellHtml(a)},computeColHeadFormat:function(){return this.rowCnt>1?"ddd":this.colCnt>1?this.view.opt("dayOfMonthFormat"):"dddd"},computeEventTimeFormat:function(){return this.view.opt("extraSmallTimeFormat")},computeDisplayEventEnd:function(){return 1==this.colCnt},rangeUpdated:function(){var a,b,c,d;if(this.updateCellDates(),a=this.cellDates,this.breakOnWeeks){for(b=a[0].day(),d=1;d<a.length&&a[d].day()!=b;d++);c=Math.ceil(a.length/d)}else c=1,d=a.length;this.rowCnt=c,this.colCnt=d},updateCellDates:function(){for(var a=this.view,b=this.start.clone(),c=[],d=-1,e=[];b.isBefore(this.end);)a.isHiddenDay(b)?e.push(d+.5):(d++,e.push(d),c.push(b.clone())),b.add(1,"days");this.cellDates=c,this.dayToCellOffsets=e},computeCellDate:function(a){var b=this.colCnt,c=a.row*b+(this.isRTL?b-a.col-1:a.col);return this.cellDates[c].clone()},getRowEl:function(a){return this.rowEls.eq(a)},getColEl:function(a){return this.dayEls.eq(a)},getCellDayEl:function(a){return this.dayEls.eq(a.row*this.colCnt+a.col)},computeRowCoords:function(){var a=jb.prototype.computeRowCoords.call(this);return a[a.length-1].bottom+=this.bottomCoordPadding,a},rangeToSegs:function(a){var b,c,d,e,f,g,h,i,j,k,l=this.isRTL,m=this.rowCnt,n=this.colCnt,o=[];for(a=this.view.computeDayRange(a),b=this.dateToCellOffset(a.start),c=this.dateToCellOffset(a.end.subtract(1,"days")),d=0;m>d;d++)e=d*n,f=e+n-1,i=Math.max(e,b),j=Math.min(f,c),i=Math.ceil(i),j=Math.floor(j),j>=i&&(g=i===b,h=j===c,i-=e,j-=e,k={row:d,isStart:g,isEnd:h},l?(k.leftCol=n-j-1,k.rightCol=n-i-1):(k.leftCol=i,k.rightCol=j),o.push(k));return o},dateToCellOffset:function(a){var b=this.dayToCellOffsets,c=a.diff(this.start,"days");return 0>c?b[0]-1:c>=b.length?b[b.length-1]+1:b[c]},renderDrag:function(a,b){return this.renderHighlight(this.eventRangeToSegs(a)),b&&!b.el.closest(this.el).length?(this.renderRangeHelper(a,b),this.applyDragOpacity(this.helperEls),!0):void 0},unrenderDrag:function(){this.unrenderHighlight(),this.unrenderHelper()},renderEventResize:function(a,b){this.renderHighlight(this.eventRangeToSegs(a)),this.renderRangeHelper(a,b)},unrenderEventResize:function(){this.unrenderHighlight(),this.unrenderHelper()},renderHelper:function(b,c){var d,e=[],f=this.eventsToSegs([b]);f=this.renderFgSegEls(f),d=this.renderSegRows(f),this.rowEls.each(function(b,f){var g,h=a(f),i=a('<div class="fc-helper-skeleton"><table/></div>');g=c&&c.row===b?c.el.position().top:h.find(".fc-content-skeleton tbody").position().top,i.css("top",g).find("table").append(d[b].tbodyEl),h.append(i),e.push(i[0])}),this.helperEls=a(e)},unrenderHelper:function(){this.helperEls&&(this.helperEls.remove(),this.helperEls=null)},fillSegTag:"td",renderFill:function(b,c,d){var e,f,g,h=[];for(c=this.renderFillSegEls(b,c),e=0;e<c.length;e++)f=c[e],g=this.renderFillRow(b,f,d),this.rowEls.eq(f.row).append(g),h.push(g[0]);return this.elsByFill[b]=a(h),c},renderFillRow:function(b,c,d){var e,f,g=this.colCnt,h=c.leftCol,i=c.rightCol+1;return d=d||b.toLowerCase(),e=a('<div class="fc-'+d+'-skeleton"><table><tr/></table></div>'),f=e.find("tr"),h>0&&f.append('<td colspan="'+h+'"/>'),f.append(c.el.attr("colspan",i-h)),g>i&&f.append('<td colspan="'+(g-i)+'"/>'),this.bookendCells(f,b),e}});kb.mixin({rowStructs:null,unrenderEvents:function(){this.removeSegPopover(),jb.prototype.unrenderEvents.apply(this,arguments)},getEventSegs:function(){return jb.prototype.getEventSegs.call(this).concat(this.popoverSegs||[])},renderBgSegs:function(b){var c=a.grep(b,function(a){return a.event.allDay});return jb.prototype.renderBgSegs.call(this,c)},renderFgSegs:function(b){var c;return b=this.renderFgSegEls(b),c=this.rowStructs=this.renderSegRows(b),this.rowEls.each(function(b,d){a(d).find(".fc-content-skeleton > table").append(c[b].tbodyEl)}),b},unrenderFgSegs:function(){for(var a,b=this.rowStructs||[];a=b.pop();)a.tbodyEl.remove();this.rowStructs=null},renderSegRows:function(a){var b,c,d=[];for(b=this.groupSegRows(a),c=0;c<b.length;c++)d.push(this.renderSegRow(c,b[c]));return d},fgSegHtml:function(a,b){var c,d,e=this.view,f=a.event,g=e.isEventDraggable(f),h=!b&&f.allDay&&a.isStart&&e.isEventResizableFromStart(f),i=!b&&f.allDay&&a.isEnd&&e.isEventResizableFromEnd(f),j=this.getSegClasses(a,g,h||i),k=$(this.getEventSkinCss(f)),l="";return j.unshift("fc-day-grid-event","fc-h-event"),a.isStart&&(c=this.getEventTimeText(f),c&&(l='<span class="fc-time">'+Y(c)+"</span>")),d='<span class="fc-title">'+(Y(f.title||"")||"&nbsp;")+"</span>",'<a class="'+j.join(" ")+'"'+(f.url?' href="'+Y(f.url)+'"':"")+(k?' style="'+k+'"':"")+'><div class="fc-content">'+(this.isRTL?d+" "+l:l+" "+d)+"</div>"+(h?'<div class="fc-resizer fc-start-resizer" />':"")+(i?'<div class="fc-resizer fc-end-resizer" />':"")+"</a>"},renderSegRow:function(b,c){function d(b){for(;b>g;)k=(r[e-1]||[])[g],k?k.attr("rowspan",parseInt(k.attr("rowspan")||1,10)+1):(k=a("<td/>"),h.append(k)),q[e][g]=k,r[e][g]=k,g++}var e,f,g,h,i,j,k,l=this.colCnt,m=this.buildSegLevels(c),n=Math.max(1,m.length),o=a("<tbody/>"),p=[],q=[],r=[];for(e=0;n>e;e++){if(f=m[e],g=0,h=a("<tr/>"),p.push([]),q.push([]),r.push([]),f)for(i=0;i<f.length;i++){for(j=f[i],d(j.leftCol),k=a('<td class="fc-event-container"/>').append(j.el),j.leftCol!=j.rightCol?k.attr("colspan",j.rightCol-j.leftCol+1):r[e][g]=k;g<=j.rightCol;)q[e][g]=k,p[e][g]=j,g++;h.append(k)}d(l),this.bookendCells(h,"eventSkeleton"),o.append(h)}return{row:b,tbodyEl:o,cellMatrix:q,segMatrix:p,segLevels:m,segs:c}},buildSegLevels:function(a){var b,c,d,e=[];for(this.sortSegs(a),b=0;b<a.length;b++){for(c=a[b],d=0;d<e.length&&za(c,e[d]);d++);c.level=d,(e[d]||(e[d]=[])).push(c)}for(d=0;d<e.length;d++)e[d].sort(Aa);return e},groupSegRows:function(a){var b,c=[];for(b=0;b<this.rowCnt;b++)c.push([]);for(b=0;b<a.length;b++)c[a[b].row].push(a[b]);return c}}),kb.mixin({segPopover:null,popoverSegs:null,removeSegPopover:function(){this.segPopover&&this.segPopover.hide()},limitRows:function(a){var b,c,d=this.rowStructs||[];for(b=0;b<d.length;b++)this.unlimitRow(b),c=a?"number"==typeof a?a:this.computeRowLevelLimit(b):!1,c!==!1&&this.limitRow(b,c)},computeRowLevelLimit:function(b){function c(b,c){f=Math.max(f,a(c).outerHeight())}var d,e,f,g=this.rowEls.eq(b),h=g.height(),i=this.rowStructs[b].tbodyEl.children();for(d=0;d<i.length;d++)if(e=i.eq(d).removeClass("fc-limited"),f=0,e.find("> td > :first-child").each(c),e.position().top+f>h)return d;return!1},limitRow:function(b,c){function d(d){for(;d>x;)e=u.getCell(b,x),k=u.getCellSegs(e,c),k.length&&(n=g[c-1][x],t=u.renderMoreLink(e,k),s=a("<div/>").append(t),n.append(s),w.push(s[0])),x++}var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u=this,v=this.rowStructs[b],w=[],x=0;if(c&&c<v.segLevels.length){for(f=v.segLevels[c-1],g=v.cellMatrix,h=v.tbodyEl.children().slice(c).addClass("fc-limited").get(),i=0;i<f.length;i++){for(j=f[i],d(j.leftCol),m=[],l=0;x<=j.rightCol;)e=this.getCell(b,x),k=this.getCellSegs(e,c),m.push(k),l+=k.length,x++;if(l){for(n=g[c-1][j.leftCol],o=n.attr("rowspan")||1,p=[],q=0;q<m.length;q++)r=a('<td class="fc-more-cell"/>').attr("rowspan",o),k=m[q],e=this.getCell(b,j.leftCol+q),t=this.renderMoreLink(e,[j].concat(k)),s=a("<div/>").append(t),r.append(s),p.push(r[0]),w.push(r[0]);n.addClass("fc-limited").after(a(p)),h.push(n[0])}}d(this.colCnt),v.moreEls=a(w),v.limitedEls=a(h)}},
8
+ unlimitRow:function(a){var b=this.rowStructs[a];b.moreEls&&(b.moreEls.remove(),b.moreEls=null),b.limitedEls&&(b.limitedEls.removeClass("fc-limited"),b.limitedEls=null)},renderMoreLink:function(b,c){var d=this,e=this.view;return a('<a class="fc-more"/>').text(this.getMoreLinkText(c.length)).on("click",function(f){var g=e.opt("eventLimitClick"),h=b.start,i=a(this),j=d.getCellDayEl(b),k=d.getCellSegs(b),l=d.resliceDaySegs(k,h),m=d.resliceDaySegs(c,h);"function"==typeof g&&(g=e.trigger("eventLimitClick",null,{date:h,dayEl:j,moreEl:i,segs:l,hiddenSegs:m},f)),"popover"===g?d.showSegPopover(b,i,l):"string"==typeof g&&e.calendar.zoomTo(h,g)})},showSegPopover:function(a,b,c){var d,e,f=this,g=this.view,h=b.parent();d=1==this.rowCnt?g.el:this.rowEls.eq(a.row),e={className:"fc-more-popover",content:this.renderSegPopoverContent(a,c),parentEl:this.el,top:d.offset().top,autoHide:!0,viewportConstrain:g.opt("popoverViewportConstrain"),hide:function(){f.segPopover.removeElement(),f.segPopover=null,f.popoverSegs=null}},this.isRTL?e.right=h.offset().left+h.outerWidth()+1:e.left=h.offset().left-1,this.segPopover=new cb(e),this.segPopover.show()},renderSegPopoverContent:function(b,c){var d,e=this.view,f=e.opt("theme"),g=b.start.format(e.opt("dayPopoverFormat")),h=a('<div class="fc-header '+e.widgetHeaderClass+'"><span class="fc-close '+(f?"ui-icon ui-icon-closethick":"fc-icon fc-icon-x")+'"></span><span class="fc-title">'+Y(g)+'</span><div class="fc-clear"/></div><div class="fc-body '+e.widgetContentClass+'"><div class="fc-event-container"></div></div>'),i=h.find(".fc-event-container");for(c=this.renderFgSegEls(c,!0),this.popoverSegs=c,d=0;d<c.length;d++)c[d].cell=b,i.append(c[d].el);return h},resliceDaySegs:function(b,c){var d=a.map(b,function(a){return a.event}),e=c.clone().stripTime(),f=e.clone().add(1,"days"),g={start:e,end:f};return b=this.eventsToSegs(d,function(a){var b=E(a,g);return b?[b]:[]}),this.sortSegs(b),b},getMoreLinkText:function(a){var b=this.view.opt("eventLimitText");return"function"==typeof b?b(a):"+"+a+" "+b},getCellSegs:function(a,b){for(var c,d=this.rowStructs[a.row].segMatrix,e=b||0,f=[];e<d.length;)c=d[e][a.col],c&&f.push(c),e++;return f}});var lb=jb.extend({slotDuration:null,snapDuration:null,minTime:null,maxTime:null,colDates:null,labelFormat:null,labelInterval:null,dayEls:null,slatEls:null,slatTops:null,helperEl:null,businessHourSegs:null,constructor:function(){jb.apply(this,arguments),this.processOptions()},renderDates:function(){this.el.html(this.renderHtml()),this.dayEls=this.el.find(".fc-day"),this.slatEls=this.el.find(".fc-slats tr")},renderBusinessHours:function(){var a=this.view.calendar.getBusinessHoursEvents();this.businessHourSegs=this.renderFill("businessHours",this.eventsToSegs(a),"bgevent")},renderHtml:function(){return'<div class="fc-bg"><table>'+this.rowHtml("slotBg")+'</table></div><div class="fc-slats"><table>'+this.slatRowHtml()+"</table></div>"},slotBgCellHtml:function(a){return this.bgCellHtml(a)},slatRowHtml:function(){for(var a,c,d,e=this.view,f=this.isRTL,g="",h=b.duration(+this.minTime);h<this.maxTime;)a=this.start.clone().time(h),c=ba(L(h,this.labelInterval)),d='<td class="fc-axis fc-time '+e.widgetContentClass+'" '+e.axisStyleAttr()+">"+(c?"<span>"+Y(a.format(this.labelFormat))+"</span>":"")+"</td>",g+="<tr "+(c?"":'class="fc-minor"')+">"+(f?"":d)+'<td class="'+e.widgetContentClass+'"/>'+(f?d:"")+"</tr>",h.add(this.slotDuration);return g},processOptions:function(){var c,d=this.view,e=d.opt("slotDuration"),f=d.opt("snapDuration");e=b.duration(e),f=f?b.duration(f):e,this.slotDuration=e,this.snapDuration=f,this.cellDuration=f,this.minTime=b.duration(d.opt("minTime")),this.maxTime=b.duration(d.opt("maxTime")),c=d.opt("slotLabelFormat"),a.isArray(c)&&(c=c[c.length-1]),this.labelFormat=c||d.opt("axisFormat")||d.opt("smallTimeFormat"),c=d.opt("slotLabelInterval"),this.labelInterval=c?b.duration(c):this.computeLabelInterval(e)},computeLabelInterval:function(a){var c,d,e;for(c=yb.length-1;c>=0;c--)if(d=b.duration(yb[c]),e=L(d,a),ba(e)&&e>1)return d;return b.duration(a)},computeColHeadFormat:function(){return this.colCnt>1?this.view.opt("dayOfMonthFormat"):"dddd"},computeEventTimeFormat:function(){return this.view.opt("noMeridiemTimeFormat")},computeDisplayEventEnd:function(){return!0},rangeUpdated:function(){var a,b=this.view,c=[];for(a=this.start.clone();a.isBefore(this.end);)c.push(a.clone()),a.add(1,"day"),a=b.skipHiddenDays(a);this.isRTL&&c.reverse(),this.colDates=c,this.colCnt=c.length,this.rowCnt=Math.ceil((this.maxTime-this.minTime)/this.snapDuration)},computeCellDate:function(a){var b=this.colDates[a.col],c=this.computeSnapTime(a.row);return b=this.view.calendar.rezoneDate(b),b.time(c),b},getColEl:function(a){return this.dayEls.eq(a)},computeSnapTime:function(a){return b.duration(this.minTime+this.snapDuration*a)},rangeToSegs:function(a){var b,c,d,e,f=this.colCnt,g=[];for(a={start:a.start.clone().stripZone(),end:a.end.clone().stripZone()},c=0;f>c;c++)d=this.colDates[c],e={start:d.clone().time(this.minTime),end:d.clone().time(this.maxTime)},b=E(a,e),b&&(b.col=c,g.push(b));return g},updateSize:function(a){this.computeSlatTops(),a&&this.updateSegVerticals()},computeRowCoords:function(){var a,b,c=this.el.offset().top,d=[];for(a=0;a<this.rowCnt;a++)b={top:c+this.computeTimeTop(this.computeSnapTime(a))},a>0&&(d[a-1].bottom=b.top),d.push(b);return b.bottom=b.top+this.computeTimeTop(this.computeSnapTime(a)),d},computeDateTop:function(a,c){return this.computeTimeTop(b.duration(a.clone().stripZone()-c.clone().stripTime()))},computeTimeTop:function(a){var b,c,d,e,f=(a-this.minTime)/this.slotDuration;return f=Math.max(0,f),f=Math.min(this.slatEls.length,f),b=Math.floor(f),c=f-b,d=this.slatTops[b],c?(e=this.slatTops[b+1],d+(e-d)*c):d},computeSlatTops:function(){var b,c=[];this.slatEls.each(function(d,e){b=a(e).position().top,c.push(b)}),c.push(b+this.slatEls.last().outerHeight()),this.slatTops=c},renderDrag:function(a,b){return b?(this.renderRangeHelper(a,b),this.applyDragOpacity(this.helperEl),!0):void this.renderHighlight(this.eventRangeToSegs(a))},unrenderDrag:function(){this.unrenderHelper(),this.unrenderHighlight()},renderEventResize:function(a,b){this.renderRangeHelper(a,b)},unrenderEventResize:function(){this.unrenderHelper()},renderHelper:function(b,c){var d,e,f,g,h=this.eventsToSegs([b]);for(h=this.renderFgSegEls(h),d=this.renderSegTable(h),e=0;e<h.length;e++)f=h[e],c&&c.col===f.col&&(g=c.el,f.el.css({left:g.css("left"),right:g.css("right"),"margin-left":g.css("margin-left"),"margin-right":g.css("margin-right")}));this.helperEl=a('<div class="fc-helper-skeleton"/>').append(d).appendTo(this.el)},unrenderHelper:function(){this.helperEl&&(this.helperEl.remove(),this.helperEl=null)},renderSelection:function(a){this.view.opt("selectHelper")?this.renderRangeHelper(a):this.renderHighlight(this.selectionRangeToSegs(a))},unrenderSelection:function(){this.unrenderHelper(),this.unrenderHighlight()},renderFill:function(b,c,d){var e,f,g,h,i,j,k,l,m,n;if(c.length){for(c=this.renderFillSegEls(b,c),e=this.groupSegCols(c),d=d||b.toLowerCase(),f=a('<div class="fc-'+d+'-skeleton"><table><tr/></table></div>'),g=f.find("tr"),h=0;h<e.length;h++)if(i=e[h],j=a("<td/>").appendTo(g),i.length)for(k=a('<div class="fc-'+d+'-container"/>').appendTo(j),l=this.colDates[h],m=0;m<i.length;m++)n=i[m],k.append(n.el.css({top:this.computeDateTop(n.start,l),bottom:-this.computeDateTop(n.end,l)}));this.bookendCells(g,b),this.el.append(f),this.elsByFill[b]=f}return c}});lb.mixin({eventSkeletonEl:null,renderFgSegs:function(b){return b=this.renderFgSegEls(b),this.el.append(this.eventSkeletonEl=a('<div class="fc-content-skeleton"/>').append(this.renderSegTable(b))),b},unrenderFgSegs:function(a){this.eventSkeletonEl&&(this.eventSkeletonEl.remove(),this.eventSkeletonEl=null)},renderSegTable:function(b){var c,d,e,f,g,h,i=a("<table><tr/></table>"),j=i.find("tr");for(c=this.groupSegCols(b),this.computeSegVerticals(b),f=0;f<c.length;f++){for(g=c[f],this.placeSlotSegs(g),h=a('<div class="fc-event-container"/>'),d=0;d<g.length;d++)e=g[d],e.el.css(this.generateSegPositionCss(e)),e.bottom-e.top<30&&e.el.addClass("fc-short"),h.append(e.el);j.append(a("<td/>").append(h))}return this.bookendCells(j,"eventSkeleton"),i},placeSlotSegs:function(a){var b,c,d;if(this.sortSegs(a),b=Ba(a),Ca(b),c=b[0]){for(d=0;d<c.length;d++)Da(c[d]);for(d=0;d<c.length;d++)this.computeSlotSegCoords(c[d],0,0)}},computeSlotSegCoords:function(a,b,c){var d,e=a.forwardSegs;if(void 0===a.forwardCoord)for(e.length?(this.sortForwardSlotSegs(e),this.computeSlotSegCoords(e[0],b+1,c),a.forwardCoord=e[0].backwardCoord):a.forwardCoord=1,a.backwardCoord=a.forwardCoord-(a.forwardCoord-c)/(b+1),d=0;d<e.length;d++)this.computeSlotSegCoords(e[d],0,a.forwardCoord)},updateSegVerticals:function(){var a,b=(this.segs||[]).concat(this.businessHourSegs||[]);for(this.computeSegVerticals(b),a=0;a<b.length;a++)b[a].el.css(this.generateSegVerticalCss(b[a]))},computeSegVerticals:function(a){var b,c;for(b=0;b<a.length;b++)c=a[b],c.top=this.computeDateTop(c.start,c.start),c.bottom=this.computeDateTop(c.end,c.start)},fgSegHtml:function(a,b){var c,d,e,f=this.view,g=a.event,h=f.isEventDraggable(g),i=!b&&a.isStart&&f.isEventResizableFromStart(g),j=!b&&a.isEnd&&f.isEventResizableFromEnd(g),k=this.getSegClasses(a,h,i||j),l=$(this.getEventSkinCss(g));return k.unshift("fc-time-grid-event","fc-v-event"),f.isMultiDayEvent(g)?(a.isStart||a.isEnd)&&(c=this.getEventTimeText(a),d=this.getEventTimeText(a,"LT"),e=this.getEventTimeText(a,null,!1)):(c=this.getEventTimeText(g),d=this.getEventTimeText(g,"LT"),e=this.getEventTimeText(g,null,!1)),'<a class="'+k.join(" ")+'"'+(g.url?' href="'+Y(g.url)+'"':"")+(l?' style="'+l+'"':"")+'><div class="fc-content">'+(c?'<div class="fc-time" data-start="'+Y(e)+'" data-full="'+Y(d)+'"><span>'+Y(c)+"</span></div>":"")+(g.title?'<div class="fc-title">'+Y(g.title)+"</div>":"")+'</div><div class="fc-bg"/>'+(j?'<div class="fc-resizer fc-end-resizer" />':"")+"</a>"},generateSegPositionCss:function(a){var b,c,d=this.view.opt("slotEventOverlap"),e=a.backwardCoord,f=a.forwardCoord,g=this.generateSegVerticalCss(a);return d&&(f=Math.min(1,e+2*(f-e))),this.isRTL?(b=1-f,c=e):(b=e,c=1-f),g.zIndex=a.level+1,g.left=100*b+"%",g.right=100*c+"%",d&&a.forwardPressure&&(g[this.isRTL?"marginLeft":"marginRight"]=20),g},generateSegVerticalCss:function(a){return{top:a.top,bottom:-a.bottom}},groupSegCols:function(a){var b,c=[];for(b=0;b<this.colCnt;b++)c.push([]);for(b=0;b<a.length;b++)c[a[b].col].push(a[b]);return c},sortForwardSlotSegs:function(a){a.sort(ca(this,"compareForwardSlotSegs"))},compareForwardSlotSegs:function(a,b){return b.forwardPressure-a.forwardPressure||(a.backwardCoord||0)-(b.backwardCoord||0)||this.compareSegs(a,b)}});var mb=Ma.View=ra.extend({type:null,name:null,title:null,calendar:null,options:null,coordMap:null,el:null,displaying:null,isSkeletonRendered:!1,isEventsRendered:!1,start:null,end:null,intervalStart:null,intervalEnd:null,intervalDuration:null,intervalUnit:null,isRTL:!1,isSelected:!1,eventOrderSpecs:null,scrollerEl:null,scrollTop:null,widgetHeaderClass:null,widgetContentClass:null,highlightStateClass:null,nextDayThreshold:null,isHiddenDayHash:null,documentMousedownProxy:null,constructor:function(a,c,d,e){this.calendar=a,this.type=this.name=c,this.options=d,this.intervalDuration=e||b.duration(1,"day"),this.nextDayThreshold=b.duration(this.opt("nextDayThreshold")),this.initThemingProps(),this.initHiddenDays(),this.isRTL=this.opt("isRTL"),this.eventOrderSpecs=A(this.opt("eventOrder")),this.documentMousedownProxy=ca(this,"documentMousedown"),this.initialize()},initialize:function(){},opt:function(a){return this.options[a]},trigger:function(a,b){var c=this.calendar;return c.trigger.apply(c,[a,b||this].concat(Array.prototype.slice.call(arguments,2),[this]))},setDate:function(a){this.setRange(this.computeRange(a))},setRange:function(b){a.extend(this,b),this.updateTitle()},computeRange:function(a){var b,c,d=I(this.intervalDuration),e=a.clone().startOf(d),f=e.clone().add(this.intervalDuration);return/year|month|week|day/.test(d)?(e.stripTime(),f.stripTime()):(e.hasTime()||(e=this.calendar.rezoneDate(e)),f.hasTime()||(f=this.calendar.rezoneDate(f))),b=e.clone(),b=this.skipHiddenDays(b),c=f.clone(),c=this.skipHiddenDays(c,-1,!0),{intervalUnit:d,intervalStart:e,intervalEnd:f,start:b,end:c}},computePrevDate:function(a){return this.massageCurrentDate(a.clone().startOf(this.intervalUnit).subtract(this.intervalDuration),-1)},computeNextDate:function(a){return this.massageCurrentDate(a.clone().startOf(this.intervalUnit).add(this.intervalDuration))},massageCurrentDate:function(a,b){return this.intervalDuration.as("days")<=1&&this.isHiddenDay(a)&&(a=this.skipHiddenDays(a,b),a.startOf("day")),a},updateTitle:function(){this.title=this.computeTitle()},computeTitle:function(){return this.formatRange({start:this.intervalStart,end:this.intervalEnd},this.opt("titleFormat")||this.computeTitleFormat(),this.opt("titleRangeSeparator"))},computeTitleFormat:function(){return"year"==this.intervalUnit?"YYYY":"month"==this.intervalUnit?this.opt("monthYearFormat"):this.intervalDuration.as("days")>1?"ll":"LL"},formatRange:function(a,b,c){var d=a.end;return d.hasTime()||(d=d.clone().subtract(1)),ma(a.start,d,b,c,this.opt("isRTL"))},setElement:function(a){this.el=a,this.bindGlobalHandlers()},removeElement:function(){this.clear(),this.isSkeletonRendered&&(this.unrenderSkeleton(),this.isSkeletonRendered=!1),this.unbindGlobalHandlers(),this.el.remove()},display:function(b){var c=this,d=null;return this.displaying&&(d=this.queryScroll()),this.clear().then(function(){return c.displaying=a.when(c.displayView(b)).then(function(){c.forceScroll(c.computeInitialScroll(d)),c.triggerRender()})})},clear:function(){var b=this,c=this.displaying;return c?c.then(function(){return b.displaying=null,b.clearEvents(),b.clearView()}):a.when()},displayView:function(a){this.isSkeletonRendered||(this.renderSkeleton(),this.isSkeletonRendered=!0),this.setDate(a),this.render&&this.render(),this.renderDates(),this.updateSize(),this.renderBusinessHours()},clearView:function(){this.unselect(),this.triggerUnrender(),this.unrenderBusinessHours(),this.unrenderDates(),this.destroy&&this.destroy()},renderSkeleton:function(){},unrenderSkeleton:function(){},renderDates:function(){},unrenderDates:function(){},renderBusinessHours:function(){},unrenderBusinessHours:function(){},triggerRender:function(){this.trigger("viewRender",this,this,this.el)},triggerUnrender:function(){this.trigger("viewDestroy",this,this,this.el)},bindGlobalHandlers:function(){a(document).on("mousedown",this.documentMousedownProxy)},unbindGlobalHandlers:function(){a(document).off("mousedown",this.documentMousedownProxy)},initThemingProps:function(){var a=this.opt("theme")?"ui":"fc";this.widgetHeaderClass=a+"-widget-header",this.widgetContentClass=a+"-widget-content",this.highlightStateClass=a+"-state-highlight"},updateSize:function(a){var b;a&&(b=this.queryScroll()),this.updateHeight(a),this.updateWidth(a),a&&this.setScroll(b)},updateWidth:function(a){},updateHeight:function(a){var b=this.calendar;this.setHeight(b.getSuggestedViewHeight(),b.isHeightAuto())},setHeight:function(a,b){},computeScrollerHeight:function(a){var b,c,d=this.scrollerEl;return b=this.el.add(d),b.css({position:"relative",left:-1}),c=this.el.outerHeight()-d.height(),b.css({position:"",left:""}),a-c},computeInitialScroll:function(a){return 0},queryScroll:function(){return this.scrollerEl?this.scrollerEl.scrollTop():void 0},setScroll:function(a){return this.scrollerEl?this.scrollerEl.scrollTop(a):void 0},forceScroll:function(a){var b=this;this.setScroll(a),setTimeout(function(){b.setScroll(a)},0)},displayEvents:function(a){var b=this.queryScroll();this.clearEvents(),this.renderEvents(a),this.isEventsRendered=!0,this.setScroll(b),this.triggerEventRender()},clearEvents:function(){this.isEventsRendered&&(this.triggerEventUnrender(),this.destroyEvents&&this.destroyEvents(),this.unrenderEvents(),this.isEventsRendered=!1)},renderEvents:function(a){},unrenderEvents:function(){},triggerEventRender:function(){this.renderedEventSegEach(function(a){this.trigger("eventAfterRender",a.event,a.event,a.el)}),this.trigger("eventAfterAllRender")},triggerEventUnrender:function(){this.renderedEventSegEach(function(a){this.trigger("eventDestroy",a.event,a.event,a.el)})},resolveEventEl:function(b,c){var d=this.trigger("eventRender",b,b,c);return d===!1?c=null:d&&d!==!0&&(c=a(d)),c},showEvent:function(a){this.renderedEventSegEach(function(a){a.el.css("visibility","")},a)},hideEvent:function(a){this.renderedEventSegEach(function(a){a.el.css("visibility","hidden")},a)},renderedEventSegEach:function(a,b){var c,d=this.getEventSegs();for(c=0;c<d.length;c++)b&&d[c].event._id!==b._id||d[c].el&&a.call(this,d[c])},getEventSegs:function(){return[]},isEventDraggable:function(a){var b=a.source||{};return X(a.startEditable,b.startEditable,this.opt("eventStartEditable"),a.editable,b.editable,this.opt("editable"))},reportEventDrop:function(a,b,c,d,e){var f=this.calendar,g=f.mutateEvent(a,b,c),h=function(){g.undo(),f.reportEventChange()};this.triggerEventDrop(a,g.dateDelta,h,d,e),f.reportEventChange()},triggerEventDrop:function(a,b,c,d,e){this.trigger("eventDrop",d[0],a,b,c,e,{})},reportExternalDrop:function(b,c,d,e,f){var g,h,i=b.eventProps;i&&(g=a.extend({},i,c),h=this.calendar.renderEvent(g,b.stick)[0]),this.triggerExternalDrop(h,c,d,e,f)},triggerExternalDrop:function(a,b,c,d,e){this.trigger("drop",c[0],b.start,d,e),a&&this.trigger("eventReceive",null,a)},renderDrag:function(a,b){},unrenderDrag:function(){},isEventResizableFromStart:function(a){return this.opt("eventResizableFromStart")&&this.isEventResizable(a)},isEventResizableFromEnd:function(a){return this.isEventResizable(a)},isEventResizable:function(a){var b=a.source||{};return X(a.durationEditable,b.durationEditable,this.opt("eventDurationEditable"),a.editable,b.editable,this.opt("editable"))},reportEventResize:function(a,b,c,d,e){var f=this.calendar,g=f.mutateEvent(a,b,c),h=function(){g.undo(),f.reportEventChange()};this.triggerEventResize(a,g.durationDelta,h,d,e),f.reportEventChange()},triggerEventResize:function(a,b,c,d,e){this.trigger("eventResize",d[0],a,b,c,e,{})},select:function(a,b){this.unselect(b),this.renderSelection(a),this.reportSelection(a,b)},renderSelection:function(a){},reportSelection:function(a,b){this.isSelected=!0,this.triggerSelect(a,b)},triggerSelect:function(a,b){this.trigger("select",null,a.start,a.end,b)},unselect:function(a){this.isSelected&&(this.isSelected=!1,this.destroySelection&&this.destroySelection(),this.unrenderSelection(),this.trigger("unselect",null,a))},unrenderSelection:function(){},documentMousedown:function(b){var c;this.isSelected&&this.opt("unselectAuto")&&v(b)&&(c=this.opt("unselectCancel"),c&&a(b.target).closest(c).length||this.unselect(b))},triggerDayClick:function(a,b,c){this.trigger("dayClick",b,a.start,c)},initHiddenDays:function(){var b,c=this.opt("hiddenDays")||[],d=[],e=0;for(this.opt("weekends")===!1&&c.push(0,6),b=0;7>b;b++)(d[b]=-1!==a.inArray(b,c))||e++;if(!e)throw"invalid hiddenDays";this.isHiddenDayHash=d},isHiddenDay:function(a){return b.isMoment(a)&&(a=a.day()),this.isHiddenDayHash[a]},skipHiddenDays:function(a,b,c){var d=a.clone();for(b=b||1;this.isHiddenDayHash[(d.day()+(c?b:0)+7)%7];)d.add(b,"days");return d},computeDayRange:function(a){var b,c=a.start.clone().stripTime(),d=a.end,e=null;return d&&(e=d.clone().stripTime(),b=+d.time(),b&&b>=this.nextDayThreshold&&e.add(1,"days")),(!d||c>=e)&&(e=c.clone().add(1,"days")),{start:c,end:e}},isMultiDayEvent:function(a){var b=this.computeDayRange(a);return b.end.diff(b.start,"days")>1}}),nb=Ma.Calendar=ra.extend({dirDefaults:null,langDefaults:null,overrides:null,options:null,viewSpecCache:null,view:null,header:null,loadingLevel:0,constructor:Ga,initialize:function(){},initOptions:function(a){var b,e,f,g;a=d(a),b=a.lang,e=ob[b],e||(b=nb.defaults.lang,e=ob[b]||{}),f=X(a.isRTL,e.isRTL,nb.defaults.isRTL),g=f?nb.rtlDefaults:{},this.dirDefaults=g,this.langDefaults=e,this.overrides=a,this.options=c([nb.defaults,g,e,a]),Ha(this.options),this.viewSpecCache={}},getViewSpec:function(a){var b=this.viewSpecCache;return b[a]||(b[a]=this.buildViewSpec(a))},getUnitViewSpec:function(b){var c,d,e;if(-1!=a.inArray(b,Ra))for(c=this.header.getViewsWithButtons(),a.each(Ma.views,function(a){c.push(a)}),d=0;d<c.length;d++)if(e=this.getViewSpec(c[d]),e&&e.singleUnit==b)return e},buildViewSpec:function(a){for(var d,e,f,g,h=this.overrides.views||{},i=[],j=[],k=[],l=a;l;)d=Na[l],e=h[l],l=null,"function"==typeof d&&(d={"class":d}),d&&(i.unshift(d),j.unshift(d.defaults||{}),f=f||d.duration,l=l||d.type),e&&(k.unshift(e),f=f||e.duration,l=l||e.type);return d=Q(i),d.type=a,d["class"]?(f&&(f=b.duration(f),f.valueOf()&&(d.duration=f,g=I(f),1===f.as(g)&&(d.singleUnit=g,k.unshift(h[g]||{})))),d.defaults=c(j),d.overrides=c(k),this.buildViewSpecOptions(d),this.buildViewSpecButtonText(d,a),d):!1},buildViewSpecOptions:function(a){a.options=c([nb.defaults,a.defaults,this.dirDefaults,this.langDefaults,this.overrides,a.overrides]),Ha(a.options)},buildViewSpecButtonText:function(a,b){function c(c){var d=c.buttonText||{};return d[b]||(a.singleUnit?d[a.singleUnit]:null)}a.buttonTextOverride=c(this.overrides)||a.overrides.buttonText,a.buttonTextDefault=c(this.langDefaults)||c(this.dirDefaults)||a.defaults.buttonText||c(nb.defaults)||(a.duration?this.humanizeDuration(a.duration):null)||b},instantiateView:function(a){var b=this.getViewSpec(a);return new b["class"](this,a,b.options,b.duration)},isValidViewType:function(a){return Boolean(this.getViewSpec(a))},pushLoading:function(){this.loadingLevel++||this.trigger("loading",null,!0,this.view)},popLoading:function(){--this.loadingLevel||this.trigger("loading",null,!1,this.view)},buildSelectRange:function(a,b){return a=this.moment(a),b=b?this.moment(b):a.hasTime()?a.clone().add(this.defaultTimedEventDuration):a.clone().add(this.defaultAllDayEventDuration),{start:a,end:b}}});nb.mixin(bb),nb.defaults={titleRangeSeparator:" — ",monthYearFormat:"MMMM YYYY",defaultTimedEventDuration:"02:00:00",defaultAllDayEventDuration:{days:1},forceEventDuration:!1,nextDayThreshold:"09:00:00",defaultView:"month",aspectRatio:1.35,header:{left:"title",center:"",right:"today prev,next"},weekends:!0,weekNumbers:!1,weekNumberTitle:"W",weekNumberCalculation:"local",scrollTime:"06:00:00",lazyFetching:!0,startParam:"start",endParam:"end",timezoneParam:"timezone",timezone:!1,isRTL:!1,buttonText:{prev:"prev",next:"next",prevYear:"prev year",nextYear:"next year",year:"year",today:"today",month:"month",week:"week",day:"day"},buttonIcons:{prev:"left-single-arrow",next:"right-single-arrow",prevYear:"left-double-arrow",nextYear:"right-double-arrow"},theme:!1,themeButtonIcons:{prev:"circle-triangle-w",next:"circle-triangle-e",prevYear:"seek-prev",nextYear:"seek-next"},dragOpacity:.75,dragRevertDuration:500,dragScroll:!0,unselectAuto:!0,dropAccept:"*",eventOrder:"title",eventLimit:!1,eventLimitText:"more",eventLimitClick:"popover",dayPopoverFormat:"LL",handleWindowResize:!0,windowResizeDelay:200},nb.englishDefaults={dayPopoverFormat:"dddd, MMMM D"},nb.rtlDefaults={header:{left:"next,prev today",center:"",right:"title"},buttonIcons:{prev:"right-single-arrow",next:"left-single-arrow",prevYear:"right-double-arrow",nextYear:"left-double-arrow"},themeButtonIcons:{prev:"circle-triangle-e",next:"circle-triangle-w",nextYear:"seek-prev",prevYear:"seek-next"}};var ob=Ma.langs={};Ma.datepickerLang=function(b,c,d){var e=ob[b]||(ob[b]={});e.isRTL=d.isRTL,e.weekNumberTitle=d.weekHeader,a.each(pb,function(a,b){e[a]=b(d)}),a.datepicker&&(a.datepicker.regional[c]=a.datepicker.regional[b]=d,a.datepicker.regional.en=a.datepicker.regional[""],a.datepicker.setDefaults(d))},Ma.lang=function(b,d){var e,f;e=ob[b]||(ob[b]={}),d&&(e=ob[b]=c([e,d])),f=Ia(b),a.each(qb,function(a,b){null==e[a]&&(e[a]=b(f,e))}),nb.defaults.lang=b};var pb={buttonText:function(a){return{prev:Z(a.prevText),next:Z(a.nextText),today:Z(a.currentText)}},monthYearFormat:function(a){return a.showMonthAfterYear?"YYYY["+a.yearSuffix+"] MMMM":"MMMM YYYY["+a.yearSuffix+"]"}},qb={dayOfMonthFormat:function(a,b){var c=a.longDateFormat("l");return c=c.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g,""),b.isRTL?c+=" ddd":c="ddd "+c,c},mediumTimeFormat:function(a){return a.longDateFormat("LT").replace(/\s*a$/i,"a")},smallTimeFormat:function(a){return a.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"a")},extraSmallTimeFormat:function(a){return a.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"t")},hourFormat:function(a){return a.longDateFormat("LT").replace(":mm","").replace(/(\Wmm)$/,"").replace(/\s*a$/i,"a")},noMeridiemTimeFormat:function(a){return a.longDateFormat("LT").replace(/\s*a$/i,"")}},rb={smallDayDateFormat:function(a){return a.isRTL?"D dd":"dd D"},weekFormat:function(a){return a.isRTL?"w[ "+a.weekNumberTitle+"]":"["+a.weekNumberTitle+" ]w"},smallWeekFormat:function(a){return a.isRTL?"w["+a.weekNumberTitle+"]":"["+a.weekNumberTitle+"]w"}};Ma.lang("en",nb.englishDefaults),Ma.sourceNormalizers=[],Ma.sourceFetchers=[];var sb={dataType:"json",cache:!1},tb=1;nb.prototype.getPeerEvents=function(a,b){var c,d,e=this.getEventCache(),f=[];for(c=0;c<e.length;c++)d=e[c],a&&a._id===d._id||f.push(d);return f};var ub=mb.extend({dayGrid:null,dayNumbersVisible:!1,weekNumbersVisible:!1,weekNumberWidth:null,headRowEl:null,initialize:function(){this.dayGrid=new kb(this),this.coordMap=this.dayGrid.coordMap},setRange:function(a){mb.prototype.setRange.call(this,a),this.dayGrid.breakOnWeeks=/year|month|week/.test(this.intervalUnit),this.dayGrid.setRange(a)},computeRange:function(a){var b=mb.prototype.computeRange.call(this,a);return/year|month/.test(b.intervalUnit)&&(b.start.startOf("week"),b.start=this.skipHiddenDays(b.start),b.end.weekday()&&(b.end.add(1,"week").startOf("week"),b.end=this.skipHiddenDays(b.end,-1,!0))),b},renderDates:function(){this.dayNumbersVisible=this.dayGrid.rowCnt>1,this.weekNumbersVisible=this.opt("weekNumbers"),this.dayGrid.numbersVisible=this.dayNumbersVisible||this.weekNumbersVisible,this.el.addClass("fc-basic-view").html(this.renderHtml()),this.headRowEl=this.el.find("thead .fc-row"),this.scrollerEl=this.el.find(".fc-day-grid-container"),this.dayGrid.coordMap.containerEl=this.scrollerEl,this.dayGrid.setElement(this.el.find(".fc-day-grid")),this.dayGrid.renderDates(this.hasRigidRows())},unrenderDates:function(){this.dayGrid.unrenderDates(),this.dayGrid.removeElement()},renderBusinessHours:function(){this.dayGrid.renderBusinessHours()},renderHtml:function(){return'<table><thead class="fc-head"><tr><td class="'+this.widgetHeaderClass+'">'+this.dayGrid.headHtml()+'</td></tr></thead><tbody class="fc-body"><tr><td class="'+this.widgetContentClass+'"><div class="fc-day-grid-container"><div class="fc-day-grid"/></div></td></tr></tbody></table>'},headIntroHtml:function(){return this.weekNumbersVisible?'<th class="fc-week-number '+this.widgetHeaderClass+'" '+this.weekNumberStyleAttr()+"><span>"+Y(this.opt("weekNumberTitle"))+"</span></th>":void 0},numberIntroHtml:function(a){return this.weekNumbersVisible?'<td class="fc-week-number" '+this.weekNumberStyleAttr()+"><span>"+this.dayGrid.getCell(a,0).start.format("w")+"</span></td>":void 0},dayIntroHtml:function(){return this.weekNumbersVisible?'<td class="fc-week-number '+this.widgetContentClass+'" '+this.weekNumberStyleAttr()+"></td>":void 0},introHtml:function(){return this.weekNumbersVisible?'<td class="fc-week-number" '+this.weekNumberStyleAttr()+"></td>":void 0},numberCellHtml:function(a){var b,c=a.start;return this.dayNumbersVisible?(b=this.dayGrid.getDayClasses(c),b.unshift("fc-day-number"),'<td class="'+b.join(" ")+'" data-date="'+c.format()+'">'+c.date()+"</td>"):"<td/>"},weekNumberStyleAttr:function(){return null!==this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},hasRigidRows:function(){var a=this.opt("eventLimit");return a&&"number"!=typeof a},updateWidth:function(){this.weekNumbersVisible&&(this.weekNumberWidth=k(this.el.find(".fc-week-number")))},setHeight:function(a,b){var c,d=this.opt("eventLimit");m(this.scrollerEl),f(this.headRowEl),this.dayGrid.removeSegPopover(),d&&"number"==typeof d&&this.dayGrid.limitRows(d),c=this.computeScrollerHeight(a),this.setGridHeight(c,b),d&&"number"!=typeof d&&this.dayGrid.limitRows(d),!b&&l(this.scrollerEl,c)&&(e(this.headRowEl,r(this.scrollerEl)),c=this.computeScrollerHeight(a),this.scrollerEl.height(c))},setGridHeight:function(a,b){b?j(this.dayGrid.rowEls):i(this.dayGrid.rowEls,a,!0)},renderEvents:function(a){this.dayGrid.renderEvents(a),this.updateHeight()},getEventSegs:function(){return this.dayGrid.getEventSegs()},unrenderEvents:function(){this.dayGrid.unrenderEvents()},renderDrag:function(a,b){return this.dayGrid.renderDrag(a,b)},unrenderDrag:function(){this.dayGrid.unrenderDrag()},renderSelection:function(a){this.dayGrid.renderSelection(a)},unrenderSelection:function(){this.dayGrid.unrenderSelection()}}),vb=ub.extend({computeRange:function(a){var b,c=ub.prototype.computeRange.call(this,a);return this.isFixedWeeks()&&(b=Math.ceil(c.end.diff(c.start,"weeks",!0)),c.end.add(6-b,"weeks")),c},setGridHeight:function(a,b){b=b||"variable"===this.opt("weekMode"),b&&(a*=this.rowCnt/6),i(this.dayGrid.rowEls,a,!b)},isFixedWeeks:function(){var a=this.opt("weekMode");return a?"fixed"===a:this.opt("fixedWeekCount")}});Na.basic={"class":ub},Na.basicDay={type:"basic",duration:{days:1}},Na.basicWeek={type:"basic",duration:{weeks:1}},Na.month={"class":vb,duration:{months:1},defaults:{fixedWeekCount:!0}};var wb=mb.extend({timeGrid:null,dayGrid:null,axisWidth:null,noScrollRowEls:null,bottomRuleEl:null,bottomRuleHeight:null,initialize:function(){this.timeGrid=new lb(this),this.opt("allDaySlot")?(this.dayGrid=new kb(this),this.coordMap=new eb([this.dayGrid.coordMap,this.timeGrid.coordMap])):this.coordMap=this.timeGrid.coordMap},setRange:function(a){mb.prototype.setRange.call(this,a),this.timeGrid.setRange(a),this.dayGrid&&this.dayGrid.setRange(a)},renderDates:function(){this.el.addClass("fc-agenda-view").html(this.renderHtml()),this.scrollerEl=this.el.find(".fc-time-grid-container"),this.timeGrid.coordMap.containerEl=this.scrollerEl,this.timeGrid.setElement(this.el.find(".fc-time-grid")),this.timeGrid.renderDates(),this.bottomRuleEl=a('<hr class="fc-divider '+this.widgetHeaderClass+'"/>').appendTo(this.timeGrid.el),this.dayGrid&&(this.dayGrid.setElement(this.el.find(".fc-day-grid")),this.dayGrid.renderDates(),this.dayGrid.bottomCoordPadding=this.dayGrid.el.next("hr").outerHeight()),this.noScrollRowEls=this.el.find(".fc-row:not(.fc-scroller *)")},unrenderDates:function(){this.timeGrid.unrenderDates(),this.timeGrid.removeElement(),this.dayGrid&&(this.dayGrid.unrenderDates(),this.dayGrid.removeElement())},renderBusinessHours:function(){this.timeGrid.renderBusinessHours(),this.dayGrid&&this.dayGrid.renderBusinessHours()},renderHtml:function(){return'<table><thead class="fc-head"><tr><td class="'+this.widgetHeaderClass+'">'+this.timeGrid.headHtml()+'</td></tr></thead><tbody class="fc-body"><tr><td class="'+this.widgetContentClass+'">'+(this.dayGrid?'<div class="fc-day-grid"/><hr class="fc-divider '+this.widgetHeaderClass+'"/>':"")+'<div class="fc-time-grid-container"><div class="fc-time-grid"/></div></td></tr></tbody></table>'},headIntroHtml:function(){var a,b;return this.opt("weekNumbers")?(a=this.timeGrid.getCell(0).start,b=a.format(this.opt("smallWeekFormat")),'<th class="fc-axis fc-week-number '+this.widgetHeaderClass+'" '+this.axisStyleAttr()+"><span>"+Y(b)+"</span></th>"):'<th class="fc-axis '+this.widgetHeaderClass+'" '+this.axisStyleAttr()+"></th>"},dayIntroHtml:function(){return'<td class="fc-axis '+this.widgetContentClass+'" '+this.axisStyleAttr()+"><span>"+(this.opt("allDayHtml")||Y(this.opt("allDayText")))+"</span></td>"},slotBgIntroHtml:function(){return'<td class="fc-axis '+this.widgetContentClass+'" '+this.axisStyleAttr()+"></td>";
9
  },introHtml:function(){return'<td class="fc-axis" '+this.axisStyleAttr()+"></td>"},axisStyleAttr:function(){return null!==this.axisWidth?'style="width:'+this.axisWidth+'px"':""},updateSize:function(a){this.timeGrid.updateSize(a),mb.prototype.updateSize.call(this,a)},updateWidth:function(){this.axisWidth=k(this.el.find(".fc-axis"))},setHeight:function(a,b){var c,d;null===this.bottomRuleHeight&&(this.bottomRuleHeight=this.bottomRuleEl.outerHeight()),this.bottomRuleEl.hide(),this.scrollerEl.css("overflow",""),m(this.scrollerEl),f(this.noScrollRowEls),this.dayGrid&&(this.dayGrid.removeSegPopover(),c=this.opt("eventLimit"),c&&"number"!=typeof c&&(c=xb),c&&this.dayGrid.limitRows(c)),b||(d=this.computeScrollerHeight(a),l(this.scrollerEl,d)?(e(this.noScrollRowEls,r(this.scrollerEl)),d=this.computeScrollerHeight(a),this.scrollerEl.height(d)):(this.scrollerEl.height(d).css("overflow","hidden"),this.bottomRuleEl.show()))},computeInitialScroll:function(){var a=b.duration(this.opt("scrollTime")),c=this.timeGrid.computeTimeTop(a);return c=Math.ceil(c),c&&c++,c},renderEvents:function(a){var b,c,d=[],e=[],f=[];for(c=0;c<a.length;c++)a[c].allDay?d.push(a[c]):e.push(a[c]);b=this.timeGrid.renderEvents(e),this.dayGrid&&(f=this.dayGrid.renderEvents(d)),this.updateHeight()},getEventSegs:function(){return this.timeGrid.getEventSegs().concat(this.dayGrid?this.dayGrid.getEventSegs():[])},unrenderEvents:function(){this.timeGrid.unrenderEvents(),this.dayGrid&&this.dayGrid.unrenderEvents()},renderDrag:function(a,b){return a.start.hasTime()?this.timeGrid.renderDrag(a,b):this.dayGrid?this.dayGrid.renderDrag(a,b):void 0},unrenderDrag:function(){this.timeGrid.unrenderDrag(),this.dayGrid&&this.dayGrid.unrenderDrag()},renderSelection:function(a){a.start.hasTime()||a.end.hasTime()?this.timeGrid.renderSelection(a):this.dayGrid&&this.dayGrid.renderSelection(a)},unrenderSelection:function(){this.timeGrid.unrenderSelection(),this.dayGrid&&this.dayGrid.unrenderSelection()}}),xb=5,yb=[{hours:1},{minutes:30},{minutes:15},{seconds:30},{seconds:15}];return Na.agenda={"class":wb,defaults:{allDaySlot:!0,allDayText:"all-day",slotDuration:"00:30:00",minTime:"00:00:00",maxTime:"24:00:00",slotEventOverlap:!0}},Na.agendaDay={type:"agenda",duration:{days:1}},Na.agendaWeek={type:"agenda",duration:{weeks:1}},Ma});
backend/modules/calendar/resources/js/ng-appointment_dialog.js CHANGED
@@ -1,729 +1,982 @@
1
- ;(function() {
2
-
3
- var module = angular.module('appointmentDialog', ['ui.date', 'customerDialog', 'paymentDetailsDialog']);
4
-
5
- /**
6
- * DataSource service.
7
- */
8
- module.factory('dataSource', function($q, $rootScope, $filter) {
9
- var ds = {
10
- loaded : false,
11
- data : {
12
- staff : [],
13
- customers : [],
14
- start_time : [],
15
- end_time : [],
16
- time_interval : 900,
17
- status : {
18
- items: [],
19
- default: null
20
- }
21
- },
22
- form : {
23
- id : null,
24
- staff : null,
25
- service : null,
26
- date : null,
27
- start_time : null,
28
- end_time : null,
29
- customers : [],
30
- notification : null
31
- },
32
- loadData : function() {
33
- var deferred = $q.defer();
34
- if (!ds.loaded) {
35
- jQuery.get(
36
- ajaxurl,
37
- { action : 'ab_get_data_for_appointment_form' },
38
- function(data) {
39
- ds.loaded = true;
40
- ds.data = data;
41
- // Add empty element to beginning of array for single-select customer form
42
- ds.data.customers.unshift({name: ''});
43
-
44
- if (data.staff.length) {
45
- ds.form.staff = data.staff[0];
46
- }
47
- ds.form.start_time = data.start_time[0];
48
- ds.form.end_time = data.end_time[1];
49
- deferred.resolve();
50
- },
51
- 'json'
52
- );
53
- } else {
54
- deferred.resolve();
55
- }
56
-
57
- return deferred.promise;
58
- },
59
- findStaff : function(id) {
60
- var result = null;
61
- jQuery.each(ds.data.staff, function(key, item) {
62
- if (item.id == id) {
63
- result = item;
64
- return false;
65
- }
66
- });
67
- return result;
68
- },
69
- findService : function(staff_id, id) {
70
- var result = null,
71
- staff = ds.findStaff(staff_id);
72
-
73
- if (staff !== null) {
74
- jQuery.each(staff.services, function(key, item) {
75
- if (item.id == id) {
76
- result = item;
77
- return false;
78
- }
79
- });
80
- }
81
- return result;
82
- },
83
- findTime : function(source, date) {
84
- var result = null,
85
- value_to_find = $filter('date')(date, 'HH:mm'),
86
- time = source == 'start' ? ds.data.start_time : ds.data.end_time;
87
-
88
- jQuery.each(time, function(key, item) {
89
- if (item.value >= value_to_find) {
90
- result = item;
91
- return false;
92
- }
93
- });
94
- return result;
95
- },
96
- findCustomer : function(id) {
97
- var result = null;
98
- jQuery.each(ds.data.customers, function(key, item) {
99
- if (item.id == id) {
100
- result = item;
101
- return false;
102
- }
103
- });
104
- return result;
105
- },
106
- resetCustomers : function() {
107
- ds.data.customers.forEach(function(customer) {
108
- customer.custom_fields = [];
109
- customer.extras = [];
110
- customer.status = ds.data.status.default;
111
- customer.number_of_persons = 1;
112
- customer.compound_token = null;
113
- customer.location_id = null;
114
- customer.payment_id = null;
115
- customer.payment_type = null;
116
- customer.payment_title = null;
117
- });
118
- },
119
- getDataForEndTime : function() {
120
- var result = [];
121
- if (ds.form.start_time) {
122
- var start_time = ds.form.start_time.value.split(':'),
123
- end = (24 + parseInt(start_time[0])) + ':' + start_time[1];
124
- jQuery.each(ds.data.end_time, function(key, item) {
125
- if (item.value > end) {
126
- return false;
127
- }
128
- if (item.value > ds.form.start_time.value) {
129
- result.push(item);
130
- }
131
- });
132
- }
133
- return result;
134
- },
135
- setEndTimeBasedOnService : function() {
136
- var i = jQuery.inArray(ds.form.start_time, ds.data.start_time),
137
- d = ds.form.service ? ds.form.service.duration : ds.data.time_interval;
138
- if (ds.form.service && ds.form.service.duration == 86400) {
139
- ds.form.start_time = ds.data.start_time[0];
140
- ds.form.end_time = ds.data.end_time[ 86400 / ds.data.time_interval ];
141
- } else {
142
- if (i !== -1) {
143
- for (; i < ds.data.end_time.length; ++i) {
144
- d -= ds.data.time_interval;
145
- if (d < 0) {
146
- break;
147
- }
148
- }
149
- ds.form.end_time = ds.data.end_time[i];
150
- }
151
- }
152
- },
153
- getStartAndEndDates : function() {
154
- var start_date = new Date(ds.form.date.getTime()),
155
- start_time = ds.form.start_time.value.split(':'),
156
- end_date = new Date(ds.form.date.getTime()),
157
- end_time = ds.form.end_time.value.split(':');
158
- start_date.setHours(start_time[0]);
159
- start_date.setMinutes(start_time[1]);
160
- end_date.setHours(end_time[0]);
161
- end_date.setMinutes(end_time[1]);
162
-
163
- return {
164
- start_date : $filter('date')(start_date, 'yyyy-MM-dd HH:mm:00'),
165
- end_date : $filter('date')(end_date, 'yyyy-MM-dd HH:mm:00')
166
- };
167
- },
168
- getTotalNumberOfPersons : function () {
169
- var result = 0;
170
- ds.form.customers.forEach(function (item) {
171
- result += parseInt(item.number_of_persons);
172
- });
173
-
174
- return result;
175
- },
176
- getTotalNotCancelledNumberOfPersons: function () {
177
- var result = 0;
178
- ds.form.customers.forEach(function (item) {
179
- if (item.status != 'cancelled') {
180
- result += parseInt(item.number_of_persons);
181
- }
182
- });
183
-
184
- return result;
185
- }
186
- };
187
-
188
- return ds;
189
- });
190
-
191
- /**
192
- * Controller for 'create/edit appointment' dialog form.
193
- */
194
- module.controller('appointmentDialogCtrl', function($scope, $element, dataSource) {
195
- // Set up initial data.
196
- $scope.$calendar = null;
197
- // Set up data source.
198
- $scope.dataSource = dataSource;
199
- $scope.form = dataSource.form; // shortcut
200
- // Error messages.
201
- $scope.errors = {};
202
- // Callback to be called after editing appointment.
203
- var callback = null;
204
-
205
- /**
206
- * Prepare the form for new event.
207
- *
208
- * @param int staff_id
209
- * @param moment start_date
210
- * @param function _callback
211
- */
212
- $scope.configureNewForm = function(staff_id, start_date, _callback) {
213
-
214
- jQuery.extend($scope.form, {
215
- id : null,
216
- staff : dataSource.findStaff(staff_id),
217
- service : null,
218
- date : start_date.clone().local().toDate(),
219
- start_time : dataSource.findTime('start', start_date.format('HH:mm')),
220
- end_time : null,
221
- customers : [],
222
- notification : 'no',
223
- internal_note : null
224
- });
225
- $scope.errors = {};
226
- dataSource.setEndTimeBasedOnService();
227
- callback = _callback;
228
-
229
- $scope.reInitChosen();
230
- $scope.prepareExtras();
231
- $scope.prepareCustomFields();
232
- $scope.dataSource.resetCustomers();
233
- };
234
-
235
- /**
236
- * Prepare the form for editing an event.
237
- */
238
- $scope.configureEditForm = function(appointment_id, _callback) {
239
- $scope.loading = true;
240
- jQuery.post(
241
- ajaxurl,
242
- {action: 'ab_get_data_for_appointment', id: appointment_id},
243
- function(response) {
244
- $scope.$apply(function($scope) {
245
- if (response.success) {
246
- var start_date = moment(response.data.start_date),
247
- end_date = moment(response.data.end_date);
248
- jQuery.extend($scope.form, {
249
- id : appointment_id,
250
- staff : $scope.dataSource.findStaff(response.data.staff_id),
251
- service : $scope.dataSource.findService(response.data.staff_id, response.data.service_id),
252
- date : start_date.clone().local().toDate(),
253
- start_time : $scope.dataSource.findTime('start', start_date.format('HH:mm')),
254
- end_time : start_date.format('YYYY-MM-DD') == end_date.format('YYYY-MM-DD')
255
- ? $scope.dataSource.findTime('end', end_date.format('HH:mm'))
256
- : $scope.dataSource.findTime('end', (24 + end_date.hour()) + end_date.format(':mm')),
257
- customers : [],
258
- notification : 'no',
259
- internal_note : response.data.internal_note
260
- });
261
-
262
- $scope.reInitChosen();
263
- $scope.prepareExtras();
264
- $scope.prepareCustomFields();
265
- $scope.dataSource.resetCustomers();
266
-
267
- var customers_ids = [];
268
- response.data.customers.forEach(function (item, i, arr) {
269
- var customer = $scope.dataSource.findCustomer(item.id),
270
- clone = {};
271
- if (customers_ids.indexOf(item.id) === -1) {
272
- customers_ids.push(item.id);
273
- clone = customer;
274
- } else {
275
- // For Error: ngRepeat:dupes & chosen directive
276
- angular.copy(customer, clone);
277
- }
278
- clone.ca_id = item.ca_id;
279
- clone.extras = item.extras;
280
- clone.status = item.status;
281
- clone.custom_fields = item.custom_fields;
282
- clone.number_of_persons = item.number_of_persons;
283
- clone.location_id = item.location_id;
284
- clone.payment_id = item.payment_id;
285
- clone.payment_type = item.payment_type;
286
- clone.payment_title = item.payment_title;
287
- clone.compound_token = item.compound_token;
288
- clone.compound_service = item.compound_service;
289
- $scope.form.customers.push(clone);
290
- });
291
- }
292
- $scope.loading = false;
293
- });
294
- },
295
- 'json'
296
- );
297
- $scope.errors = {};
298
- callback = _callback;
299
- };
300
-
301
- var checkTimeInterval = function() {
302
- var dates = $scope.dataSource.getStartAndEndDates();
303
- jQuery.get(
304
- ajaxurl,
305
- {
306
- action : 'ab_check_appointment_date_selection',
307
- start_date : dates.start_date,
308
- end_date : dates.end_date,
309
- appointment_id : $scope.form.id,
310
- staff_id : $scope.form.staff ? $scope.form.staff.id : null,
311
- service_id : $scope.form.service ? $scope.form.service.id : null
312
- },
313
- function (response) {
314
- $scope.$apply(function ($scope) {
315
- $scope.errors = response;
316
- });
317
- },
318
- 'json'
319
- );
320
- };
321
-
322
- $scope.onServiceChange = function() {
323
- $scope.dataSource.setEndTimeBasedOnService();
324
- $scope.reInitChosen();
325
- $scope.prepareExtras();
326
- $scope.prepareCustomFields();
327
- checkTimeInterval();
328
- };
329
-
330
- $scope.onStaffChange = function() {
331
- $scope.form.service = null;
332
- };
333
-
334
- $scope.onStartTimeChange = function() {
335
- $scope.dataSource.setEndTimeBasedOnService();
336
- checkTimeInterval();
337
- };
338
-
339
- $scope.onEndTimeChange = function() {
340
- checkTimeInterval();
341
- };
342
-
343
- $scope.processForm = function() {
344
- $scope.loading = true;
345
-
346
- var dates = $scope.dataSource.getStartAndEndDates(),
347
- customers = []
348
- ;
349
- $scope.form.customers.forEach(function (item, i, arr) {
350
- var customer_extras = {};
351
- if ($scope.form.service) {
352
- jQuery('#bookly-extras .service_' + $scope.form.service.id + ' input.extras-count').each(function () {
353
- var extra_id = jQuery(this).data('id');
354
- if (item.extras[extra_id] !== undefined) {
355
- customer_extras[extra_id] = item.extras[extra_id];
356
- }
357
- });
358
- }
359
- customers.push({
360
- id : item.id,
361
- ca_id : item.ca_id,
362
- custom_fields : item.custom_fields,
363
- extras : customer_extras,
364
- location_id : item.location_id,
365
- number_of_persons : item.number_of_persons,
366
- status : item.status
367
- });
368
- });
369
-
370
- jQuery.post(
371
- ajaxurl,
372
- {
373
- action : 'ab_save_appointment_form',
374
- id : $scope.form.id,
375
- staff_id : $scope.form.staff ? $scope.form.staff.id : null,
376
- service_id : $scope.form.service ? $scope.form.service.id : null,
377
- start_date : dates.start_date,
378
- end_date : dates.end_date,
379
- customers : JSON.stringify(customers),
380
- notification : $scope.form.notification,
381
- internal_note : $scope.form.internal_note
382
- },
383
- function (response) {
384
- $scope.$apply(function($scope) {
385
- if (response.success) {
386
- if (callback) {
387
- // Call callback.
388
- callback(response.data);
389
- }
390
- // Close the dialog.
391
- $element.children().modal('hide');
392
- } else {
393
- $scope.errors = response.errors;
394
- }
395
- $scope.loading = false;
396
- });
397
- },
398
- 'json'
399
- );
400
- };
401
-
402
- // On 'Cancel' button click.
403
- $scope.closeDialog = function () {
404
- // Close the dialog.
405
- $element.children().modal('hide');
406
- };
407
-
408
- $scope.reInitChosen = function () {
409
- jQuery('#chosen')
410
- .chosen('destroy')
411
- .chosen({
412
- search_contains : true,
413
- width : '100%',
414
- max_selected_options: dataSource.form.service ? dataSource.form.service.capacity : 0
415
- });
416
- };
417
-
418
- $scope.statusToString = function (status) {
419
- return dataSource.data.status.items[status];
420
- };
421
-
422
- /**************************************************************************************************************
423
- * New customer *
424
- **************************************************************************************************************/
425
-
426
- /**
427
- * Create new customer.
428
- * @param customer
429
- */
430
- $scope.createCustomer = function(customer) {
431
- // Add new customer to the list.
432
- var new_customer = {
433
- id : customer.id.toString(),
434
- name : customer.name,
435
- custom_fields : customer.custom_fields,
436
- extras : customer.extras,
437
- status : customer.status,
438
- number_of_persons : 1,
439
- compound_token : null,
440
- location_id : null,
441
- payment_id : null,
442
- payment_type : null,
443
- payment_title : null
444
- };
445
-
446
- if (customer.email || customer.phone){
447
- new_customer.name += ' (' + [customer.email, customer.phone].filter(Boolean).join(', ') + ')';
448
- }
449
-
450
- dataSource.data.customers.push(new_customer);
451
-
452
- // Make it selected.
453
- if (!dataSource.form.service || dataSource.form.customers.length < dataSource.form.service.capacity) {
454
- dataSource.form.customers.push(new_customer);
455
- }
456
-
457
- setTimeout(function() { jQuery('#chosen').trigger('chosen:updated'); }, 0);
458
- };
459
-
460
- $scope.removeCustomer = function(customer) {
461
- $scope.form.customers.splice($scope.form.customers.indexOf(customer), 1);
462
- };
463
-
464
- /**************************************************************************************************************
465
- * Customer Details *
466
- **************************************************************************************************************/
467
-
468
- $scope.editCustomerDetails = function(customer) {
469
- var $dialog = jQuery('#bookly-customer-details-dialog');
470
- $dialog.find('input.ab-custom-field:text, textarea.ab-custom-field, select.ab-custom-field').val('');
471
- $dialog.find('input.ab-custom-field:checkbox, input.ab-custom-field:radio').prop('checked', false);
472
- $dialog.find('#bookly-extras :checkbox').prop('checked', false);
473
-
474
- customer.custom_fields.forEach(function (field) {
475
- var $custom_field = $dialog.find('#ab--custom-fields > *[data-id="' + field.id + '"]');
476
- switch ($custom_field.data('type')) {
477
- case 'checkboxes':
478
- field.value.forEach(function (value) {
479
- $custom_field.find('.ab-custom-field').filter(function () {
480
- return this.value == value;
481
- }).prop('checked', true);
482
- });
483
- break;
484
- case 'radio-buttons':
485
- $custom_field.find('.ab-custom-field').filter(function () {
486
- return this.value == field.value;
487
- }).prop('checked', true);
488
- break;
489
- default:
490
- $custom_field.find('.ab-custom-field').val(field.value);
491
- break;
492
- }
493
- });
494
-
495
- angular.forEach(customer.extras, function (extra_count, extra_id) {
496
- $dialog.find('#bookly-extras .extras-count[data-id="' + extra_id + '"]').val(extra_count);
497
- });
498
-
499
- // Prepare select for number of persons.
500
- var $number_of_persons = $dialog.find('#ab-edit-number-of-persons');
501
-
502
- var max = $scope.form.service
503
- ? parseInt($scope.form.service.capacity) - $scope.dataSource.getTotalNotCancelledNumberOfPersons() + ( customer.status != 'cancelled' ? parseInt(customer.number_of_persons) : 0 )
504
- : 1;
505
- $number_of_persons.empty();
506
- for (var i = 1; i <= max; ++i) {
507
- $number_of_persons.append('<option value="' + i + '">' + i + '</option>');
508
- }
509
- if (customer.number_of_persons > max) {
510
- $number_of_persons.append('<option value="' + customer.number_of_persons + '">' + customer.number_of_persons + '</option>');
511
- }
512
- $number_of_persons.val(customer.number_of_persons);
513
- $dialog.find('#ab-appointment-status').val(customer.status);
514
- $dialog.find('#ab-appointment-location').val(customer.location_id);
515
- $dialog.find('#ab-deposit-due').val(customer.due);
516
- $scope.edit_customer = customer;
517
-
518
- $dialog.modal({show: true, backdrop: false})
519
- .on('hidden.bs.modal', function () {
520
- jQuery('body').addClass('modal-open');
521
- });
522
- };
523
-
524
- $scope.prepareExtras = function () {
525
- if ($scope.form.service) {
526
- jQuery('#bookly-extras > *').hide();
527
- var $service_extras = jQuery('#bookly-extras .service_' + $scope.form.service.id);
528
- if ($service_extras.length) {
529
- $service_extras.show();
530
- jQuery('#bookly-extras').show();
531
- } else {
532
- jQuery('#bookly-extras').hide();
533
- }
534
- } else {
535
- jQuery('#bookly-extras').hide();
536
- }
537
- };
538
-
539
- // Hide or unhide custom fields for current service
540
- $scope.prepareCustomFields = function () {
541
- if (BooklyL10nAppDialog.cf_per_service == 1) {
542
- var show = false;
543
- jQuery('#ab--custom-fields div[data-services]').each(function() {
544
- var $this = jQuery(this);
545
- if (dataSource.form.service !== null) {
546
- var services = $this.data('services');
547
- if (services && jQuery.inArray(dataSource.form.service.id, services) > -1) {
548
- $this.show();
549
- show = true;
550
- } else {
551
- $this.hide();
552
- }
553
- } else {
554
- $this.hide();
555
- }
556
- });
557
- if (show) {
558
- jQuery('#ab--custom-fields').show();
559
- } else {
560
- jQuery('#ab--custom-fields').hide();
561
- }
562
- }
563
- };
564
-
565
- $scope.saveCustomFields = function() {
566
- var result = [],
567
- extras = {},
568
- $fields = jQuery('#ab--custom-fields > *'),
569
- $number_of_persons = jQuery('#bookly-customer-details-dialog #ab-edit-number-of-persons')
570
- ;
571
-
572
- $fields.each(function () {
573
- var $this = jQuery(this),
574
- value;
575
- if ($this.is(':visible')) {
576
- switch ($this.data('type')) {
577
- case 'checkboxes':
578
- value = [];
579
- $this.find('.ab-custom-field:checked').each(function () {
580
- value.push(this.value);
581
- });
582
- break;
583
- case 'radio-buttons':
584
- value = $this.find('.ab-custom-field:checked').val();
585
- break;
586
- default:
587
- value = $this.find('.ab-custom-field').val();
588
- break;
589
- }
590
- result.push({id: $this.data('id'), value: value});
591
- }
592
- });
593
-
594
- if ($scope.form.service) {
595
- jQuery('#bookly-extras .service_' + $scope.form.service.id + ' input.extras-count').each(function () {
596
- if (this.value > 0) {
597
- extras[jQuery(this).data('id')] = this.value;
598
- }
599
- });
600
- }
601
-
602
- $scope.edit_customer.custom_fields = result;
603
- $scope.edit_customer.number_of_persons = $number_of_persons.val();
604
- $scope.edit_customer.location_id = jQuery('#bookly-customer-details-dialog #ab-appointment-location').val();
605
- $scope.edit_customer.extras = extras;
606
- $scope.edit_customer.status = jQuery('#bookly-customer-details-dialog #ab-appointment-status').val();
607
-
608
- jQuery('#bookly-customer-details-dialog').modal('hide');
609
- };
610
-
611
- /**************************************************************************************************************
612
- * Payment Details *
613
- **************************************************************************************************************/
614
-
615
- $scope.completePayment = function(payment_id, payment_title) {
616
- jQuery.each($scope.dataSource.data.customers, function(key, item) {
617
- if (item.payment_id == payment_id) {
618
- item.payment_type = 'full';
619
- item.payment_title = payment_title;
620
- }
621
- });
622
- };
623
-
624
- /**
625
- * Datepicker options.
626
- */
627
- $scope.dateOptions = {
628
- dateFormat : BooklyL10nAppDialog.dpDateFormat,
629
- dayNamesMin : BooklyL10nAppDialog.calendar.shortDays,
630
- monthNames : BooklyL10nAppDialog.calendar.longMonths,
631
- monthNamesShort : BooklyL10nAppDialog.calendar.shortMonths,
632
- firstDay : BooklyL10nAppDialog.startOfWeek
633
- };
634
- });
635
-
636
- /**
637
- * Directive for slide up/down.
638
- */
639
- module.directive('mySlideUp', function() {
640
- return function(scope, element, attrs) {
641
- element.hide();
642
- // watch the expression, and update the UI on change.
643
- scope.$watch(attrs.mySlideUp, function(value) {
644
- if (value) {
645
- element.delay(0).slideDown();
646
- } else {
647
- element.slideUp();
648
- }
649
- });
650
- };
651
- });
652
-
653
- /**
654
- * Directive for chosen.
655
- */
656
- module.directive('chosen',function($timeout) {
657
- var linker = function(scope,element,attrs) {
658
- scope.$watch(attrs['chosen'], function() {
659
- element.trigger('chosen:updated');
660
- });
661
-
662
- scope.$watchCollection(attrs['ngModel'], function() {
663
- $timeout(function() {
664
- element.trigger('chosen:updated');
665
- });
666
- });
667
-
668
- scope.reInitChosen();
669
- };
670
-
671
- return {
672
- restrict:'A',
673
- link: linker
674
- };
675
- });
676
-
677
- /**
678
- * Directive for Popover jQuery plugin.
679
- */
680
- module.directive('popover', function() {
681
- return function(scope, element, attrs) {
682
- element.popover({
683
- trigger : 'hover',
684
- content : function() { return this.getAttribute('popover'); },
685
- html : true,
686
- placement: 'top',
687
- template: '<div class="popover bookly-font-xs" style="width: 220px" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
688
- });
689
- };
690
- });
691
- })();
692
-
693
- /**
694
- * @param int appointment_id
695
- * @param int staff_id
696
- * @param moment start_date
697
- * @param function callback
698
- */
699
- var showAppointmentDialog = function (appointment_id, staff_id, start_date, callback) {
700
- var $dialog = jQuery('#bookly-appointment-dialog');
701
- var $scope = angular.element($dialog[0]).scope();
702
- $scope.$apply(function ($scope) {
703
- $scope.loading = true;
704
- $dialog
705
- .find('.modal-title')
706
- .text(appointment_id ? BooklyL10nAppDialog.title.edit_appointment : BooklyL10nAppDialog.title.new_appointment);
707
- // Populate data source.
708
- $scope.dataSource.loadData().then(function() {
709
- $scope.loading = false;
710
- if (appointment_id) {
711
- $scope.configureEditForm(appointment_id, callback);
712
- } else {
713
- $scope.configureNewForm(staff_id, start_date, callback);
714
- }
715
- });
716
- });
717
-
718
- // hide customer details dialog, if it remained opened.
719
- if (jQuery('#bookly-customer-details-dialog').hasClass('in')) {
720
- jQuery('#bookly-customer-details-dialog').modal('hide');
721
- }
722
-
723
- // hide new customer dialog, if it remained opened.
724
- if (jQuery('#bookly-customer-dialog').hasClass('in')) {
725
- jQuery('#bookly-customer-dialog').modal('hide');
726
- }
727
-
728
- $dialog.modal('show');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
729
  };
1
+ ;(function() {
2
+
3
+ var module = angular.module('appointmentDialog', ['ui.date', 'customerDialog', 'paymentDetailsDialog']);
4
+
5
+ /**
6
+ * DataSource service.
7
+ */
8
+ module.factory('dataSource', function($q, $rootScope, $filter) {
9
+ var ds = {
10
+ loaded : false,
11
+ data : {
12
+ staff : [],
13
+ customers : [],
14
+ start_time : [],
15
+ end_time : [],
16
+ time_interval : 900,
17
+ status : {
18
+ items: [],
19
+ default: null
20
+ }
21
+ },
22
+ form : {
23
+ screen : null,
24
+ id : null,
25
+ staff : null,
26
+ service : null,
27
+ date : null,
28
+ repeat : {
29
+ enabled : null,
30
+ repeat : null,
31
+ daily : { every : null },
32
+ weekly : { on : null },
33
+ biweekly : { on : null },
34
+ monthly : { on : null, day : null, weekday : null },
35
+ until : null
36
+ },
37
+ schedule : {
38
+ items : [],
39
+ edit : null,
40
+ page : null,
41
+ another_time : []
42
+ },
43
+ start_time : null,
44
+ end_time : null,
45
+ customers : [],
46
+ notification : null
47
+ },
48
+ loadData : function() {
49
+ var deferred = $q.defer();
50
+ if (!ds.loaded) {
51
+ jQuery.get(
52
+ ajaxurl,
53
+ { action : 'bookly_get_data_for_appointment_form' },
54
+ function(data) {
55
+ ds.loaded = true;
56
+ ds.data = data;
57
+ // Add empty element to beginning of array for single-select customer form
58
+ ds.data.customers.unshift({name: ''});
59
+
60
+ if (data.staff.length) {
61
+ ds.form.staff = data.staff[0];
62
+ }
63
+ ds.form.start_time = data.start_time[0];
64
+ ds.form.end_time = data.end_time[1];
65
+ deferred.resolve();
66
+ },
67
+ 'json'
68
+ );
69
+ } else {
70
+ deferred.resolve();
71
+ }
72
+
73
+ return deferred.promise;
74
+ },
75
+ findStaff : function(id) {
76
+ var result = null;
77
+ jQuery.each(ds.data.staff, function(key, item) {
78
+ if (item.id == id) {
79
+ result = item;
80
+ return false;
81
+ }
82
+ });
83
+ return result;
84
+ },
85
+ findService : function(staff_id, id) {
86
+ var result = null,
87
+ staff = ds.findStaff(staff_id);
88
+
89
+ if (staff !== null) {
90
+ jQuery.each(staff.services, function(key, item) {
91
+ if (item.id == id) {
92
+ result = item;
93
+ return false;
94
+ }
95
+ });
96
+ }
97
+ return result;
98
+ },
99
+ findTime : function(source, date) {
100
+ var result = null,
101
+ value_to_find = $filter('date')(date, 'HH:mm'),
102
+ time = source == 'start' ? ds.data.start_time : ds.data.end_time;
103
+
104
+ jQuery.each(time, function(key, item) {
105
+ if (item.value >= value_to_find) {
106
+ result = item;
107
+ return false;
108
+ }
109
+ });
110
+ return result;
111
+ },
112
+ findCustomer : function(id) {
113
+ var result = null;
114
+ jQuery.each(ds.data.customers, function(key, item) {
115
+ if (item.id == id) {
116
+ result = item;
117
+ return false;
118
+ }
119
+ });
120
+ return result;
121
+ },
122
+ resetCustomers : function() {
123
+ ds.data.customers.forEach(function(customer) {
124
+ customer.custom_fields = [];
125
+ customer.extras = [];
126
+ customer.status = ds.data.status.default;
127
+ customer.number_of_persons = 1;
128
+ customer.compound_token = null;
129
+ customer.location_id = null;
130
+ customer.payment_id = null;
131
+ customer.payment_type = null;
132
+ customer.payment_title = null;
133
+ });
134
+ },
135
+ getDataForEndTime : function() {
136
+ var result = [];
137
+ if (ds.form.start_time) {
138
+ var start_time = ds.form.start_time.value.split(':'),
139
+ end = (24 + parseInt(start_time[0])) + ':' + start_time[1];
140
+ jQuery.each(ds.data.end_time, function(key, item) {
141
+ if (item.value > end) {
142
+ return false;
143
+ }
144
+ if (item.value > ds.form.start_time.value) {
145
+ result.push(item);
146
+ }
147
+ });
148
+ }
149
+ return result;
150
+ },
151
+ setEndTimeBasedOnService : function() {
152
+ var i = jQuery.inArray(ds.form.start_time, ds.data.start_time),
153
+ d = ds.form.service ? ds.form.service.duration : ds.data.time_interval;
154
+ if (ds.form.service && ds.form.service.duration == 86400) {
155
+ ds.form.start_time = ds.data.start_time[0];
156
+ ds.form.end_time = ds.data.end_time[ 86400 / ds.data.time_interval ];
157
+ } else {
158
+ if (i !== -1) {
159
+ for (; i < ds.data.end_time.length; ++i) {
160
+ d -= ds.data.time_interval;
161
+ if (d < 0) {
162
+ break;
163
+ }
164
+ }
165
+ ds.form.end_time = ds.data.end_time[i];
166
+ }
167
+ }
168
+ },
169
+ getStartAndEndDates : function() {
170
+ var start_date = new Date(ds.form.date.getTime()),
171
+ start_time = ds.form.start_time.value.split(':'),
172
+ end_date = new Date(ds.form.date.getTime()),
173
+ end_time = ds.form.end_time.value.split(':');
174
+ start_date.setHours(start_time[0]);
175
+ start_date.setMinutes(start_time[1]);
176
+ end_date.setHours(end_time[0]);
177
+ end_date.setMinutes(end_time[1]);
178
+
179
+ return {
180
+ start_date : $filter('date')(start_date, 'yyyy-MM-dd HH:mm:00'),
181
+ end_date : $filter('date')(end_date, 'yyyy-MM-dd HH:mm:00')
182
+ };
183
+ },
184
+ getTotalNumberOfPersons : function () {
185
+ var result = 0;
186
+ ds.form.customers.forEach(function (item) {
187
+ result += parseInt(item.number_of_persons);
188
+ });
189
+
190
+ return result;
191
+ },
192
+ getTotalNumberOfNotCancelledPersons: function () {
193
+ var result = 0;
194
+ ds.form.customers.forEach(function (item) {
195
+ if (item.status != 'cancelled') {
196
+ result += parseInt(item.number_of_persons);
197
+ }
198
+ });
199
+
200
+ return result;
201
+ },
202
+ getTotalNumberOfCancelledPersons: function () {
203
+ var result = 0;
204
+ ds.form.customers.forEach(function (item) {
205
+ if (item.status == 'cancelled') {
206
+ result += parseInt(item.number_of_persons);
207
+ }
208
+ });
209
+
210
+ return result;
211
+ }
212
+ };
213
+
214
+ return ds;
215
+ });
216
+
217
+ /**
218
+ * Controller for 'create/edit appointment' dialog form.
219
+ */
220
+ module.controller('appointmentDialogCtrl', function($scope, $element, dataSource, $filter) {
221
+ // Set up initial data.
222
+ $scope.$calendar = null;
223
+ // Set up data source.
224
+ $scope.dataSource = dataSource;
225
+ $scope.form = dataSource.form; // shortcut
226
+ // Error messages.
227
+ $scope.errors = {};
228
+ // Callback to be called after editing appointment.
229
+ var callback = null;
230
+
231
+ /**
232
+ * Prepare the form for new event.
233
+ *
234
+ * @param int staff_id
235
+ * @param moment start_date
236
+ * @param function _callback
237
+ */
238
+ $scope.configureNewForm = function(staff_id, start_date, _callback) {
239
+ var weekday = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][start_date.format('d')];
240
+ jQuery.extend($scope.form, {
241
+ screen : 'main',
242
+ id : null,
243
+ staff : dataSource.findStaff(staff_id),
244
+ service : null,
245
+ date : start_date.clone().local().toDate(),
246
+ start_time : dataSource.findTime('start', start_date.format('HH:mm')),
247
+ end_time : null,
248
+ repeat : {
249
+ enabled : 0,
250
+ repeat : 'daily',
251
+ daily : { every: 1 },
252
+ weekly : { on : [weekday] },
253
+ biweekly : { on : [weekday] },
254
+ monthly : { on : 'day', day : start_date.format('D'), weekday : weekday },
255
+ until : start_date.clone().add(1, 'month').format('YYYY-MM-DD')
256
+ },
257
+ schedule : {
258
+ items : [],
259
+ edit : 0,
260
+ page : 0,
261
+ another_time : []
262
+ },
263
+ customers : [],
264
+ notification : 'no',
265
+ internal_note : null
266
+ });
267
+ $scope.errors = {};
268
+ dataSource.setEndTimeBasedOnService();
269
+ callback = _callback;
270
+
271
+ $scope.reInitChosen();
272
+ $scope.prepareExtras();
273
+ $scope.prepareCustomFields();
274
+ $scope.dataSource.resetCustomers();
275
+ };
276
+
277
+ /**
278
+ * Prepare the form for editing an event.
279
+ */
280
+ $scope.configureEditForm = function(appointment_id, _callback) {
281
+ $scope.loading = true;
282
+ jQuery.post(
283
+ ajaxurl,
284
+ {action: 'bookly_get_data_for_appointment', id: appointment_id},
285
+ function(response) {
286
+ $scope.$apply(function($scope) {
287
+ if (response.success) {
288
+ var start_date = moment(response.data.start_date),
289
+ end_date = moment(response.data.end_date);
290
+ jQuery.extend($scope.form, {
291
+ screen : 'main',
292
+ id : appointment_id,
293
+ staff : $scope.dataSource.findStaff(response.data.staff_id),
294
+ service : $scope.dataSource.findService(response.data.staff_id, response.data.service_id),
295
+ date : start_date.clone().local().toDate(),
296
+ start_time : $scope.dataSource.findTime('start', start_date.format('HH:mm')),
297
+ end_time : start_date.format('YYYY-MM-DD') == end_date.format('YYYY-MM-DD')
298
+ ? $scope.dataSource.findTime('end', end_date.format('HH:mm'))
299
+ : $scope.dataSource.findTime('end', (24 + end_date.hour()) + end_date.format(':mm')),
300
+ repeat : {
301
+ enabled : 0,
302
+ repeat : 'daily',
303
+ daily : { every: 1 },
304
+ weekly : { on : [] },
305
+ biweekly : { on : [] },
306
+ monthly : { on : 'day', day : '1', weekday : 'mon' },
307
+ until : start_date.clone().add(1, 'month').format('YYYY-MM-DD')
308
+ },
309
+ schedule : {
310
+ items : [],
311
+ edit : 0,
312
+ page : 0,
313
+ another_time : []
314
+ },
315
+ customers : [],
316
+ notification : 'no',
317
+ internal_note : response.data.internal_note
318
+ });
319
+
320
+ $scope.reInitChosen();
321
+ $scope.prepareExtras();
322
+ $scope.prepareCustomFields();
323
+ $scope.dataSource.resetCustomers();
324
+
325
+ var customers_ids = [];
326
+ response.data.customers.forEach(function (item, i, arr) {
327
+ var customer = $scope.dataSource.findCustomer(item.id),
328
+ clone = {};
329
+ if (customers_ids.indexOf(item.id) === -1) {
330
+ customers_ids.push(item.id);
331
+ clone = customer;
332
+ } else {
333
+ // For Error: ngRepeat:dupes & chosen directive
334
+ angular.copy(customer, clone);
335
+ }
336
+ clone.ca_id = item.ca_id;
337
+ clone.extras = item.extras;
338
+ clone.status = item.status;
339
+ clone.custom_fields = item.custom_fields;
340
+ clone.number_of_persons = item.number_of_persons;
341
+ clone.location_id = item.location_id;
342
+ clone.payment_id = item.payment_id;
343
+ clone.payment_type = item.payment_type;
344
+ clone.payment_title = item.payment_title;
345
+ clone.compound_token = item.compound_token;
346
+ clone.compound_service = item.compound_service;
347
+ $scope.form.customers.push(clone);
348
+ });
349
+ }
350
+ $scope.loading = false;
351
+ });
352
+ },
353
+ 'json'
354
+ );
355
+ $scope.errors = {};
356
+ callback = _callback;
357
+ };
358
+
359
+ var checkTimeInterval = function() {
360
+ var dates = $scope.dataSource.getStartAndEndDates();
361
+ jQuery.post(
362
+ ajaxurl,
363
+ {
364
+ action : 'bookly_check_appointment_date_selection',
365
+ start_date : dates.start_date,
366
+ end_date : dates.end_date,
367
+ appointment_id : $scope.form.id,
368
+ staff_id : $scope.form.staff ? $scope.form.staff.id : null,
369
+ service_id : $scope.form.service ? $scope.form.service.id : null
370
+ },
371
+ function (response) {
372
+ $scope.$apply(function ($scope) {
373
+ $scope.errors = response;
374
+ });
375
+ },
376
+ 'json'
377
+ );
378
+ };
379
+
380
+ $scope.onServiceChange = function() {
381
+ $scope.dataSource.setEndTimeBasedOnService();
382
+ $scope.reInitChosen();
383
+ $scope.prepareExtras();
384
+ $scope.prepareCustomFields();
385
+ checkTimeInterval();
386
+ };
387
+
388
+ $scope.onStaffChange = function() {
389
+ $scope.form.service = null;
390
+ };
391
+
392
+ $scope.onStartTimeChange = function() {
393
+ $scope.dataSource.setEndTimeBasedOnService();
394
+ checkTimeInterval();
395
+ };
396
+
397
+ $scope.onEndTimeChange = function() {
398
+ checkTimeInterval();
399
+ };
400
+
401
+ $scope.processForm = function() {
402
+ $scope.loading = true;
403
+
404
+ var dates = $scope.dataSource.getStartAndEndDates(),
405
+ schedule = [],
406
+ customers = []
407
+ ;
408
+
409
+ angular.forEach($scope.form.schedule.items, function (item) {
410
+ if (!item.deleted) {
411
+ schedule.push(item.slots);
412
+ }
413
+ });
414
+
415
+ $scope.form.customers.forEach(function (item, i, arr) {
416
+ var customer_extras = {};
417
+ if ($scope.form.service) {
418
+ jQuery('#bookly-extras .service_' + $scope.form.service.id + ' input.extras-count').each(function () {
419
+ var extra_id = jQuery(this).data('id');
420
+ if (item.extras[extra_id] !== undefined) {
421
+ customer_extras[extra_id] = item.extras[extra_id];
422
+ }
423
+ });
424
+ }
425
+ customers.push({
426
+ id : item.id,
427
+ ca_id : item.ca_id,
428
+ custom_fields : item.custom_fields,
429
+ extras : customer_extras,
430
+ location_id : item.location_id,
431
+ number_of_persons : item.number_of_persons,
432
+ status : item.status
433
+ });
434
+ });
435
+
436
+ jQuery.post(
437
+ ajaxurl,
438
+ {
439
+ action : 'bookly_save_appointment_form',
440
+ id : $scope.form.id,
441
+ staff_id : $scope.form.staff ? $scope.form.staff.id : null,
442
+ service_id : $scope.form.service ? $scope.form.service.id : null,
443
+ start_date : dates.start_date,
444
+ end_date : dates.end_date,
445
+ repeat : JSON.stringify($scope.form.repeat),
446
+ schedule : schedule,
447
+ customers : JSON.stringify(customers),
448
+ notification : $scope.form.notification,
449
+ internal_note : $scope.form.internal_note
450
+ },
451
+ function (response) {
452
+ $scope.$apply(function($scope) {
453
+ if (response.success) {
454
+ if (callback) {
455
+ // Call callback.
456
+ callback(response.data);
457
+ }
458
+ // Close the dialog.
459
+ $element.children().modal('hide');
460
+ } else {
461
+ $scope.errors = response.errors;
462
+ }
463
+ $scope.loading = false;
464
+ });
465
+ },
466
+ 'json'
467
+ );
468
+ };
469
+
470
+ // On 'Cancel' button click.
471
+ $scope.closeDialog = function () {
472
+ // Close the dialog.
473
+ $element.children().modal('hide');
474
+ };
475
+
476
+ $scope.reInitChosen = function () {
477
+ jQuery('#bookly-chosen')
478
+ .chosen('destroy')
479
+ .chosen({
480
+ search_contains : true,
481
+ width : '100%',
482
+ max_selected_options: dataSource.form.service ? dataSource.form.service.capacity + dataSource.getTotalNumberOfCancelledPersons() : 0
483
+ });
484
+ };
485
+
486
+ $scope.statusToString = function (status) {
487
+ return dataSource.data.status.items[status];
488
+ };
489
+
490
+ /**************************************************************************************************************
491
+ * New customer *
492
+ **************************************************************************************************************/
493
+
494
+ /**
495
+ * Create new customer.
496
+ * @param customer
497
+ */
498
+ $scope.createCustomer = function(customer) {
499
+ // Add new customer to the list.
500
+ var new_customer = {
501
+ id : customer.id.toString(),
502
+ name : customer.name,
503
+ custom_fields : customer.custom_fields,
504
+ extras : customer.extras,
505
+ status : customer.status,
506
+ number_of_persons : 1,
507
+ compound_token : null,
508
+ location_id : null,
509
+ payment_id : null,
510
+ payment_type : null,
511
+ payment_title : null
512
+ };
513
+
514
+ if (customer.email || customer.phone){
515
+ new_customer.name += ' (' + [customer.email, customer.phone].filter(Boolean).join(', ') + ')';
516
+ }
517
+
518
+ dataSource.data.customers.push(new_customer);
519
+
520
+ // Make it selected.
521
+ if (!dataSource.form.service || dataSource.form.customers.length < dataSource.form.service.capacity) {
522
+ dataSource.form.customers.push(new_customer);
523
+ }
524
+
525
+ setTimeout(function() { jQuery('#bookly-chosen').trigger('chosen:updated'); }, 0);
526
+ };
527
+
528
+ $scope.removeCustomer = function(customer) {
529
+ $scope.form.customers.splice($scope.form.customers.indexOf(customer), 1);
530
+ };
531
+
532
+ /**************************************************************************************************************
533
+ * Customer Details *
534
+ **************************************************************************************************************/
535
+
536
+ $scope.editCustomerDetails = function(customer) {
537
+ var $dialog = jQuery('#bookly-customer-details-dialog');
538
+ $dialog.find('input.ab-custom-field:text, textarea.ab-custom-field, select.ab-custom-field').val('');
539
+ $dialog.find('input.ab-custom-field:checkbox, input.ab-custom-field:radio').prop('checked', false);
540
+ $dialog.find('#bookly-extras :checkbox').prop('checked', false);
541
+
542
+ customer.custom_fields.forEach(function (field) {
543
+ var $custom_field = $dialog.find('#ab--custom-fields > *[data-id="' + field.id + '"]');
544
+ switch ($custom_field.data('type')) {
545
+ case 'checkboxes':
546
+ field.value.forEach(function (value) {
547
+ $custom_field.find('.ab-custom-field').filter(function () {
548
+ return this.value == value;
549
+ }).prop('checked', true);
550
+ });
551
+ break;
552
+ case 'radio-buttons':
553
+ $custom_field.find('.ab-custom-field').filter(function () {
554
+ return this.value == field.value;
555
+ }).prop('checked', true);
556
+ break;
557
+ default:
558
+ $custom_field.find('.ab-custom-field').val(field.value);
559
+ break;
560
+ }
561
+ });
562
+
563
+ $dialog.find('#bookly-extras .extras-count').val(0);
564
+ angular.forEach(customer.extras, function (extra_count, extra_id) {
565
+ $dialog.find('#bookly-extras .extras-count[data-id="' + extra_id + '"]').val(extra_count);
566
+ });
567
+
568
+ // Prepare select for number of persons.
569
+ var $number_of_persons = $dialog.find('#ab-edit-number-of-persons');
570
+
571
+ var max = $scope.form.service
572
+ ? parseInt($scope.form.service.capacity) - $scope.dataSource.getTotalNumberOfNotCancelledPersons() + ( customer.status != 'cancelled' ? parseInt(customer.number_of_persons) : 0 )
573
+ : 1;
574
+ $number_of_persons.empty();
575
+ for (var i = 1; i <= max; ++i) {
576
+ $number_of_persons.append('<option value="' + i + '">' + i + '</option>');
577
+ }
578
+ if (customer.number_of_persons > max) {
579
+ $number_of_persons.append('<option value="' + customer.number_of_persons + '">' + customer.number_of_persons + '</option>');
580
+ }
581
+ $number_of_persons.val(customer.number_of_persons);
582
+ $dialog.find('#ab-appointment-status').val(customer.status);
583
+ $dialog.find('#ab-appointment-location').val(customer.location_id);
584
+ $dialog.find('#ab-deposit-due').val(customer.due);
585
+ $scope.edit_customer = customer;
586
+
587
+ $dialog.modal({show: true, backdrop: false})
588
+ .on('hidden.bs.modal', function () {
589
+ jQuery('body').addClass('modal-open');
590
+ });
591
+ };
592
+
593
+ $scope.prepareExtras = function () {
594
+ if ($scope.form.service) {
595
+ jQuery('#bookly-extras > *').hide();
596
+ var $service_extras = jQuery('#bookly-extras .service_' + $scope.form.service.id);
597
+ if ($service_extras.length) {
598
+ $service_extras.show();
599
+ jQuery('#bookly-extras').show();
600
+ } else {
601
+ jQuery('#bookly-extras').hide();
602
+ }
603
+ } else {
604
+ jQuery('#bookly-extras').hide();
605
+ }
606
+ };
607
+
608
+ // Hide or unhide custom fields for current service
609
+ $scope.prepareCustomFields = function () {
610
+ if (BooklyL10nAppDialog.cf_per_service == 1) {
611
+ var show = false;
612
+ jQuery('#ab--custom-fields div[data-services]').each(function() {
613
+ var $this = jQuery(this);
614
+ if (dataSource.form.service !== null) {
615
+ var services = $this.data('services');
616
+ if (services && jQuery.inArray(dataSource.form.service.id, services) > -1) {
617
+ $this.show();
618
+ show = true;
619
+ } else {
620
+ $this.hide();
621
+ }
622
+ } else {
623
+ $this.hide();
624
+ }
625
+ });
626
+ if (show) {
627
+ jQuery('#ab--custom-fields').show();
628
+ } else {
629
+ jQuery('#ab--custom-fields').hide();
630
+ }
631
+ }
632
+ };
633
+
634
+ $scope.saveCustomFields = function() {
635
+ var result = [],
636
+ extras = {},
637
+ $fields = jQuery('#ab--custom-fields > *'),
638
+ $number_of_persons = jQuery('#bookly-customer-details-dialog #ab-edit-number-of-persons')
639
+ ;
640
+
641
+ $fields.each(function () {
642
+ var $this = jQuery(this),
643
+ value;
644
+ if ($this.is(':visible')) {
645
+ switch ($this.data('type')) {
646
+ case 'checkboxes':
647
+ value = [];
648
+ $this.find('.ab-custom-field:checked').each(function () {
649
+ value.push(this.value);
650
+ });
651
+ break;
652
+ case 'radio-buttons':
653
+ value = $this.find('.ab-custom-field:checked').val();
654
+ break;
655
+ default:
656
+ value = $this.find('.ab-custom-field').val();
657
+ break;
658
+ }
659
+ result.push({id: $this.data('id'), value: value});
660
+ }
661
+ });
662
+
663
+ if ($scope.form.service) {
664
+ jQuery('#bookly-extras .service_' + $scope.form.service.id + ' input.extras-count').each(function () {
665
+ if (this.value > 0) {
666
+ extras[jQuery(this).data('id')] = this.value;
667
+ }
668
+ });
669
+ }
670
+
671
+ $scope.edit_customer.custom_fields = result;
672
+ $scope.edit_customer.number_of_persons = $number_of_persons.val();
673
+ $scope.edit_customer.location_id = jQuery('#bookly-customer-details-dialog #ab-appointment-location').val();
674
+ $scope.edit_customer.extras = extras;
675
+ $scope.edit_customer.status = jQuery('#bookly-customer-details-dialog #ab-appointment-status').val();
676
+
677
+ jQuery('#bookly-customer-details-dialog').modal('hide');
678
+ };
679
+
680
+ /**************************************************************************************************************
681
+ * Payment Details *
682
+ **************************************************************************************************************/
683
+
684
+ $scope.completePayment = function(payment_id, payment_title) {
685
+ jQuery.each($scope.dataSource.data.customers, function(key, item) {
686
+ if (item.payment_id == payment_id) {
687
+ item.payment_type = 'full';
688
+ item.payment_title = payment_title;
689
+ }
690
+ });
691
+ };
692
+
693
+ /**************************************************************************************************************
694
+ * Schedule of Recurring Appointments *
695
+ **************************************************************************************************************/
696
+
697
+ $scope.schSchedule = function ($event) {
698
+ var extras = [];
699
+ $scope.form.customers.forEach(function (item, i, arr) {
700
+ extras.push(item.extras);
701
+ });
702
+
703
+ if (
704
+ ($scope.form.repeat.repeat == 'weekly' || $scope.form.repeat.repeat == 'biweekly') &&
705
+ $scope.form.repeat[$scope.form.repeat.repeat].on.length == 0
706
+ ) {
707
+ $scope.errors.repeat_weekdays_empty = true;
708
+ } else {
709
+ delete $scope.errors.repeat_weekdays_empty;
710
+ var ladda = Ladda.create($event.currentTarget);
711
+ ladda.start();
712
+ var dates = $scope.dataSource.getStartAndEndDates();
713
+ jQuery.post(
714
+ ajaxurl,
715
+ {
716
+ action : 'bookly_recurring_appointments_get_schedule',
717
+ staff_id : $scope.form.staff.id,
718
+ service_id : $scope.form.service.id,
719
+ datetime : dates.start_date,
720
+ until : $scope.form.repeat.until,
721
+ repeat : $scope.form.repeat.repeat,
722
+ params : $scope.form.repeat[$scope.form.repeat.repeat],
723
+ extras : extras
724
+ },
725
+ function (response) {
726
+ $scope.$apply(function($scope) {
727
+ $scope.form.schedule.items = response.data;
728
+ $scope.form.schedule.page = 0;
729
+ $scope.form.schedule.another_time = [];
730
+ angular.forEach($scope.form.schedule.items, function (item) {
731
+ if (item.another_time) {
732
+ var page = parseInt( ( item.index - 1 ) / 10 ) + 1;
733
+ if ($scope.form.schedule.another_time.indexOf(page) < 0) {
734
+ $scope.form.schedule.another_time.push(page);
735
+ }
736
+ }
737
+ });
738
+ $scope.form.screen = 'schedule';
739
+ ladda.stop();
740
+ });
741
+ },
742
+ 'json'
743
+ );
744
+ }
745
+ };
746
+ $scope.schFormatDate = function(date) {
747
+ var m = moment(date),
748
+ weekday = m.format('d'),
749
+ month = m.format('M'),
750
+ day = m.format('DD');
751
+
752
+ return BooklyL10nAppDialog.calendar.shortDays[weekday] + ', ' + BooklyL10nAppDialog.calendar.shortMonths[month-1] + ' ' + day;
753
+ };
754
+ $scope.schFormatTime = function(slots, options) {
755
+ for (var i = 0; i < options.length; ++ i) {
756
+ if (slots == options[i].value) {
757
+ return options[i].title;
758
+ }
759
+ }
760
+ };
761
+ $scope.schFirstPage = function() {
762
+ return $scope.form.schedule.page == 0;
763
+ };
764
+ $scope.schLastPage = function() {
765
+ var lastPageNum = Math.ceil($scope.form.schedule.items.length / 10 - 1);
766
+ return $scope.form.schedule.page == lastPageNum;
767
+ };
768
+ $scope.schNumberOfPages = function() {
769
+ return Math.ceil($scope.form.schedule.items.length / 10);
770
+ };
771
+ $scope.schStartingItem = function() {
772
+ return $scope.form.schedule.page * 10;
773
+ };
774
+ $scope.schPageBack = function() {
775
+ $scope.form.schedule.page = $scope.form.schedule.page - 1;
776
+ };
777
+ $scope.schPageForward = function() {
778
+ $scope.form.schedule.page = $scope.form.schedule.page + 1;
779
+ };
780
+ $scope.schOnWeekdayClick = function (weekday) {
781
+ var idx = $scope.form.repeat.weekly.on.indexOf(weekday);
782
+
783
+ // is currently selected
784
+ if (idx > -1) {
785
+ $scope.form.repeat.weekly.on.splice(idx, 1);
786
+ }
787
+ // is newly selected
788
+ else {
789
+ $scope.form.repeat.weekly.on.push(weekday);
790
+ }
791
+ // copy weekly to biweekly
792
+ $scope.form.repeat.biweekly.on = $scope.form.repeat.weekly.on.slice();
793
+ };
794
+ $scope.schOnDateChange = function(item) {
795
+ var extras = [];
796
+ $scope.form.customers.forEach(function (item, i, arr) {
797
+ extras.push(item.extras);
798
+ });
799
+
800
+ var exclude = [];
801
+ angular.forEach($scope.form.schedule.items, function (_item) {
802
+ if (item.slots != _item.slots && !_item.deleted) {
803
+ exclude.push(_item.slots);
804
+ }
805
+ });
806
+ jQuery.post(
807
+ ajaxurl,
808
+ {
809
+ action : 'bookly_recurring_appointments_get_schedule',
810
+ staff_id : $scope.form.staff.id,
811
+ service_id : $scope.form.service.id,
812
+ datetime : item.date + ' 00:00',
813
+ until : item.date,
814
+ repeat : 'daily',
815
+ params : {every: 1},
816
+ with_options : 1,
817
+ exclude : exclude,
818
+ extras : extras
819
+ },
820
+ function (response) {
821
+ $scope.$apply(function($scope) {
822
+ if (response.data.length) {
823
+ item.options = response.data[0].options;
824
+ var found = false;
825
+ jQuery.each(item.options, function (key, option) {
826
+ if ( option.value == item.slots ) {
827
+ found = true;
828
+ return false;
829
+ }
830
+ });
831
+ if (!found) {
832
+ item.slots = item.options[0].value;
833
+ }
834
+ } else {
835
+ item.options = [];
836
+ }
837
+ });
838
+ },
839
+ 'json'
840
+ );
841
+ };
842
+ $scope.schIsScheduleEmpty = function () {
843
+ return $scope.form.schedule.items.every(function(item) {
844
+ return item.deleted;
845
+ });
846
+ };
847
+ $scope.schDateOptions = {
848
+ dateFormat : 'D, M dd, yy',
849
+ dayNamesMin : BooklyL10nAppDialog.calendar.shortDays,
850
+ dayNamesShort : BooklyL10nAppDialog.calendar.shortDays,
851
+ monthNames : BooklyL10nAppDialog.calendar.longMonths,
852
+ monthNamesShort : BooklyL10nAppDialog.calendar.shortMonths,
853
+ firstDay : BooklyL10nAppDialog.startOfWeek
854
+ };
855
+
856
+ /**
857
+ * Datepicker options.
858
+ */
859
+ $scope.dateOptions = {
860
+ dateFormat : BooklyL10nAppDialog.dpDateFormat,
861
+ dayNamesMin : BooklyL10nAppDialog.calendar.shortDays,
862
+ monthNames : BooklyL10nAppDialog.calendar.longMonths,
863
+ monthNamesShort : BooklyL10nAppDialog.calendar.shortMonths,
864
+ firstDay : BooklyL10nAppDialog.startOfWeek
865
+ };
866
+ });
867
+
868
+ /**
869
+ * Directive for slide up/down.
870
+ */
871
+ module.directive('mySlideUp', function() {
872
+ return function(scope, element, attrs) {
873
+ element.hide();
874
+ // watch the expression, and update the UI on change.
875
+ scope.$watch(attrs.mySlideUp, function(value) {
876
+ if (value) {
877
+ element.delay(0).slideDown();
878
+ } else {
879
+ element.slideUp();
880
+ }
881
+ });
882
+ };
883
+ });
884
+
885
+ /**
886
+ * Directive for chosen.
887
+ */
888
+ module.directive('chosen',function($timeout) {
889
+ var linker = function(scope,element,attrs) {
890
+ scope.$watch(attrs['chosen'], function() {
891
+ element.trigger('chosen:updated');
892
+ });
893
+
894
+ scope.$watchCollection(attrs['ngModel'], function() {
895
+ $timeout(function() {
896
+ element.trigger('chosen:updated');
897
+ });
898
+ });
899
+
900
+ scope.reInitChosen();
901
+ };
902
+
903
+ return {
904
+ restrict:'A',
905
+ link: linker
906
+ };
907
+ });
908
+
909
+ /**
910
+ * Directive for Popover jQuery plugin.
911
+ */
912
+ module.directive('popover', function() {
913
+ return function(scope, element, attrs) {
914
+ element.popover({
915
+ trigger : 'hover',
916
+ content : function() { return this.getAttribute('popover'); },
917
+ html : true,
918
+ placement: 'top',
919
+ template: '<div class="popover bookly-font-xs" style="width: 220px" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
920
+ });
921
+ };
922
+ });
923
+
924
+ /**
925
+ * Filters for pagination in Schedule.
926
+ */
927
+ module.filter('startFrom', function() {
928
+ return function(input, start){
929
+ start = +start;
930
+ return input.slice(start);
931
+ }
932
+ });
933
+ module.filter('range', function() {
934
+ return function(input, total) {
935
+ total = parseInt(total);
936
+
937
+ for (var i = 1; i <= total; ++ i) {
938
+ input.push(i);
939
+ }
940
+
941
+ return input;
942
+ };
943
+ });
944
+ })();
945
+
946
+ /**
947
+ * @param int appointment_id
948
+ * @param int staff_id
949
+ * @param moment start_date
950
+ * @param function callback
951
+ */
952
+ var showAppointmentDialog = function (appointment_id, staff_id, start_date, callback) {
953
+ var $dialog = jQuery('#bookly-appointment-dialog');
954
+ var $scope = angular.element($dialog[0]).scope();
955
+ $scope.$apply(function ($scope) {
956
+ $scope.loading = true;
957
+ $dialog
958
+ .find('.modal-title')
959
+ .text(appointment_id ? BooklyL10nAppDialog.title.edit_appointment : BooklyL10nAppDialog.title.new_appointment);
960
+ // Populate data source.
961
+ $scope.dataSource.loadData().then(function() {
962
+ $scope.loading = false;
963
+ if (appointment_id) {
964
+ $scope.configureEditForm(appointment_id, callback);
965
+ } else {
966
+ $scope.configureNewForm(staff_id, start_date, callback);
967
+ }
968
+ });
969
+ });
970
+
971
+ // hide customer details dialog, if it remained opened.
972
+ if (jQuery('#bookly-customer-details-dialog').hasClass('in')) {
973
+ jQuery('#bookly-customer-details-dialog').modal('hide');
974
+ }
975
+
976
+ // hide new customer dialog, if it remained opened.
977
+ if (jQuery('#bookly-customer-dialog').hasClass('in')) {
978
+ jQuery('#bookly-customer-dialog').modal('hide');
979
+ }
980
+
981
+ $dialog.modal('show');
982
  };
backend/modules/calendar/templates/_appointment_dialog.php CHANGED
@@ -11,7 +11,7 @@
11
  <div ng-show=loading class="modal-body">
12
  <div class="bookly-loading"></div>
13
  </div>
14
- <div ng-hide=loading class="modal-body">
15
  <div class=form-group>
16
  <label for="ab_provider"><?php _e( 'Provider', 'bookly' ) ?></label>
17
  <select id="ab_provider" class="field form-control" ng-model="form.staff" ng-options="s.full_name for s in dataSource.data.staff" ng-change="onStaffChange()"></select>
@@ -59,16 +59,16 @@
59
  <?php _e( 'Selected period doesn\'t match service duration', 'bookly' ) ?>
60
  </p>
61
  <p class="text-success" my-slide-up="errors.time_interval" ng-bind="errors.time_interval"></p>
62
- <p class="text-success" my-slide-up=errors.date_interval_not_available id=date_interval_not_available_msg>
63
  <?php _e( 'The selected period is occupied by another appointment', 'bookly' ) ?>
64
  </p>
65
  </div>
66
  </div>
67
  </div>
68
  </div>
69
-
70
  <div class=form-group>
71
- <label for="chosen"><?php _e( 'Customers', 'bookly' ) ?></label>
72
  <span ng-show="form.service" title="<?php esc_attr_e( 'Selected / maximum', 'bookly' ) ?>">
73
  ({{dataSource.getTotalNumberOfPersons()}}/{{form.service.capacity}})
74
  </span>
@@ -76,24 +76,24 @@
76
  <li ng-repeat="customer in form.customers">
77
  {{customer.number_of_persons}}&times;<i class="glyphicon glyphicon-user"></i>
78
  <a ng-click="editCustomerDetails(customer)" title="<?php esc_attr_e( 'Edit booking details', 'bookly' ) ?>" href="#">{{customer.name}}</a>
79
- <span ng-class="{'bookly-margin-left-sm glyphicon': true, 'glyphicon-time': customer.status == 'pending', 'glyphicon-ok': customer.status == 'approved', 'glyphicon-remove': customer.status == 'cancelled'}"
80
  popover="<?php esc_attr_e( 'Status', 'bookly' ) ?>: {{statusToString(customer.status)}}"></span>
81
  <span ng-show="customer.payment_id"
82
  popover="<?php esc_attr_e( 'Payment', 'bookly' ) ?>: {{customer.payment_title}}">
83
  <a data-toggle="modal"
84
  href="#bookly-payment-details-modal"
85
  data-payment_id="{{customer.payment_id}}"
86
- ng-class="{'bookly-js-toggle-popover bookly-margin-left-sm glyphicon': true, 'glyphicon-usd': customer.payment_type == 'full', 'glyphicon-alert': customer.payment_type == 'partial'}"></a>
87
  </span>
88
- <a ng-click="removeCustomer(customer)" class="glyphicon glyphicon-trash text-danger bookly-margin-left-sm" href="#"
89
  popover="<?php esc_attr_e( 'Remove customer', 'bookly' ) ?>"></a>
90
  </li>
91
  </ul>
92
 
93
- <div ng-show="!form.service || dataSource.getTotalNotCancelledNumberOfPersons() < form.service.capacity">
94
  <div class="form-group">
95
  <div class="input-group">
96
- <select id="chosen" multiple data-placeholder="<?php esc_attr_e( '-- Search customers --', 'bookly' ) ?>"
97
  class="field chzn-select form-control" chosen="dataSource.data.customers"
98
  ng-model="form.customers" ng-options="c.name for c in dataSource.data.customers">
99
  </select>
@@ -115,7 +115,7 @@
115
  <div class=form-group>
116
  <label for="ab_notification"><?php _e( 'Send notifications', 'bookly' ) ?></label>
117
  <p class="help-block"><?php _e( 'If email or SMS notifications are enabled and you want customers or staff member to be notified about this appointment after saving, select appropriate option before clicking Save. With "If status changed" the notifications are sent to those customers whose status has just been changed. With "To all customers" the notifications are sent to everyone in the list.', 'bookly' ) ?></p>
118
- <select class="form-control ab-inline-block ab-auto-w" style="margin-top: 0" ng-model=form.notification id="ab_notification">
119
  <option value="no"><?php _e( 'Don\'t send', 'bookly' ) ?></option>
120
  <option value="changed_status"><?php _e( 'If status changed', 'bookly' ) ?></option>
121
  <option value="all"><?php _e( 'To all customers', 'bookly' ) ?></option>
@@ -127,12 +127,12 @@
127
  <textarea class="form-control" ng-model=form.internal_note id="ab_internal_note"></textarea>
128
  </div>
129
  </div>
 
130
  <div class="modal-footer">
131
  <div ng-hide=loading>
132
- <?php \BooklyLite\Lib\Utils\Common::submitButton() ?>
133
- <button type="button" ng-click="closeDialog()" class="ab-reset-form btn btn-lg btn-default" data-dismiss="modal">
134
- <?php _e( 'Cancel', 'bookly' ) ?>
135
- </button>
136
  </div>
137
  </div>
138
  </form>
11
  <div ng-show=loading class="modal-body">
12
  <div class="bookly-loading"></div>
13
  </div>
14
+ <div ng-hide="loading || form.screen != 'main'" class="modal-body">
15
  <div class=form-group>
16
  <label for="ab_provider"><?php _e( 'Provider', 'bookly' ) ?></label>
17
  <select id="ab_provider" class="field form-control" ng-model="form.staff" ng-options="s.full_name for s in dataSource.data.staff" ng-change="onStaffChange()"></select>
59
  <?php _e( 'Selected period doesn\'t match service duration', 'bookly' ) ?>
60
  </p>
61
  <p class="text-success" my-slide-up="errors.time_interval" ng-bind="errors.time_interval"></p>
62
+ <p class="text-danger" my-slide-up=errors.date_interval_not_available id=date_interval_not_available_msg>
63
  <?php _e( 'The selected period is occupied by another appointment', 'bookly' ) ?>
64
  </p>
65
  </div>
66
  </div>
67
  </div>
68
  </div>
69
+ <?php do_action( 'bookly_recurring_appointments_render_appointment_dialog_repeat' ) ?>
70
  <div class=form-group>
71
+ <label for="bookly-chosen"><?php _e( 'Customers', 'bookly' ) ?></label>
72
  <span ng-show="form.service" title="<?php esc_attr_e( 'Selected / maximum', 'bookly' ) ?>">
73
  ({{dataSource.getTotalNumberOfPersons()}}/{{form.service.capacity}})
74
  </span>
76
  <li ng-repeat="customer in form.customers">
77
  {{customer.number_of_persons}}&times;<i class="glyphicon glyphicon-user"></i>
78
  <a ng-click="editCustomerDetails(customer)" title="<?php esc_attr_e( 'Edit booking details', 'bookly' ) ?>" href="#">{{customer.name}}</a>
79
+ <span ng-class="{'dashicons': true, 'dashicons-clock': customer.status == 'pending', 'dashicons-yes': customer.status == 'approved', 'dashicons-no': customer.status == 'cancelled', 'dashicons-dismiss': customer.status == 'rejected'}"
80
  popover="<?php esc_attr_e( 'Status', 'bookly' ) ?>: {{statusToString(customer.status)}}"></span>
81
  <span ng-show="customer.payment_id"
82
  popover="<?php esc_attr_e( 'Payment', 'bookly' ) ?>: {{customer.payment_title}}">
83
  <a data-toggle="modal"
84
  href="#bookly-payment-details-modal"
85
  data-payment_id="{{customer.payment_id}}"
86
+ ng-class="{'bookly-js-toggle-popover dashicons': true, 'dashicons-thumbs-up': customer.payment_type == 'full', 'dashicons-warning': customer.payment_type == 'partial'}"></a>
87
  </span>
88
+ <a ng-click="removeCustomer(customer)" class="dashicons dashicons-trash text-danger" href="#"
89
  popover="<?php esc_attr_e( 'Remove customer', 'bookly' ) ?>"></a>
90
  </li>
91
  </ul>
92
 
93
+ <div ng-show="!form.service || dataSource.getTotalNumberOfNotCancelledPersons() < form.service.capacity">
94
  <div class="form-group">
95
  <div class="input-group">
96
+ <select id="bookly-chosen" multiple data-placeholder="<?php esc_attr_e( '-- Search customers --', 'bookly' ) ?>"
97
  class="field chzn-select form-control" chosen="dataSource.data.customers"
98
  ng-model="form.customers" ng-options="c.name for c in dataSource.data.customers">
99
  </select>
115
  <div class=form-group>
116
  <label for="ab_notification"><?php _e( 'Send notifications', 'bookly' ) ?></label>
117
  <p class="help-block"><?php _e( 'If email or SMS notifications are enabled and you want customers or staff member to be notified about this appointment after saving, select appropriate option before clicking Save. With "If status changed" the notifications are sent to those customers whose status has just been changed. With "To all customers" the notifications are sent to everyone in the list.', 'bookly' ) ?></p>
118
+ <select class="form-control" style="margin-top: 0" ng-model=form.notification id="ab_notification">
119
  <option value="no"><?php _e( 'Don\'t send', 'bookly' ) ?></option>
120
  <option value="changed_status"><?php _e( 'If status changed', 'bookly' ) ?></option>
121
  <option value="all"><?php _e( 'To all customers', 'bookly' ) ?></option>
127
  <textarea class="form-control" ng-model=form.internal_note id="ab_internal_note"></textarea>
128
  </div>
129
  </div>
130
+ <?php do_action( 'bookly_recurring_appointments_render_appointment_dialog_schedule' ) ?>
131
  <div class="modal-footer">
132
  <div ng-hide=loading>
133
+ <?php do_action( 'bookly_recurring_appointments_render_appointment_dialog_next_button' ) ?>
134
+ <?php \BooklyLite\Lib\Utils\Common::customButton( 'bookly-save', 'btn-lg btn-success', null, array( 'ng-hide' => 'form.repeat.enabled && form.screen == \'main\'', 'ng-disabled' => 'form.repeat.enabled && schIsScheduleEmpty()' ), 'submit' ) ?>
135
+ <?php \BooklyLite\Lib\Utils\Common::customButton( null, 'btn-lg btn-default', __( 'Cancel', 'bookly' ), array( 'ng-click' => 'closeDialog()', 'data-dismiss' => 'modal' ) ) ?>
 
136
  </div>
137
  </div>
138
  </form>
backend/modules/calendar/templates/_customer_details_dialog.php CHANGED
@@ -1,98 +1,99 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Entities\CustomerAppointment;
3
- ?>
4
- <div id="bookly-customer-details-dialog" class="modal fade" tabindex=-1 role="dialog">
5
- <div class="modal-dialog">
6
- <div class="modal-content">
7
- <div class="modal-header">
8
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
9
- <div class="modal-title h2"><?php _e( 'Edit booking details', 'bookly' ) ?></div>
10
- </div>
11
- <form ng-hide=loading style="z-index: 1050">
12
- <div class="modal-body">
13
- <div class="form-group">
14
- <label for="ab-appointment-status"><?php _e( 'Status', 'bookly' ) ?></label>
15
- <select class="ab-custom-field form-control" id="ab-appointment-status">
16
- <option value="<?php echo CustomerAppointment::STATUS_PENDING ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_PENDING ) ) ?></option>
17
- <option value="<?php echo CustomerAppointment::STATUS_APPROVED ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_APPROVED ) ) ?></option>
18
- <option value="<?php echo CustomerAppointment::STATUS_CANCELLED ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_CANCELLED ) ) ?></option>
19
- </select>
20
- </div>
21
- <div class="form-group">
22
- <label for="ab-edit-number-of-persons"><?php _e( 'Number of persons', 'bookly' ) ?></label>
23
- <select class="ab-custom-field form-control" id="ab-edit-number-of-persons"></select>
24
- </div>
25
- <?php do_action( 'bookly_render_customer_details_dialog' ) ?>
26
- <h3 class="bookly-block-head bookly-color-gray">
27
- <?php _e( 'Custom Fields', 'bookly' ) ?>
28
- </h3>
29
- <div id="ab--custom-fields">
30
- <?php foreach ( $custom_fields as $custom_field ) : ?>
31
- <div class="form-group" data-type="<?php echo esc_attr( $custom_field->type )?>" data-id="<?php echo esc_attr( $custom_field->id ) ?>" data-services="<?php echo esc_attr( json_encode( $custom_field->services ) ) ?>">
32
- <label for="custom_field_<?php echo esc_attr( $custom_field->id ) ?>"><?php echo $custom_field->label ?></label>
33
- <div>
34
- <?php if ( $custom_field->type == 'text-field' ) : ?>
35
- <input id="custom_field_<?php echo esc_attr( $custom_field->id ) ?>" type="text" class="ab-custom-field form-control" />
36
-
37
- <?php elseif ( $custom_field->type == 'textarea' ) : ?>
38
- <textarea id="custom_field_<?php echo esc_attr( $custom_field->id ) ?>" rows="3" class="ab-custom-field form-control"></textarea>
39
-
40
- <?php elseif ( $custom_field->type == 'checkboxes' ) : ?>
41
- <?php foreach ( $custom_field->items as $item ) : ?>
42
- <div class="checkbox">
43
- <label>
44
- <input class="ab-custom-field" type="checkbox" value="<?php echo esc_attr( $item ) ?>" />
45
- <?php echo $item ?>
46
- </label>
47
- </div>
48
- <?php endforeach ?>
49
-
50
- <?php elseif ( $custom_field->type == 'radio-buttons' ) : ?>
51
- <?php foreach ( $custom_field->items as $item ) : ?>
52
- <div class="radio">
53
- <label>
54
- <input type="radio" name="<?php echo $custom_field->id ?>" class="ab-custom-field" value="<?php echo esc_attr( $item ) ?>" />
55
- <?php echo $item ?>
56
- </label>
57
- </div>
58
- <?php endforeach ?>
59
-
60
- <?php elseif ( $custom_field->type == 'drop-down' ) : ?>
61
- <select id="custom_field_<?php echo esc_attr( $custom_field->id ) ?>" class="ab-custom-field form-control">
62
- <option value=""></option>
63
- <?php foreach ( $custom_field->items as $item ) : ?>
64
- <option value="<?php echo esc_attr( $item ) ?>"><?php echo $item ?></option>
65
- <?php endforeach ?>
66
- </select>
67
- <?php endif ?>
68
- </div>
69
- </div>
70
- <?php endforeach ?>
71
- </div>
72
-
73
- <?php if ( $extras = apply_filters( 'bookly_extras_find_all', array() ) ) : ?>
74
- <h3 class="bookly-block-head bookly-color-gray">
75
- <?php _e( 'Extras', 'bookly' ) ?>
76
- </h3>
77
- <div id="bookly-extras" class="bookly-flexbox">
78
- <?php foreach ( $extras as $extra ) : ?>
79
- <div class="bookly-flex-row service_<?php echo $extra->get( 'service_id' ) ?> bookly-margin-bottom-sm">
80
- <div class="bookly-flex-cell bookly-padding-bottom-sm" style="width:5em">
81
- <input class="extras-count form-control" data-id="<?php echo $extra->get( 'id' ) ?>" type="number" min="0" name="extra[<?php echo $extra->get( 'id' ) ?>]" value="0" />
82
- </div>
83
- <div class="bookly-flex-cell bookly-padding-bottom-sm bookly-vertical-middle">
84
- &nbsp;&times; <b><?php echo $extra->getTitle() ?></b> (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $extra->get( 'price' ) ) ?>)
85
- </div>
86
- </div>
87
- <?php endforeach ?>
88
- </div>
89
- <?php endif ?>
90
- </div>
91
- <div class="modal-footer">
92
- <input type="button" data-customer="" ng-click=saveCustomFields() class="ab-popup-save btn btn-lg btn-success" value="<?php _e( 'Apply', 'bookly' ) ?>">
93
- <input type="button" class="ab-reset-form btn btn-lg btn-default" data-dismiss=modal value="<?php _e( 'Cancel', 'bookly' ) ?>" aria-hidden=true>
94
- </div>
95
- </form>
96
- </div><!-- /.modal-content -->
97
- </div><!-- /.modal-dialog -->
 
98
  </div><!-- /.modal -->
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use BooklyLite\Lib\Entities\CustomerAppointment;
3
+ ?>
4
+ <div id="bookly-customer-details-dialog" class="modal fade" tabindex=-1 role="dialog">
5
+ <div class="modal-dialog">
6
+ <div class="modal-content">
7
+ <div class="modal-header">
8
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
9
+ <div class="modal-title h2"><?php _e( 'Edit booking details', 'bookly' ) ?></div>
10
+ </div>
11
+ <form ng-hide=loading style="z-index: 1050">
12
+ <div class="modal-body">
13
+ <div class="form-group">
14
+ <label for="ab-appointment-status"><?php _e( 'Status', 'bookly' ) ?></label>
15
+ <select class="ab-custom-field form-control" id="ab-appointment-status">
16
+ <option value="<?php echo CustomerAppointment::STATUS_PENDING ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_PENDING ) ) ?></option>
17
+ <option value="<?php echo CustomerAppointment::STATUS_APPROVED ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_APPROVED ) ) ?></option>
18
+ <option value="<?php echo CustomerAppointment::STATUS_CANCELLED ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_CANCELLED ) ) ?></option>
19
+ <option value="<?php echo CustomerAppointment::STATUS_REJECTED ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_REJECTED ) ) ?></option>
20
+ </select>
21
+ </div>
22
+ <div class="form-group">
23
+ <label for="ab-edit-number-of-persons"><?php _e( 'Number of persons', 'bookly' ) ?></label>
24
+ <select class="ab-custom-field form-control" id="ab-edit-number-of-persons"></select>
25
+ </div>
26
+ <?php do_action( 'bookly_render_customer_details_dialog' ) ?>
27
+ <h3 class="bookly-block-head bookly-color-gray">
28
+ <?php _e( 'Custom Fields', 'bookly' ) ?>
29
+ </h3>
30
+ <div id="ab--custom-fields">
31
+ <?php foreach ( $custom_fields as $custom_field ) : ?>
32
+ <div class="form-group" data-type="<?php echo esc_attr( $custom_field->type )?>" data-id="<?php echo esc_attr( $custom_field->id ) ?>" data-services="<?php echo esc_attr( json_encode( $custom_field->services ) ) ?>">
33
+ <label for="custom_field_<?php echo esc_attr( $custom_field->id ) ?>"><?php echo $custom_field->label ?></label>
34
+ <div>
35
+ <?php if ( $custom_field->type == 'text-field' ) : ?>
36
+ <input id="custom_field_<?php echo esc_attr( $custom_field->id ) ?>" type="text" class="ab-custom-field form-control" />
37
+
38
+ <?php elseif ( $custom_field->type == 'textarea' ) : ?>
39
+ <textarea id="custom_field_<?php echo esc_attr( $custom_field->id ) ?>" rows="3" class="ab-custom-field form-control"></textarea>
40
+
41
+ <?php elseif ( $custom_field->type == 'checkboxes' ) : ?>
42
+ <?php foreach ( $custom_field->items as $item ) : ?>
43
+ <div class="checkbox">
44
+ <label>
45
+ <input class="ab-custom-field" type="checkbox" value="<?php echo esc_attr( $item ) ?>" />
46
+ <?php echo $item ?>
47
+ </label>
48
+ </div>
49
+ <?php endforeach ?>
50
+
51
+ <?php elseif ( $custom_field->type == 'radio-buttons' ) : ?>
52
+ <?php foreach ( $custom_field->items as $item ) : ?>
53
+ <div class="radio">
54
+ <label>
55
+ <input type="radio" name="<?php echo $custom_field->id ?>" class="ab-custom-field" value="<?php echo esc_attr( $item ) ?>" />
56
+ <?php echo $item ?>
57
+ </label>
58
+ </div>
59
+ <?php endforeach ?>
60
+
61
+ <?php elseif ( $custom_field->type == 'drop-down' ) : ?>
62
+ <select id="custom_field_<?php echo esc_attr( $custom_field->id ) ?>" class="ab-custom-field form-control">
63
+ <option value=""></option>
64
+ <?php foreach ( $custom_field->items as $item ) : ?>
65
+ <option value="<?php echo esc_attr( $item ) ?>"><?php echo $item ?></option>
66
+ <?php endforeach ?>
67
+ </select>
68
+ <?php endif ?>
69
+ </div>
70
+ </div>
71
+ <?php endforeach ?>
72
+ </div>
73
+
74
+ <?php if ( $extras = apply_filters( 'bookly_service_extras_find_all', array() ) ) : ?>
75
+ <h3 class="bookly-block-head bookly-color-gray">
76
+ <?php _e( 'Extras', 'bookly' ) ?>
77
+ </h3>
78
+ <div id="bookly-extras" class="bookly-flexbox">
79
+ <?php foreach ( $extras as $extra ) : ?>
80
+ <div class="bookly-flex-row service_<?php echo $extra->get( 'service_id' ) ?> bookly-margin-bottom-sm">
81
+ <div class="bookly-flex-cell bookly-padding-bottom-sm" style="width:5em">
82
+ <input class="extras-count form-control" data-id="<?php echo $extra->get( 'id' ) ?>" type="number" min="0" name="extra[<?php echo $extra->get( 'id' ) ?>]" value="0" />
83
+ </div>
84
+ <div class="bookly-flex-cell bookly-padding-bottom-sm bookly-vertical-middle">
85
+ &nbsp;&times; <b><?php echo $extra->getTitle() ?></b> (<?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $extra->get( 'price' ) ) ?>)
86
+ </div>
87
+ </div>
88
+ <?php endforeach ?>
89
+ </div>
90
+ <?php endif ?>
91
+ </div>
92
+ <div class="modal-footer">
93
+ <input type="button" data-customer="" ng-click=saveCustomFields() class="ab-popup-save btn btn-lg btn-success" value="<?php _e( 'Apply', 'bookly' ) ?>">
94
+ <input type="button" class="btn btn-lg btn-default" data-dismiss=modal value="<?php _e( 'Cancel', 'bookly' ) ?>" aria-hidden=true>
95
+ </div>
96
+ </form>
97
+ </div><!-- /.modal-content -->
98
+ </div><!-- /.modal-dialog -->
99
  </div><!-- /.modal -->
backend/modules/calendar/templates/_delete_dialog.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Template for delete appointment dialog
4
+ */
5
+ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
6
+ ?>
7
+
8
+ <div id="bookly-delete-dialog" class="modal fade" tabindex=-1 role="dialog">
9
+ <div class="modal-dialog">
10
+ <div class="modal-content">
11
+ <div class="modal-header">
12
+ <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
13
+ <div class="modal-title h2"><?php _e( 'Delete', 'bookly' ) ?></div>
14
+ </div>
15
+ <div class="modal-body">
16
+ <div class="checkbox">
17
+ <label>
18
+ <input id="bookly-delete-notify" type="checkbox" />
19
+ <?php _e( 'Send notifications', 'bookly' ) ?>
20
+ </label>
21
+ </div>
22
+ <div class="form-group" style="display: none;" id="bookly-delete-reason-cover">
23
+ <input class="form-control" type="text" id="bookly-delete-reason" placeholder="<?php _e( 'Cancellation reason (optional)', 'bookly' ) ?>" />
24
+ </div>
25
+ </div>
26
+ <div class="modal-footer">
27
+ <?php \BooklyLite\Lib\Utils\Common::deleteButton(); ?>
28
+ <?php \BooklyLite\Lib\Utils\Common::customButton( null, 'btn-default', __( 'Cancel', 'bookly' ), array( 'ng-click' => 'closeDialog()', 'data-dismiss' => 'modal' ) ) ?>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
backend/modules/calendar/templates/calendar.php CHANGED
@@ -1,92 +1,94 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- $user_names = array();
3
- $user_ids = array();
4
- ?>
5
- <style>
6
- .fc-slats tr { height: <?php echo max( 21, intval( 620 / (1440 / get_option( 'ab_settings_time_slot_length' ) ) ) ) ?>px; }
7
- .fc-time-grid-event.fc-short .fc-time::after { content: '' !important; }
8
- </style>
9
- <div id="bookly-tbs" class="wrap">
10
- <div class="bookly-tbs-body">
11
- <div class="page-header text-right clearfix">
12
- <div class="bookly-page-title">
13
- <?php _e( 'Calendar', 'bookly' ) ?>
14
- </div>
15
- </div>
16
- <div class="panel panel-default bookly-main bookly-fc-inner">
17
- <div class="panel-body">
18
- <?php if ( $staff_members ) : ?>
19
- <ul class="bookly-nav bookly-nav-tabs">
20
- <?php if ( \BooklyLite\Lib\Utils\Common::isCurrentUserAdmin() ) : ?>
21
- <li class="bookly-nav-item bookly-js-calendar-tab" data-staff_id="0">
22
- <?php _e( 'All', 'bookly' ) ?>
23
- </li>
24
- <?php endif ?>
25
- <?php foreach ( $staff_members as $i => $staff ) : ?>
26
- <li class="bookly-nav-item bookly-js-calendar-tab" data-staff_id="<?php echo $staff->id ?>" style="display: none">
27
- <?php echo $staff->full_name ?>
28
- </li>
29
- <?php endforeach ?>
30
- <?php if ( \BooklyLite\Lib\Utils\Common::isCurrentUserAdmin() ) : ?>
31
- <div class="btn-group pull-right" style="margin-top: 5px;">
32
- <button class="btn btn-default dropdown-toggle bookly-flexbox" data-toggle="dropdown">
33
- <div class="bookly-flex-cell"><i class="dashicons dashicons-admin-users bookly-margin-right-md"></i></div>
34
- <div class="bookly-flex-cell text-left"><span id="ab-staff-button"></span></div>
35
- <div class="bookly-flex-cell"><div class="bookly-margin-left-md"><span class="caret"></span></div></div>
36
- </button>
37
- <ul class="dropdown-menu bookly-entity-selector">
38
- <li>
39
- <a class="checkbox" href="javascript:void(0)">
40
- <label><input type="checkbox" id="bookly-check-all-entities"><?php _e( 'All staff', 'bookly' ) ?></label>
41
- </a>
42
- </li>
43
- <?php foreach ( $staff_members as $i => $staff ) : ?>
44
- <li>
45
- <a class="checkbox" href="javascript:void(0)">
46
- <label>
47
- <input type="checkbox" id="ab-filter-staff-<?php echo $staff->id ?>" value="<?php echo $staff->id ?>" data-staff_name="<?php echo esc_attr( $staff->full_name ) ?>" class="bookly-js-check-entity">
48
- <?php echo $staff->full_name ?>
49
- </label>
50
- </a>
51
- </li>
52
- <?php endforeach ?>
53
- </ul>
54
- </div>
55
- <?php endif ?>
56
- </ul>
57
- <?php endif ?>
58
- <div class="bookly-margin-top-xlg">
59
- <div style="display: none;" class="bookly-loading bookly-js-loading"></div>
60
- <?php if ( $staff_members ) : ?>
61
- <div class="fc-loading-inner" style="display: none">
62
- <div class="fc-loading"></div>
63
- </div>
64
- <div id="bookly-fc-wrapper" class="bookly-calendar">
65
- <div class="bookly-js-calendar-element"></div>
66
- </div>
67
- <?php \BooklyLite\Backend\Modules\Calendar\Components::getInstance()->renderAppointmentDialog() ?>
68
- <?php do_action( 'bookly_render_component_calendar' ) ?>
69
- <?php else : ?>
70
- <div class="well">
71
- <div class="h1"><?php _e( 'Welcome to Bookly!', 'bookly' ) ?></div>
72
- <h3><?php _e( 'Thank you for purchasing our product.', 'bookly' ) ?></h3>
73
- <h3><?php _e( 'Bookly offers a simple solution for making appointments. With our plugin you will be able to easily manage your availability time and handle the flow of your clients.', 'bookly' ) ?></h3>
74
- <p><?php _e( 'To start using Bookly, you need to follow these steps which are the minimum requirements to get it running!', 'bookly' ) ?></p>
75
- <ol>
76
- <li><?php _e( 'Add staff members.', 'bookly' ) ?></li>
77
- <li><?php _e( 'Add services and assign them to staff members.', 'bookly' ) ?></li>
78
- </ol>
79
- <hr>
80
- <a class="btn btn-success" href="<?php echo \BooklyLite\Lib\Utils\Common::escAdminUrl( BooklyLite\Backend\Modules\Staff\Controller::page_slug ) ?>">
81
- <?php _e( 'Add Staff Members', 'bookly' ) ?>
82
- </a>
83
- <a class="btn btn-success" href="<?php echo \BooklyLite\Lib\Utils\Common::escAdminUrl( BooklyLite\Backend\Modules\Services\Controller::page_slug ) ?>">
84
- <?php _e( 'Add Services', 'bookly' ) ?>
85
- </a>
86
- </div>
87
- <?php endif ?>
88
- </div>
89
- </div>
90
- </div>
91
- </div>
 
 
92
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <style>
3
+ .fc-slats tr { height: <?php echo max( 21, (int) ( 0.43 * get_option( 'bookly_gen_time_slot_length' ) ) ) ?>px; }
4
+ .fc-time-grid-event.fc-short .fc-time::after { content: '' !important; }
5
+ </style>
6
+ <div id="bookly-tbs" class="wrap">
7
+ <div class="bookly-tbs-body">
8
+ <div class="page-header text-right clearfix">
9
+ <div class="bookly-page-title">
10
+ <?php _e( 'Calendar', 'bookly' ) ?>
11
+ </div>
12
+ <?php if ( \BooklyLite\Lib\Utils\Common::isCurrentUserAdmin() ) : ?>
13
+ <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
14
+ <?php endif ?>
15
+ </div>
16
+ <div class="panel panel-default bookly-main bookly-fc-inner">
17
+ <div class="panel-body">
18
+ <?php if ( $staff_members ) : ?>
19
+ <ul class="bookly-nav bookly-nav-tabs">
20
+ <?php if ( \BooklyLite\Lib\Utils\Common::isCurrentUserAdmin() ) : ?>
21
+ <li class="bookly-nav-item bookly-js-calendar-tab" data-staff_id="0">
22
+ <?php _e( 'All', 'bookly' ) ?>
23
+ </li>
24
+ <?php endif ?>
25
+ <?php foreach ( $staff_members as $staff ) : ?>
26
+ <li class="bookly-nav-item bookly-js-calendar-tab" data-staff_id="<?php echo $staff->id ?>" style="display: none">
27
+ <?php echo $staff->full_name ?>
28
+ </li>
29
+ <?php endforeach ?>
30
+ <?php if ( \BooklyLite\Lib\Utils\Common::isCurrentUserAdmin() ) : ?>
31
+ <div class="btn-group pull-right" style="margin-top: 5px;">
32
+ <button class="btn btn-default dropdown-toggle bookly-flexbox" data-toggle="dropdown">
33
+ <div class="bookly-flex-cell"><i class="dashicons dashicons-admin-users bookly-margin-right-md"></i></div>
34
+ <div class="bookly-flex-cell text-left"><span id="ab-staff-button"></span></div>
35
+ <div class="bookly-flex-cell"><div class="bookly-margin-left-md"><span class="caret"></span></div></div>
36
+ </button>
37
+ <ul class="dropdown-menu bookly-entity-selector">
38
+ <li>
39
+ <a class="checkbox" href="javascript:void(0)">
40
+ <label><input type="checkbox" id="bookly-check-all-entities"><?php _e( 'All staff', 'bookly' ) ?></label>
41
+ </a>
42
+ </li>
43
+ <?php foreach ( $staff_members as $staff ) : ?>
44
+ <li>
45
+ <a class="checkbox" href="javascript:void(0)">
46
+ <label>
47
+ <input type="checkbox" id="ab-filter-staff-<?php echo $staff->id ?>" value="<?php echo $staff->id ?>" data-staff_name="<?php echo esc_attr( $staff->full_name ) ?>" class="bookly-js-check-entity">
48
+ <?php echo $staff->full_name ?>
49
+ </label>
50
+ </a>
51
+ </li>
52
+ <?php endforeach ?>
53
+ </ul>
54
+ </div>
55
+ <?php endif ?>
56
+ </ul>
57
+ <?php endif ?>
58
+ <div class="bookly-margin-top-xlg">
59
+ <div style="display: none;" class="bookly-loading bookly-js-loading"></div>
60
+ <?php if ( $staff_members ) : ?>
61
+ <div class="fc-loading-inner" style="display: none">
62
+ <div class="fc-loading"></div>
63
+ </div>
64
+ <div id="bookly-fc-wrapper" class="bookly-calendar">
65
+ <div class="bookly-js-calendar-element"></div>
66
+ </div>
67
+ <?php \BooklyLite\Backend\Modules\Calendar\Components::getInstance()->renderAppointmentDialog() ?>
68
+ <?php do_action( 'bookly_render_component_calendar' ) ?>
69
+ <?php else : ?>
70
+ <div class="well">
71
+ <div class="h1"><?php _e( 'Welcome to Bookly!', 'bookly' ) ?></div>
72
+ <h3><?php _e( 'Thank you for purchasing our product.', 'bookly' ) ?></h3>
73
+ <h3><?php _e( 'Bookly offers a simple solution for making appointments. With our plugin you will be able to easily manage your availability time and handle the flow of your clients.', 'bookly' ) ?></h3>
74
+ <p><?php _e( 'To start using Bookly, you need to follow these steps which are the minimum requirements to get it running!', 'bookly' ) ?></p>
75
+ <ol>
76
+ <li><?php _e( 'Add staff members.', 'bookly' ) ?></li>
77
+ <li><?php _e( 'Add services and assign them to staff members.', 'bookly' ) ?></li>
78
+ </ol>
79
+ <hr>
80
+ <a class="btn btn-success" href="<?php echo \BooklyLite\Lib\Utils\Common::escAdminUrl( BooklyLite\Backend\Modules\Staff\Controller::page_slug ) ?>">
81
+ <?php _e( 'Add Staff Members', 'bookly' ) ?>
82
+ </a>
83
+ <a class="btn btn-success" href="<?php echo \BooklyLite\Lib\Utils\Common::escAdminUrl( BooklyLite\Backend\Modules\Services\Controller::page_slug ) ?>">
84
+ <?php _e( 'Add Services', 'bookly' ) ?>
85
+ </a>
86
+ </div>
87
+ <?php endif ?>
88
+ </div>
89
+ </div>
90
+ </div>
91
+
92
+ <?php \BooklyLite\Backend\Modules\Calendar\Components::getInstance()->renderDeleteDialog(); ?>
93
+ </div>
94
  </div>
backend/modules/coupons/Controller.php CHANGED
@@ -1,64 +1,66 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Coupons;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Coupons
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- /**
13
- * Default action
14
- */
15
- public function index()
16
- {
17
- $this->enqueueStyles( array(
18
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
19
- 'frontend' => array( 'css/ladda.min.css', ),
20
- ) );
21
-
22
- $this->enqueueScripts( array(
23
- 'backend' => array(
24
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
25
- 'js/datatables.min.js' => array( 'jquery' ),
26
- 'js/alert.js' => array( 'jquery' ),
27
- ),
28
- 'frontend' => array(
29
- 'js/spin.min.js' => array( 'jquery' ),
30
- 'js/ladda.min.js' => array( 'jquery' ),
31
- ),
32
- 'module' => array( 'js/coupons.js' => array( 'jquery' ) )
33
- ) );
34
-
35
- wp_localize_script( 'ab-coupons.js', 'BooklyL10n', array(
36
- 'edit' => __( 'Edit', 'bookly' ),
37
- 'zeroRecords' => __( 'No coupons found.', 'bookly' ),
38
- 'processing' => __( 'Processing...', 'bookly' ),
39
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
40
- 'selector' => array(
41
- 'all_selected' => __( 'All services', 'bookly' ),
42
- 'nothing_selected' => __( 'No service selected', 'bookly' ),
43
- 'collection' => array(),
44
- ),
45
- 'limitations' => __( '<b class="h4">This function is disabled in the Lite version of Bookly.</b><br><br>If you find the plugin useful for your business please consider buying a licence for the full version.<br>It costs just $59 and for this money you will get many useful functions, lifetime free updates and excellent support!<br>More information can be found here', 'bookly' ) . ': <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
46
- ) );
47
-
48
- $this->render( 'index' );
49
- }
50
-
51
- // Protected methods.
52
-
53
- /**
54
- * Override parent method to add 'wp_ajax_ab_' prefix
55
- * so current 'execute*' methods look nicer.
56
- *
57
- * @param string $prefix
58
- */
59
- protected function registerWpActions( $prefix = '' )
60
- {
61
- parent::registerWpActions( 'wp_ajax_ab_' );
62
- }
63
-
 
 
64
  }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Coupons;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Controller
8
+ * @package BooklyLite\Backend\Modules\Coupons
9
+ */
10
+ class Controller extends Lib\Base\Controller
11
+ {
12
+ const page_slug = 'bookly-coupons';
13
+
14
+ /**
15
+ * Default action
16
+ */
17
+ public function index()
18
+ {
19
+ $this->enqueueStyles( array(
20
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
21
+ 'frontend' => array( 'css/ladda.min.css', ),
22
+ ) );
23
+
24
+ $this->enqueueScripts( array(
25
+ 'backend' => array(
26
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
27
+ 'js/datatables.min.js' => array( 'jquery' ),
28
+ 'js/alert.js' => array( 'jquery' ),
29
+ ),
30
+ 'frontend' => array(
31
+ 'js/spin.min.js' => array( 'jquery' ),
32
+ 'js/ladda.min.js' => array( 'jquery' ),
33
+ ),
34
+ 'module' => array( 'js/coupons.js' => array( 'jquery' ) )
35
+ ) );
36
+
37
+ wp_localize_script( 'bookly-coupons.js', 'BooklyL10n', array(
38
+ 'edit' => __( 'Edit', 'bookly' ),
39
+ 'zeroRecords' => __( 'No coupons found.', 'bookly' ),
40
+ 'processing' => __( 'Processing...', 'bookly' ),
41
+ 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
42
+ 'selector' => array(
43
+ 'all_selected' => __( 'All services', 'bookly' ),
44
+ 'nothing_selected' => __( 'No service selected', 'bookly' ),
45
+ 'collection' => array(),
46
+ ),
47
+ 'limitations' => __( '<b class="h4">This function is disabled in the Lite version of Bookly.</b><br><br>If you find the plugin useful for your business please consider buying a licence for the full version.<br>It costs just $59 and for this money you will get many useful functions, lifetime free updates and excellent support!<br>More information can be found here', 'bookly' ) . ': <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
48
+ ) );
49
+
50
+ $this->render( 'index' );
51
+ }
52
+
53
+ // Protected methods.
54
+
55
+ /**
56
+ * Override parent method to add 'wp_ajax_bookly_' prefix
57
+ * so current 'execute*' methods look nicer.
58
+ *
59
+ * @param string $prefix
60
+ */
61
+ protected function registerWpActions( $prefix = '' )
62
+ {
63
+ parent::registerWpActions( 'wp_ajax_bookly_' );
64
+ }
65
+
66
  }
backend/modules/coupons/forms/Coupon.php CHANGED
@@ -1,19 +1,19 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Coupons\Forms;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Coupon
8
- * @package BooklyLite\Backend\Modules\Coupons\Forms
9
- */
10
- class Coupon extends Lib\Base\Form
11
- {
12
- protected static $entity_class = 'Coupon';
13
-
14
- public function configure()
15
- {
16
- $this->setFields( array( 'id', 'code', 'discount', 'deduction', 'usage_limit' ) );
17
- }
18
-
19
  }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Coupons\Forms;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Coupon
8
+ * @package BooklyLite\Backend\Modules\Coupons\Forms
9
+ */
10
+ class Coupon extends Lib\Base\Form
11
+ {
12
+ protected static $entity_class = 'Coupon';
13
+
14
+ public function configure()
15
+ {
16
+ $this->setFields( array( 'id', 'code', 'discount', 'deduction', 'usage_limit' ) );
17
+ }
18
+
19
  }
backend/modules/coupons/resources/js/coupons.js CHANGED
@@ -1,64 +1,63 @@
1
- jQuery(function($) {
2
-
3
- var
4
- $coupons_list = $('#bookly-coupons-list'),
5
- $coupon_modal = $('#bookly-coupon-modal'),
6
- $save_button = $('#bookly-coupon-save')
7
- ;
8
-
9
- /**
10
- * Init DataTables.
11
- */
12
- var dt = $coupons_list.DataTable({
13
- order: [[ 0, "asc" ]],
14
- paging: false,
15
- info: false,
16
- searching: false,
17
- processing: true,
18
- responsive: true,
19
- data: [],
20
-
21
- columns: [
22
- { data: "code" },
23
- { data: "discount" },
24
- { data: "deduction" },
25
- { data: 'service_ids'},
26
- { data: "usage_limit" },
27
- { data: "used" },
28
- {
29
- responsivePriority: 1,
30
- orderable: false,
31
- searchable: false,
32
- render: function ( data, type, row, meta ) {
33
- return '';
34
- }
35
- },
36
- {
37
- responsivePriority: 1,
38
- orderable: false,
39
- searchable: false,
40
- render: function ( data, type, row, meta ) {
41
- return '';
42
- }
43
- }
44
- ],
45
- language: {
46
- zeroRecords: BooklyL10n.zeroRecords,
47
- processing: BooklyL10n.processing
48
- }
49
- });
50
-
51
- /**
52
- * Save coupon.
53
- */
54
- $save_button.on('click', function (e) {
55
- e.preventDefault();
56
- $coupon_modal.modal('hide');
57
- booklyAlert({error: [BooklyL10n.limitations]});
58
- });
59
- $('#bookly-add,#bookly-delete').on('click', function (e) {
60
- e.preventDefault();
61
- booklyAlert({error: [BooklyL10n.limitations]});
62
- });
63
-
64
- });
1
+ jQuery(function($) {
2
+
3
+ var
4
+ $coupons_list = $('#bookly-coupons-list'),
5
+ $coupon_modal = $('#bookly-coupon-modal'),
6
+ $save_button = $('#bookly-coupon-save')
7
+ ;
8
+
9
+ /**
10
+ * Init DataTables.
11
+ */
12
+ var dt = $coupons_list.DataTable({
13
+ order: [[ 0, "asc" ]],
14
+ paging: false,
15
+ info: false,
16
+ searching: false,
17
+ processing: true,
18
+ responsive: true,
19
+ data: [],
20
+ columns: [
21
+ { data: "code" },
22
+ { data: "discount" },
23
+ { data: "deduction" },
24
+ { data: 'service_ids'},
25
+ { data: "usage_limit" },
26
+ { data: "used" },
27
+ {
28
+ responsivePriority: 1,
29
+ orderable: false,
30
+ searchable: false,
31
+ render: function ( data, type, row, meta ) {
32
+ return '<button type="button" class="btn btn-default" data-toggle="modal" data-target="#bookly-coupon-modal"><i class="glyphicon glyphicon-edit"></i> ' + BooklyL10n.edit + '</button>';
33
+ }
34
+ },
35
+ {
36
+ responsivePriority: 1,
37
+ orderable: false,
38
+ searchable: false,
39
+ render: function ( data, type, row, meta ) {
40
+ return '<input type="checkbox" value="' + row.id + '">';
41
+ }
42
+ }
43
+ ],
44
+ language: {
45
+ zeroRecords: BooklyL10n.zeroRecords,
46
+ processing: BooklyL10n.processing
47
+ }
48
+ });
49
+
50
+ /**
51
+ * Save coupon.
52
+ */
53
+ $save_button.on('click', function (e) {
54
+ e.preventDefault();
55
+ $coupon_modal.modal('hide');
56
+ booklyAlert({error: [BooklyL10n.limitations]});
57
+ });
58
+ $('#bookly-add,#bookly-delete').on('click', function (e) {
59
+ e.preventDefault();
60
+ booklyAlert({error: [BooklyL10n.limitations]});
61
+ });
62
+
63
+ });
 
backend/modules/coupons/templates/_modal.php CHANGED
@@ -1 +1,66 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div class="modal fade" id="bookly-coupon-modal" tabindex="-1">
3
+ <div class="modal-dialog">
4
+ <div class="modal-content">
5
+ <form>
6
+ <div class="modal-header">
7
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
8
+ <h4 class="modal-title" id="bookly-new-coupon-title"><?php _e( 'New coupon', 'bookly' ) ?></h4>
9
+ <h4 class="modal-title" id="bookly-edit-coupon-title"><?php _e( 'Edit coupon', 'bookly' ) ?></h4>
10
+ </div>
11
+ <div class="modal-body">
12
+ <div class="row">
13
+ <div class="col-sm-12">
14
+ <div class=form-group>
15
+ <label for="bookly-coupon-code"><?php _e( 'Code', 'bookly' ) ?></label>
16
+ <input type="text" id="bookly-coupon-code" class="form-control" name="code" />
17
+ </div>
18
+ </div>
19
+ <div class="col-sm-6">
20
+ <div class=form-group>
21
+ <label for="bookly-coupon-discount"><?php _e( 'Discount (%)', 'bookly' ) ?></label>
22
+ <input type="number" id="bookly-coupon-discount" class="form-control" name="discount" />
23
+ </div>
24
+ </div>
25
+ <div class="col-sm-6">
26
+ <div class=form-group>
27
+ <label for="bookly-coupon-deduction"><?php _e( 'Deduction', 'bookly' ) ?></label>
28
+ <input type="text" id="bookly-coupon-deduction" class="form-control" name="deduction" />
29
+ </div>
30
+ </div>
31
+ <div class="col-sm-12">
32
+ <div class=form-group>
33
+ <label for="bookly-coupon-usage-limit"><?php _e( 'Usage limit', 'bookly' ) ?></label>
34
+ <input type="number" id="bookly-coupon-usage-limit" class="form-control" name="usage_limit" min="0" step="1" />
35
+ </div>
36
+ </div>
37
+ <div class="col-sm-12">
38
+ <div class="btn-group">
39
+ <button class="btn btn-default dropdown-toggle bookly-flexbox" data-toggle="dropdown">
40
+ <div class="bookly-flex-cell"><i class="glyphicon glyphicon-tag bookly-margin-right-md"></i></div>
41
+ <div class="bookly-flex-cell text-left"><span id="bookly-entity-counter"></span></div>
42
+ <div class="bookly-flex-cell">
43
+ <div class="bookly-margin-left-md"><span class="caret"></span></div>
44
+ </div>
45
+ </button>
46
+ <ul class="dropdown-menu bookly-entity-selector">
47
+ <li>
48
+ <a class="checkbox" href="javascript:void(0)">
49
+ <label><input type="checkbox" id="bookly-check-all-entities"/><?php _e( 'All Services', 'bookly' ) ?></label>
50
+ </a>
51
+ </li>
52
+ </ul>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ <div class="modal-footer">
58
+ <?php \BooklyLite\Lib\Utils\Common::submitButton( 'bookly-coupon-save' ) ?>
59
+ <button class="btn btn-lg btn-default" data-dismiss="modal">
60
+ <?php _e( 'Cancel', 'bookly' ) ?>
61
+ </button>
62
+ </div>
63
+ </form>
64
+ </div>
65
+ </div>
66
+ </div>
backend/modules/coupons/templates/index.php CHANGED
@@ -1,45 +1,44 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div id="bookly-tbs" class="wrap">
3
- <div class="bookly-tbs-body">
4
- <div class="page-header text-right clearfix">
5
- <div class="bookly-page-title">
6
- <?php _e( 'Coupons', 'bookly' ) ?>
7
- </div>
8
- </div>
9
- <div class="panel panel-default bookly-main">
10
- <div class="panel-body">
11
- <div class="form-inline bookly-margin-bottom-lg text-right">
12
- <div class="form-group">
13
- <button type="button"
14
- id="bookly-add"
15
- class="btn btn-success">
16
- <i class="glyphicon glyphicon-plus"></i> <?php _e( 'Add Coupon', 'bookly' ) ?>
17
- </button>
18
- </div>
19
- </div>
20
-
21
- <table class="table table-striped"
22
- id="bookly-coupons-list"
23
- width="100%">
24
- <thead>
25
- <tr>
26
- <th><?php _e( 'Code', 'bookly' ) ?></th>
27
- <th><?php _e( 'Discount (%)', 'bookly' ) ?></th>
28
- <th><?php _e( 'Deduction', 'bookly' ) ?></th>
29
- <th><?php _e( 'Services', 'bookly' ) ?></th>
30
- <th><?php _e( 'Usage limit', 'bookly' ) ?></th>
31
- <th><?php _e( 'Number of times used', 'bookly' ) ?></th>
32
- <th></th>
33
- <th width="16"><input type="checkbox" id="bookly-check-all"></th>
34
- </tr>
35
- </thead>
36
- </table>
37
-
38
- <div class="text-right bookly-margin-top-lg">
39
- <?php \BooklyLite\Lib\Utils\Common::deleteButton() ?>
40
- </div>
41
- </div>
42
- </div>
43
- </div>
44
- <?php include '_modal.php' ?>
45
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div id="bookly-tbs" class="wrap">
3
+ <div class="bookly-tbs-body">
4
+ <div class="page-header text-right clearfix">
5
+ <div class="bookly-page-title">
6
+ <?php _e( 'Coupons', 'bookly' ) ?>
7
+ </div>
8
+ <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
9
+ </div>
10
+ <div class="panel panel-default bookly-main">
11
+ <div class="panel-body">
12
+ <div class="form-inline bookly-margin-bottom-lg text-right">
13
+ <div class="form-group">
14
+ <button type="button"
15
+ id="bookly-add"
16
+ class="btn btn-success">
17
+ <i class="glyphicon glyphicon-plus"></i> <?php _e( 'Add Coupon', 'bookly' ) ?>
18
+ </button>
19
+ </div>
20
+ </div>
21
+
22
+ <table class="table table-striped" id="bookly-coupons-list" width="100%">
23
+ <thead>
24
+ <tr>
25
+ <th><?php _e( 'Code', 'bookly' ) ?></th>
26
+ <th><?php _e( 'Discount (%)', 'bookly' ) ?></th>
27
+ <th><?php _e( 'Deduction', 'bookly' ) ?></th>
28
+ <th><?php _e( 'Services', 'bookly' ) ?></th>
29
+ <th><?php _e( 'Usage limit', 'bookly' ) ?></th>
30
+ <th><?php _e( 'Number of times used', 'bookly' ) ?></th>
31
+ <th></th>
32
+ <th width="16"><input type="checkbox" id="bookly-check-all"></th>
33
+ </tr>
34
+ </thead>
35
+ </table>
36
+
37
+ <div class="text-right bookly-margin-top-lg">
38
+ <?php \BooklyLite\Lib\Utils\Common::deleteButton() ?>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ <?php include '_modal.php' ?>
 
44
  </div>
backend/modules/custom_fields/Controller.php CHANGED
@@ -1,88 +1,90 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\CustomFields;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\CustomFields
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- /**
13
- * Default Action
14
- */
15
- public function index()
16
- {
17
- $this->enqueueStyles( array(
18
- 'frontend' => array( 'css/ladda.min.css' ),
19
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
20
- ) );
21
-
22
- $this->enqueueScripts( array(
23
- 'module' => array( 'js/custom_fields.js' => array( 'jquery-ui-sortable' ) ),
24
- 'frontend' => array(
25
- 'js/spin.min.js' => array( 'jquery' ),
26
- 'js/ladda.min.js' => array( 'jquery' ),
27
- ),
28
- 'backend' => array(
29
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
30
- 'js/alert.js' => array( 'jquery' ),
31
- ),
32
- ) );
33
-
34
- wp_localize_script( 'ab-custom_fields.js', 'BooklyL10n', array(
35
- 'custom_fields' => get_option( 'ab_custom_fields' ),
36
- 'saved' => __( 'Settings saved.', 'bookly' ),
37
- 'selector' => array(
38
- 'all_selected' => __( 'All services', 'bookly' ),
39
- 'nothing_selected' => __( 'No service selected', 'bookly' ),
40
- ),
41
- 'limitations' => __( '<b class="h4">This function is disabled in the Lite version of Bookly.</b><br><br>If you find the plugin useful for your business please consider buying a licence for the full version.<br>It costs just $59 and for this money you will get many useful functions, lifetime free updates and excellent support!<br>More information can be found here', 'bookly' ) . ': <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
42
- ) );
43
-
44
- $services = $this->render( '_services', array( 'services' => Lib\Entities\Service::query()->select( 'id, title' )->where( 'type', Lib\Entities\Service::TYPE_SIMPLE )->fetchArray() ), false );
45
- $this->render( 'index', array( 'services_html' => $services ) );
46
- }
47
-
48
- /**
49
- * Save custom fields.
50
- */
51
- public function executeSaveCustomFields()
52
- {
53
- $custom_fields = $this->getParameter( 'fields' );
54
- foreach ( json_decode( $custom_fields ) as $custom_field ) {
55
- switch ( $custom_field->type ) {
56
- case 'textarea':
57
- case 'text-content':
58
- case 'text-field':
59
- case 'captcha':
60
- do_action( 'wpml_register_single_string', 'bookly', 'custom_field_' . $custom_field->id . '_' .sanitize_title( $custom_field->label ), $custom_field->label );
61
- break;
62
- case 'checkboxes':
63
- case 'radio-buttons':
64
- case 'drop-down':
65
- do_action( 'wpml_register_single_string', 'bookly', 'custom_field_' . $custom_field->id . '_' . sanitize_title( $custom_field->label ), $custom_field->label );
66
- foreach ( $custom_field->items as $label ) {
67
- do_action( 'wpml_register_single_string', 'bookly', 'custom_field_' . $custom_field->id . '_' . sanitize_title( $custom_field->label ) . '=' . sanitize_title( $label ), $label );
68
- }
69
- break;
70
- }
71
- }
72
- update_option( 'ab_custom_fields', $custom_fields );
73
- update_option( 'ab_custom_fields_per_service', '0' );
74
- wp_send_json_success();
75
- }
76
-
77
- /**
78
- * Override parent method to add 'wp_ajax_ab_' prefix
79
- * so current 'execute*' methods look nicer.
80
- *
81
- * @param string $prefix
82
- */
83
- protected function registerWpActions( $prefix = '' )
84
- {
85
- parent::registerWpActions( 'wp_ajax_ab_' );
86
- }
87
-
 
 
88
  }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\CustomFields;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Controller
8
+ * @package BooklyLite\Backend\Modules\CustomFields
9
+ */
10
+ class Controller extends Lib\Base\Controller
11
+ {
12
+ const page_slug = 'bookly-custom-fields';
13
+
14
+ /**
15
+ * Default Action
16
+ */
17
+ public function index()
18
+ {
19
+ $this->enqueueStyles( array(
20
+ 'frontend' => array( 'css/ladda.min.css' ),
21
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
22
+ ) );
23
+
24
+ $this->enqueueScripts( array(
25
+ 'module' => array( 'js/custom_fields.js' => array( 'jquery-ui-sortable' ) ),
26
+ 'frontend' => array(
27
+ 'js/spin.min.js' => array( 'jquery' ),
28
+ 'js/ladda.min.js' => array( 'jquery' ),
29
+ ),
30
+ 'backend' => array(
31
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
32
+ 'js/alert.js' => array( 'jquery' ),
33
+ ),
34
+ ) );
35
+
36
+ wp_localize_script( 'bookly-custom_fields.js', 'BooklyL10n', array(
37
+ 'custom_fields' => get_option( 'bookly_custom_fields' ),
38
+ 'saved' => __( 'Settings saved.', 'bookly' ),
39
+ 'selector' => array(
40
+ 'all_selected' => __( 'All services', 'bookly' ),
41
+ 'nothing_selected' => __( 'No service selected', 'bookly' ),
42
+ ),
43
+ 'limitations' => __( '<b class="h4">This function is disabled in the Lite version of Bookly.</b><br><br>If you find the plugin useful for your business please consider buying a licence for the full version.<br>It costs just $59 and for this money you will get many useful functions, lifetime free updates and excellent support!<br>More information can be found here', 'bookly' ) . ': <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
44
+ ) );
45
+
46
+ $services = $this->render( '_services', array( 'services' => Lib\Entities\Service::query()->select( 'id, title' )->where( 'type', Lib\Entities\Service::TYPE_SIMPLE )->fetchArray() ), false );
47
+ $this->render( 'index', array( 'services_html' => $services ) );
48
+ }
49
+
50
+ /**
51
+ * Save custom fields.
52
+ */
53
+ public function executeSaveCustomFields()
54
+ {
55
+ $custom_fields = $this->getParameter( 'fields' );
56
+ foreach ( json_decode( $custom_fields ) as $custom_field ) {
57
+ switch ( $custom_field->type ) {
58
+ case 'textarea':
59
+ case 'text-content':
60
+ case 'text-field':
61
+ case 'captcha':
62
+ do_action( 'wpml_register_single_string', 'bookly', 'custom_field_' . $custom_field->id . '_' .sanitize_title( $custom_field->label ), $custom_field->label );
63
+ break;
64
+ case 'checkboxes':
65
+ case 'radio-buttons':
66
+ case 'drop-down':
67
+ do_action( 'wpml_register_single_string', 'bookly', 'custom_field_' . $custom_field->id . '_' . sanitize_title( $custom_field->label ), $custom_field->label );
68
+ foreach ( $custom_field->items as $label ) {
69
+ do_action( 'wpml_register_single_string', 'bookly', 'custom_field_' . $custom_field->id . '_' . sanitize_title( $custom_field->label ) . '=' . sanitize_title( $label ), $label );
70
+ }
71
+ break;
72
+ }
73
+ }
74
+ update_option( 'bookly_custom_fields', $custom_fields );
75
+ update_option( 'bookly_custom_fields_per_service', '0' );
76
+ wp_send_json_success();
77
+ }
78
+
79
+ /**
80
+ * Override parent method to add 'wp_ajax_bookly_' prefix
81
+ * so current 'execute*' methods look nicer.
82
+ *
83
+ * @param string $prefix
84
+ */
85
+ protected function registerWpActions( $prefix = '' )
86
+ {
87
+ parent::registerWpActions( 'wp_ajax_bookly_' );
88
+ }
89
+
90
  }
backend/modules/custom_fields/resources/js/custom_fields.js CHANGED
@@ -1,7 +1,7 @@
1
  jQuery(function($) {
2
 
3
  var $fields = $("#ab-custom-fields"),
4
- $cf_per_service = $('#ab_custom_fields_per_service');
5
 
6
  $fields.sortable({
7
  axis : 'y',
@@ -81,7 +81,7 @@ jQuery(function($) {
81
  type : 'POST',
82
  url : ajaxurl,
83
  xhrFields : { withCredentials: true },
84
- data : { action: 'ab_save_custom_fields', fields: JSON.stringify(data), cf_per_service: $cf_per_service.val() },
85
  complete : function() {
86
  ladda.stop();
87
  booklyAlert({success : [BooklyL10n.saved]});
1
  jQuery(function($) {
2
 
3
  var $fields = $("#ab-custom-fields"),
4
+ $cf_per_service = $('#bookly_custom_fields_per_service');
5
 
6
  $fields.sortable({
7
  axis : 'y',
81
  type : 'POST',
82
  url : ajaxurl,
83
  xhrFields : { withCredentials: true },
84
+ data : { action: 'bookly_save_custom_fields', fields: JSON.stringify(data), cf_per_service: $cf_per_service.val() },
85
  complete : function() {
86
  ladda.stop();
87
  booklyAlert({success : [BooklyL10n.saved]});
backend/modules/custom_fields/templates/_services.php CHANGED
@@ -1,32 +1,32 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="btn-group ab--services-holder bookly-margin-top-screenxs-sm" style="display: none;">
3
- <button class="btn btn-default btn-block dropdown-toggle bookly-flexbox" data-toggle="dropdown">
4
- <div class="bookly-flex-cell">
5
- <i class="glyphicon glyphicon-tag bookly-margin-right-md"></i>
6
- </div>
7
- <div class="bookly-flex-cell text-left" style="width: 100%">
8
- <span class="ab-count"></span>
9
- </div>
10
- <div class="bookly-flex-cell"><div class="bookly-margin-left-md"><span class="caret"></span></div></div>
11
- </button>
12
- <ul class="dropdown-menu bookly-entity-selector">
13
- <li>
14
- <a class="checkbox" href="javascript:void(0)">
15
- <label>
16
- <input type="checkbox" class="bookly-check-all-entities">
17
- <?php _e( 'All services', 'bookly' ) ?>
18
- </label>
19
- </a>
20
- </li>
21
- <?php foreach ( $services as $service ) : ?>
22
- <li>
23
- <a class="checkbox" href="javascript:void(0)">
24
- <label>
25
- <input type="checkbox" class="bookly-js-check-entity" value="<?php echo $service['id'] ?>" data-title="<?php echo esc_attr( $service['title'] ) ?>">
26
- <?php echo $service['title'] ?>
27
- </label>
28
- </a>
29
- </li>
30
- <?php endforeach ?>
31
- </ul>
32
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div class="btn-group ab--services-holder bookly-margin-top-screenxs-sm" style="display: none;">
3
+ <button class="btn btn-default btn-block dropdown-toggle bookly-flexbox" data-toggle="dropdown">
4
+ <div class="bookly-flex-cell">
5
+ <i class="glyphicon glyphicon-tag bookly-margin-right-md"></i>
6
+ </div>
7
+ <div class="bookly-flex-cell text-left" style="width: 100%">
8
+ <span class="ab-count"></span>
9
+ </div>
10
+ <div class="bookly-flex-cell"><div class="bookly-margin-left-md"><span class="caret"></span></div></div>
11
+ </button>
12
+ <ul class="dropdown-menu bookly-entity-selector">
13
+ <li>
14
+ <a class="checkbox" href="javascript:void(0)">
15
+ <label>
16
+ <input type="checkbox" class="bookly-check-all-entities">
17
+ <?php _e( 'All services', 'bookly' ) ?>
18
+ </label>
19
+ </a>
20
+ </li>
21
+ <?php foreach ( $services as $service ) : ?>
22
+ <li>
23
+ <a class="checkbox" href="javascript:void(0)">
24
+ <label>
25
+ <input type="checkbox" class="bookly-js-check-entity" value="<?php echo $service['id'] ?>" data-title="<?php echo esc_attr( $service['title'] ) ?>">
26
+ <?php echo $service['title'] ?>
27
+ </label>
28
+ </a>
29
+ </li>
30
+ <?php endforeach ?>
31
+ </ul>
32
  </div>
backend/modules/custom_fields/templates/index.php CHANGED
@@ -5,18 +5,13 @@
5
  <div class="bookly-page-title">
6
  <?php _e( 'Custom Fields', 'bookly' ) ?>
7
  </div>
 
8
  </div>
9
  <div class="panel panel-default bookly-main">
10
  <div class="panel-body">
11
  <div class="row">
12
  <div class="col-md-4">
13
- <div class="form-group">
14
- <label for="ab_custom_fields_per_service">
15
- <?php _e( 'Bind fields to services', 'bookly' ) ?>
16
- </label>
17
- <p class="help-block"><?php _e( 'When this setting is enabled you will be able to create service specific custom fields.', 'bookly' ) ?></p>
18
- <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'ab_custom_fields_per_service' ) ?>
19
- </div>
20
  </div>
21
  </div>
22
 
5
  <div class="bookly-page-title">
6
  <?php _e( 'Custom Fields', 'bookly' ) ?>
7
  </div>
8
+ <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
9
  </div>
10
  <div class="panel panel-default bookly-main">
11
  <div class="panel-body">
12
  <div class="row">
13
  <div class="col-md-4">
14
+ <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'bookly_custom_fields_per_service', __( 'Bind fields to services', 'bookly' ), __( 'When this setting is enabled you will be able to create service specific custom fields.', 'bookly' ) ) ?>
 
 
 
 
 
 
15
  </div>
16
  </div>
17
 
backend/modules/customers/Components.php CHANGED
@@ -16,25 +16,25 @@ class Components extends Lib\Base\Components
16
  public function renderCustomerDialog()
17
  {
18
  $this->enqueueStyles( array(
19
- 'frontend' => get_option( 'ab_settings_phone_default_country' ) == 'disabled'
20
  ? array()
21
  : array( 'css/intlTelInput.css' ),
22
  ) );
23
 
24
  $this->enqueueScripts( array(
25
  'backend' => array( 'js/angular.min.js' => array( 'jquery' ), ),
26
- 'frontend' => get_option( 'ab_settings_phone_default_country' ) == 'disabled'
27
  ? array()
28
  : array( 'js/intlTelInput.min.js' => array( 'jquery' ) ),
29
- 'module' => array( 'js/ng-customer_dialog.js' => array( 'ab-angular.min.js' ), )
30
  ) );
31
 
32
- wp_localize_script( 'ab-ng-customer_dialog.js', 'BooklyL10nCustDialog', array(
33
- 'default_status' => get_option( 'ab_settings_default_appointment_status' ),
34
  'intlTelInput' => array(
35
- 'enabled' => ( get_option( 'ab_settings_phone_default_country' ) != 'disabled' ),
36
  'utils' => plugins_url( 'intlTelInput.utils.js', Lib\Plugin::getDirectory() . '/frontend/resources/js/intlTelInput.utils.js' ),
37
- 'country' => get_option( 'ab_settings_phone_default_country' ),
38
  ),
39
  ) );
40
 
16
  public function renderCustomerDialog()
17
  {
18
  $this->enqueueStyles( array(
19
+ 'frontend' => get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
20
  ? array()
21
  : array( 'css/intlTelInput.css' ),
22
  ) );
23
 
24
  $this->enqueueScripts( array(
25
  'backend' => array( 'js/angular.min.js' => array( 'jquery' ), ),
26
+ 'frontend' => get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
27
  ? array()
28
  : array( 'js/intlTelInput.min.js' => array( 'jquery' ) ),
29
+ 'module' => array( 'js/ng-customer_dialog.js' => array( 'bookly-angular.min.js' ), )
30
  ) );
31
 
32
+ wp_localize_script( 'bookly-ng-customer_dialog.js', 'BooklyL10nCustDialog', array(
33
+ 'default_status' => get_option( 'bookly_gen_default_appointment_status' ),
34
  'intlTelInput' => array(
35
+ 'enabled' => get_option( 'bookly_cst_phone_default_country' ) != 'disabled',
36
  'utils' => plugins_url( 'intlTelInput.utils.js', Lib\Plugin::getDirectory() . '/frontend/resources/js/intlTelInput.utils.js' ),
37
+ 'country' => get_option( 'bookly_cst_phone_default_country' ),
38
  ),
39
  ) );
40
 
backend/modules/customers/Controller.php CHANGED
@@ -1,235 +1,235 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Customers;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Customers
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- const page_slug = 'ab-customers';
13
-
14
- protected function getPermissions()
15
- {
16
- return array(
17
- 'executeSaveCustomer' => 'user',
18
- );
19
- }
20
-
21
- public function index()
22
- {
23
- if ( $this->hasParameter( 'import-customers' ) ) {
24
- $this->importCustomers();
25
- }
26
-
27
- $this->enqueueStyles( array(
28
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
29
- 'frontend' => array( 'css/ladda.min.css', ),
30
- ) );
31
-
32
- $this->enqueueScripts( array(
33
- 'backend' => array(
34
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
35
- 'js/datatables.min.js' => array( 'jquery' ),
36
- 'js/alert.js' => array( 'jquery' ),
37
- ),
38
- 'frontend' => array(
39
- 'js/spin.min.js' => array( 'jquery' ),
40
- 'js/ladda.min.js' => array( 'jquery' ),
41
- ),
42
- 'module' => array(
43
- 'js/customers.js' => array( 'ab-datatables.min.js', 'ab-ng-customer_dialog.js' ),
44
- ),
45
- ) );
46
-
47
- wp_localize_script( 'ab-customers.js', 'BooklyL10n', array(
48
- 'edit' => __( 'Edit', 'bookly' ),
49
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
50
- 'wp_users' => get_users( array( 'fields' => array( 'ID', 'display_name' ), 'orderby' => 'display_name' ) ),
51
- 'zeroRecords' => __( 'No customers found.', 'bookly' ),
52
- 'processing' => __( 'Processing...', 'bookly' ),
53
- 'edit_customer' => __( 'Edit customer', 'bookly' ),
54
- 'new_customer' => __( 'New customer', 'bookly' ),
55
- 'create_customer' => __( 'Create customer', 'bookly' ),
56
- 'save' => __( 'Save', 'bookly' ),
57
- 'search' => __( 'Quick search customer', 'bookly' ),
58
- 'limitations' => __( '<b class="h4">This function is disabled in the Lite version of Bookly.</b><br><br>If you find the plugin useful for your business please consider buying a licence for the full version.<br>It costs just $59 and for this money you will get many useful functions, lifetime free updates and excellent support!<br>More information can be found here', 'bookly' ) . ': <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
59
- ) );
60
-
61
- $this->render( 'index' );
62
- }
63
-
64
- /**
65
- * Get list of customers.
66
- */
67
- public function executeGetCustomers()
68
- {
69
- global $wpdb;
70
-
71
- $columns = $this->getParameter( 'columns' );
72
- $order = $this->getParameter( 'order' );
73
- $filter = $this->getParameter( 'filter' );
74
-
75
- $query = Lib\Entities\Customer::query( 'c' );
76
-
77
- $total = $query->count();
78
-
79
- $query
80
- ->select( 'SQL_CALC_FOUND_ROWS c.*, MAX(a.start_date) AS last_appointment,
81
- COUNT(DISTINCT a.id) AS total_appointments,
82
- SUM(p.total)*COUNT(DISTINCT p.id)/COUNT(*) AS payments,
83
- wpu.display_name AS wp_user' )
84
- ->leftJoin( 'CustomerAppointment', 'ca', 'ca.customer_id = c.id' )
85
- ->leftJoin( 'Appointment', 'a', 'a.id = ca.appointment_id' )
86
- ->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
87
- ->tableJoin( $wpdb->users, 'wpu', 'wpu.ID = c.wp_user_id' )
88
- ->groupBy( 'c.id' );
89
-
90
- if ( $filter != '' ) {
91
- $search_value = Lib\Query::escape( $filter );
92
- $query
93
- ->whereLike( 'c.name', "%{$search_value}%" )
94
- ->whereLike( 'c.phone', "%{$search_value}%", 'OR' )
95
- ->whereLike( 'c.email', "%{$search_value}%", 'OR' );
96
- }
97
-
98
- foreach ( $order as $sort_by ) {
99
- $query->sortBy( str_replace( '.', '_', $columns[ $sort_by['column'] ]['data'] ) )
100
- ->order( $sort_by['dir'] == 'desc' ? Lib\Query::ORDER_DESCENDING : Lib\Query::ORDER_ASCENDING );
101
- }
102
-
103
- $query->limit( $this->getParameter( 'length' ) )->offset( $this->getParameter( 'start' ) );
104
-
105
- $data = array();
106
- foreach ( $query->fetchArray() as $row ) {
107
- $data[] = array(
108
- 'id' => $row['id'],
109
- 'name' => $row['name'],
110
- 'wp_user' => $row['wp_user'],
111
- 'wp_user_id' => $row['wp_user_id'],
112
- 'phone' => $row['phone'],
113
- 'email' => $row['email'],
114
- 'notes' => $row['notes'],
115
- 'last_appointment' => $row['last_appointment'] ? Lib\Utils\DateTime::formatDateTime( $row['last_appointment'] ) : '',
116
- 'total_appointments' => $row['total_appointments'],
117
- 'payments' => Lib\Utils\Common::formatPrice( $row['payments'] ),
118
- );
119
- }
120
-
121
- wp_send_json( array(
122
- 'draw' => ( int ) $this->getParameter( 'draw' ),
123
- 'recordsTotal' => $total,
124
- 'recordsFiltered' => ( int ) $wpdb->get_var( 'SELECT FOUND_ROWS()' ),
125
- 'data' => $data,
126
- ) );
127
- }
128
-
129
- /**
130
- * Create or edit a customer.
131
- */
132
- public function executeSaveCustomer()
133
- {
134
- $response = array();
135
- $form = new Forms\Customer();
136
-
137
- do {
138
- if ( $this->getParameter( 'name' ) !== '' ) {
139
- $params = $this->getPostParameters();
140
- if ( ! $params['wp_user_id'] ) {
141
- $params['wp_user_id'] = null;
142
- }
143
- $form->bind( $params );
144
- /** @var Lib\Entities\Customer $customer */
145
- $customer = $form->save();
146
- if ( $customer ) {
147
- $response['success'] = true;
148
- $response['customer'] = array(
149
- 'id' => $customer->get( 'id' ),
150
- 'name' => $customer->get( 'name' ),
151
- 'phone' => $customer->get( 'phone' ),
152
- 'email' => $customer->get( 'email' ),
153
- 'notes' => $customer->get( 'notes' ),
154
- 'wp_user_id' => $customer->get( 'wp_user_id' ),
155
- 'jsonString' => json_encode( array(
156
- 'name' => $customer->get( 'name' ),
157
- 'phone' => $customer->get( 'phone' ),
158
- 'email' => $customer->get( 'email' ),
159
- 'notes' => $customer->get( 'notes' )
160
- ) )
161
- );
162
- break;
163
- }
164
- }
165
- $response['success'] = false;
166
- $response['errors'] = array( 'name' => array( 'required' ) );
167
- } while ( 0 );
168
-
169
- wp_send_json( $response );
170
- }
171
-
172
- /**
173
- * Import customers from CSV.
174
- */
175
- private function importCustomers()
176
- {
177
- @ini_set( 'auto_detect_line_endings', true );
178
-
179
- $csv_mime_types = array(
180
- 'text/csv',
181
- 'application/csv',
182
- 'text/comma-separated-values',
183
- 'application/excel',
184
- 'application/vnd.ms-excel',
185
- 'application/vnd.msexcel'
186
- );
187
-
188
- if ( in_array( $_FILES['import_customers_file']['type'], $csv_mime_types ) ) {
189
- $file = fopen( $_FILES['import_customers_file']['tmp_name'], 'r' );
190
- while ( $line = fgetcsv( $file, null, $this->getParameter( 'import_customers_delimiter' ) ) ) {
191
- if ( $line[0] != '' ) {
192
- $customer = new Lib\Entities\Customer();
193
- $customer->set( 'name', $line[0] );
194
- if ( isset( $line[1] ) ) {
195
- $customer->set( 'phone', $line[1] );
196
- }
197
- if ( isset( $line[2] ) ) {
198
- $customer->set( 'email', $line[2] );
199
- }
200
- $customer->save();
201
- }
202
- }
203
- }
204
- }
205
-
206
- /**
207
- * Delete customers.
208
- */
209
- public function executeDeleteCustomers()
210
- {
211
- foreach ( $this->getParameter( 'data', array() ) as $id ) {
212
- $customer = new Lib\Entities\Customer();
213
- $customer->load( $id );
214
- $customer->deleteWithWPUser( (bool) $this->getParameter( 'with_wp_user' ) );
215
- }
216
- wp_send_json_success();
217
- }
218
-
219
- /**
220
- * Export Customers to CSV
221
- */
222
- public function executeExportCustomers() { exit; }
223
-
224
- /**
225
- * Override parent method to add 'wp_ajax_ab_' prefix
226
- * so current 'execute*' methods look nicer.
227
- *
228
- * @param string $prefix
229
- */
230
- protected function registerWpActions( $prefix = '' )
231
- {
232
- parent::registerWpActions( 'wp_ajax_ab_' );
233
- }
234
-
235
  }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Customers;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Controller
8
+ * @package BooklyLite\Backend\Modules\Customers
9
+ */
10
+ class Controller extends Lib\Base\Controller
11
+ {
12
+ const page_slug = 'bookly-customers';
13
+
14
+ protected function getPermissions()
15
+ {
16
+ return array(
17
+ 'executeSaveCustomer' => 'user',
18
+ );
19
+ }
20
+
21
+ public function index()
22
+ {
23
+ if ( $this->hasParameter( 'import-customers' ) ) {
24
+ $this->importCustomers();
25
+ }
26
+
27
+ $this->enqueueStyles( array(
28
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
29
+ 'frontend' => array( 'css/ladda.min.css', ),
30
+ ) );
31
+
32
+ $this->enqueueScripts( array(
33
+ 'backend' => array(
34
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
35
+ 'js/datatables.min.js' => array( 'jquery' ),
36
+ 'js/alert.js' => array( 'jquery' ),
37
+ ),
38
+ 'frontend' => array(
39
+ 'js/spin.min.js' => array( 'jquery' ),
40
+ 'js/ladda.min.js' => array( 'jquery' ),
41
+ ),
42
+ 'module' => array(
43
+ 'js/customers.js' => array( 'bookly-datatables.min.js', 'bookly-ng-customer_dialog.js' ),
44
+ ),
45
+ ) );
46
+
47
+ wp_localize_script( 'bookly-customers.js', 'BooklyL10n', array(
48
+ 'edit' => __( 'Edit', 'bookly' ),
49
+ 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
50
+ 'wp_users' => get_users( array( 'fields' => array( 'ID', 'display_name' ), 'orderby' => 'display_name' ) ),
51
+ 'zeroRecords' => __( 'No customers found.', 'bookly' ),
52
+ 'processing' => __( 'Processing...', 'bookly' ),
53
+ 'edit_customer' => __( 'Edit customer', 'bookly' ),
54
+ 'new_customer' => __( 'New customer', 'bookly' ),
55
+ 'create_customer' => __( 'Create customer', 'bookly' ),
56
+ 'save' => __( 'Save', 'bookly' ),
57
+ 'search' => __( 'Quick search customer', 'bookly' ),
58
+ 'limitations' => __( '<b class="h4">This function is disabled in the Lite version of Bookly.</b><br><br>If you find the plugin useful for your business please consider buying a licence for the full version.<br>It costs just $59 and for this money you will get many useful functions, lifetime free updates and excellent support!<br>More information can be found here', 'bookly' ) . ': <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
59
+ ) );
60
+
61
+ $this->render( 'index' );
62
+ }
63
+
64
+ /**
65
+ * Get list of customers.
66
+ */
67
+ public function executeGetCustomers()
68
+ {
69
+ global $wpdb;
70
+
71
+ $columns = $this->getParameter( 'columns' );
72
+ $order = $this->getParameter( 'order' );
73
+ $filter = $this->getParameter( 'filter' );
74
+
75
+ $query = Lib\Entities\Customer::query( 'c' );
76
+
77
+ $total = $query->count();
78
+
79
+ $query
80
+ ->select( 'SQL_CALC_FOUND_ROWS c.*,
81
+ (
82
+ SELECT MAX(a.start_date) FROM ' . Lib\Entities\Appointment::getTableName() . ' a
83
+ LEFT JOIN ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca ON ca.appointment_id = a.id
84
+ WHERE ca.customer_id = c.id
85
+ ) AS last_appointment,
86
+ (
87
+ SELECT COUNT(DISTINCT ca.appointment_id) FROM ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca
88
+ WHERE ca.customer_id = c.id
89
+ ) AS total_appointments,
90
+ (
91
+ SELECT SUM(p.total) FROM ' . Lib\Entities\Payment::getTableName() . ' p
92
+ WHERE p.id IN (
93
+ SELECT DISTINCT ca.payment_id FROM ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca
94
+ WHERE ca.customer_id = c.id
95
+ )
96
+ ) AS payments,
97
+ wpu.display_name AS wp_user' )
98
+ ->tableJoin( $wpdb->users, 'wpu', 'wpu.ID = c.wp_user_id' )
99
+ ->groupBy( 'c.id' );
100
+
101
+ if ( $filter != '' ) {
102
+ $search_value = Lib\Query::escape( $filter );
103
+ $query
104
+ ->whereLike( 'c.name', "%{$search_value}%" )
105
+ ->whereLike( 'c.phone', "%{$search_value}%", 'OR' )
106
+ ->whereLike( 'c.email', "%{$search_value}%", 'OR' );
107
+ }
108
+
109
+ foreach ( $order as $sort_by ) {
110
+ $query->sortBy( str_replace( '.', '_', $columns[ $sort_by['column'] ]['data'] ) )
111
+ ->order( $sort_by['dir'] == 'desc' ? Lib\Query::ORDER_DESCENDING : Lib\Query::ORDER_ASCENDING );
112
+ }
113
+
114
+ $query->limit( $this->getParameter( 'length' ) )->offset( $this->getParameter( 'start' ) );
115
+
116
+ $data = array();
117
+ foreach ( $query->fetchArray() as $row ) {
118
+ $data[] = array(
119
+ 'id' => $row['id'],
120
+ 'name' => $row['name'],
121
+ 'wp_user' => $row['wp_user'],
122
+ 'wp_user_id' => $row['wp_user_id'],
123
+ 'phone' => $row['phone'],
124
+ 'email' => $row['email'],
125
+ 'notes' => $row['notes'],
126
+ 'last_appointment' => $row['last_appointment'] ? Lib\Utils\DateTime::formatDateTime( $row['last_appointment'] ) : '',
127
+ 'total_appointments' => $row['total_appointments'],
128
+ 'payments' => Lib\Utils\Common::formatPrice( $row['payments'] ),
129
+ );
130
+ }
131
+
132
+ wp_send_json( array(
133
+ 'draw' => ( int ) $this->getParameter( 'draw' ),
134
+ 'recordsTotal' => $total,
135
+ 'recordsFiltered' => ( int ) $wpdb->get_var( 'SELECT FOUND_ROWS()' ),
136
+ 'data' => $data,
137
+ ) );
138
+ }
139
+
140
+ /**
141
+ * Create or edit a customer.
142
+ */
143
+ public function executeSaveCustomer()
144
+ {
145
+ $response = array();
146
+ $form = new Forms\Customer();
147
+
148
+ do {
149
+ if ( $this->getParameter( 'name' ) !== '' ) {
150
+ $params = $this->getPostParameters();
151
+ if ( ! $params['wp_user_id'] ) {
152
+ $params['wp_user_id'] = null;
153
+ }
154
+ $form->bind( $params );
155
+ /** @var Lib\Entities\Customer $customer */
156
+ $customer = $form->save();
157
+ if ( $customer ) {
158
+ $response['success'] = true;
159
+ $response['customer'] = array(
160
+ 'id' => $customer->get( 'id' ),
161
+ 'name' => $customer->get( 'name' ),
162
+ 'phone' => $customer->get( 'phone' ),
163
+ 'email' => $customer->get( 'email' ),
164
+ 'notes' => $customer->get( 'notes' ),
165
+ 'wp_user_id' => $customer->get( 'wp_user_id' ),
166
+ 'jsonString' => json_encode( array(
167
+ 'name' => $customer->get( 'name' ),
168
+ 'phone' => $customer->get( 'phone' ),
169
+ 'email' => $customer->get( 'email' ),
170
+ 'notes' => $customer->get( 'notes' )
171
+ ) )
172
+ );
173
+ break;
174
+ }
175
+ }
176
+ $response['success'] = false;
177
+ $response['errors'] = array( 'name' => array( 'required' ) );
178
+ } while ( 0 );
179
+
180
+ wp_send_json( $response );
181
+ }
182
+
183
+ /**
184
+ * Import customers from CSV.
185
+ */
186
+ private function importCustomers()
187
+ {
188
+ @ini_set( 'auto_detect_line_endings', true );
189
+
190
+ $file = fopen( $_FILES['import_customers_file']['tmp_name'], 'r' );
191
+ while ( $line = fgetcsv( $file, null, $this->getParameter( 'import_customers_delimiter' ) ) ) {
192
+ if ( $line[0] != '' ) {
193
+ $customer = new Lib\Entities\Customer();
194
+ $customer->set( 'name', $line[0] );
195
+ if ( isset( $line[1] ) ) {
196
+ $customer->set( 'phone', $line[1] );
197
+ }
198
+ if ( isset( $line[2] ) ) {
199
+ $customer->set( 'email', $line[2] );
200
+ }
201
+ $customer->save();
202
+ }
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Delete customers.
208
+ */
209
+ public function executeDeleteCustomers()
210
+ {
211
+ foreach ( $this->getParameter( 'data', array() ) as $id ) {
212
+ $customer = new Lib\Entities\Customer();
213
+ $customer->load( $id );
214
+ $customer->deleteWithWPUser( (bool) $this->getParameter( 'with_wp_user' ) );
215
+ }
216
+ wp_send_json_success();
217
+ }
218
+
219
+ /**
220
+ * Export Customers to CSV
221
+ */
222
+ public function executeExportCustomers() { exit; }
223
+
224
+ /**
225
+ * Override parent method to add 'wp_ajax_bookly_' prefix
226
+ * so current 'execute*' methods look nicer.
227
+ *
228
+ * @param string $prefix
229
+ */
230
+ protected function registerWpActions( $prefix = '' )
231
+ {
232
+ parent::registerWpActions( 'wp_ajax_bookly_' );
233
+ }
234
+
235
  }
backend/modules/customers/forms/Customer.php CHANGED
@@ -1,25 +1,25 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Customers\Forms;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Customer
8
- * @package BooklyLite\Backend\Modules\Customers\Forms
9
- */
10
- class Customer extends Lib\Base\Form
11
- {
12
- protected static $entity_class = 'Customer';
13
-
14
- public function configure()
15
- {
16
- $this->setFields( array(
17
- 'name',
18
- 'wp_user_id',
19
- 'phone',
20
- 'email',
21
- 'notes'
22
- ) );
23
- }
24
-
25
- }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Customers\Forms;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Customer
8
+ * @package BooklyLite\Backend\Modules\Customers\Forms
9
+ */
10
+ class Customer extends Lib\Base\Form
11
+ {
12
+ protected static $entity_class = 'Customer';
13
+
14
+ public function configure()
15
+ {
16
+ $this->setFields( array(
17
+ 'name',
18
+ 'wp_user_id',
19
+ 'phone',
20
+ 'email',
21
+ 'notes'
22
+ ) );
23
+ }
24
+
25
+ }
backend/modules/customers/resources/js/customers.js CHANGED
@@ -32,17 +32,17 @@ jQuery(function($) {
32
  url: ajaxurl,
33
  data: function (d) {
34
  return $.extend({}, d, {
35
- action: 'ab_get_customers',
36
  filter: $filter.val()
37
  });
38
  }
39
  },
40
  columns: [
41
- { data: 'name' },
42
- { data: 'wp_user' },
43
- { data: 'phone' },
44
- { data: 'email' },
45
- { data: 'notes' },
46
  { data: 'last_appointment' },
47
  { data: 'total_appointments' },
48
  { data: 'payments' },
@@ -177,7 +177,7 @@ jQuery(function($) {
177
  url : ajaxurl,
178
  type : 'POST',
179
  data : {
180
- action : 'ab_delete_customers',
181
  data : data,
182
  with_wp_user : with_wp_user
183
  },
32
  url: ajaxurl,
33
  data: function (d) {
34
  return $.extend({}, d, {
35
+ action: 'bookly_get_customers',
36
  filter: $filter.val()
37
  });
38
  }
39
  },
40
  columns: [
41
+ { data: 'name', render: $.fn.dataTable.render.text() },
42
+ { data: 'wp_user', render: $.fn.dataTable.render.text() },
43
+ { data: 'phone', render: $.fn.dataTable.render.text() },
44
+ { data: 'email', render: $.fn.dataTable.render.text() },
45
+ { data: 'notes', render: $.fn.dataTable.render.text() },
46
  { data: 'last_appointment' },
47
  { data: 'total_appointments' },
48
  { data: 'payments' },
177
  url : ajaxurl,
178
  type : 'POST',
179
  data : {
180
+ action : 'bookly_delete_customers',
181
  data : data,
182
  with_wp_user : with_wp_user
183
  },
backend/modules/customers/resources/js/ng-customer_dialog.js CHANGED
@@ -1,122 +1,122 @@
1
- ;(function() {
2
-
3
- angular.module('customerDialog', []).directive('customerDialog', function() {
4
- return {
5
- restrict : 'A',
6
- replace : true,
7
- scope : {
8
- callback : '&customerDialog',
9
- form : '=customer'
10
- },
11
- templateUrl : 'bookly-customer-dialog.tpl',
12
- // The linking function will add behavior to the template.
13
- link: function(scope, element, attrs) {
14
- // Init properties.
15
- var init = function() {
16
- // Form fields.
17
- if (!scope.form) {
18
- scope.form = {
19
- id : '',
20
- wp_user_id : '',
21
- name : '',
22
- phone : '',
23
- email : '',
24
- notes : ''
25
- };
26
- }
27
- if (BooklyL10nCustDialog.intlTelInput.enabled) {
28
- element.find('#phone').intlTelInput({
29
- preferredCountries: [BooklyL10nCustDialog.intlTelInput.country],
30
- defaultCountry: BooklyL10nCustDialog.intlTelInput.country,
31
- geoIpLookup: function (callback) {
32
- jQuery.get(ajaxurl, {action: 'ab_ip_info'}, function () {
33
- }, 'json').always(function (resp) {
34
- var countryCode = (resp && resp.country) ? resp.country : '';
35
- callback(countryCode);
36
- });
37
- },
38
- utilsScript: BooklyL10nCustDialog.intlTelInput.utils
39
- });
40
- }
41
- // Form errors.
42
- scope.errors = {
43
- name: {required: false}
44
- };
45
- // Loading indicator.
46
- scope.loading = false;
47
-
48
- jQuery('#bookly-customer-dialog').modal('hide')
49
- .on('hidden.bs.modal', function () {
50
- if (jQuery('#bookly-appointment-dialog').length) {
51
- jQuery('body').addClass('modal-open');
52
- }
53
- });
54
- };
55
-
56
- // Run init.
57
- init();
58
-
59
- // On 'Cancel' button click.
60
- scope.closeDialog = function () {
61
- // Close the dialog.
62
- jQuery('#bookly-customer-dialog').modal('hide');
63
- // Re-init all properties.
64
- init();
65
- };
66
-
67
- /**
68
- * Send form to server.
69
- */
70
- scope.processForm = function() {
71
- scope.errors = {};
72
- scope.loading = true;
73
- scope.form.phone = BooklyL10nCustDialog.intlTelInput.enabled
74
- ? element.find('#phone').intlTelInput('getNumber')
75
- : element.find('#phone').val();
76
- jQuery.ajax({
77
- url : ajaxurl,
78
- type : 'POST',
79
- data : jQuery.extend({ action : 'ab_save_customer' }, scope.form),
80
- dataType : 'json',
81
- success : function ( response ) {
82
- scope.$apply(function(scope) {
83
- if (response.success) {
84
- response.customer.custom_fields = [];
85
- response.customer.extras = [];
86
- response.customer.status = BooklyL10nCustDialog.default_status;
87
- // Send new customer to the parent scope.
88
- scope.callback({customer : response.customer});
89
- scope.form = {
90
- id : '',
91
- wp_user_id : '',
92
- name : '',
93
- phone : '',
94
- email : '',
95
- notes : ''
96
- };
97
- // Close the dialog.
98
- scope.closeDialog();
99
- } else {
100
- // Set errors.
101
- jQuery.each(response.errors, function(field, errors) {
102
- scope.errors[field] = {};
103
- jQuery.each(errors, function(key, error) {
104
- scope.errors[field][error] = true;
105
- });
106
- });
107
- }
108
- scope.loading = false;
109
- });
110
- },
111
- error : function() {
112
- scope.$apply(function(scope) {
113
- scope.loading = false;
114
- });
115
- }
116
- });
117
- };
118
- }
119
- };
120
- });
121
-
122
  })();
1
+ ;(function() {
2
+
3
+ angular.module('customerDialog', []).directive('customerDialog', function() {
4
+ return {
5
+ restrict : 'A',
6
+ replace : true,
7
+ scope : {
8
+ callback : '&customerDialog',
9
+ form : '=customer'
10
+ },
11
+ templateUrl : 'bookly-customer-dialog.tpl',
12
+ // The linking function will add behavior to the template.
13
+ link: function(scope, element, attrs) {
14
+ // Init properties.
15
+ var init = function() {
16
+ // Form fields.
17
+ if (!scope.form) {
18
+ scope.form = {
19
+ id : '',
20
+ wp_user_id : '',
21
+ name : '',
22
+ phone : '',
23
+ email : '',
24
+ notes : ''
25
+ };
26
+ }
27
+ if (BooklyL10nCustDialog.intlTelInput.enabled) {
28
+ element.find('#phone').intlTelInput({
29
+ preferredCountries: [BooklyL10nCustDialog.intlTelInput.country],
30
+ defaultCountry: BooklyL10nCustDialog.intlTelInput.country,
31
+ geoIpLookup: function (callback) {
32
+ jQuery.get(ajaxurl, {action: 'bookly_ip_info'}, function () {
33
+ }, 'json').always(function (resp) {
34
+ var countryCode = (resp && resp.country) ? resp.country : '';
35
+ callback(countryCode);
36
+ });
37
+ },
38
+ utilsScript: BooklyL10nCustDialog.intlTelInput.utils
39
+ });
40
+ }
41
+ // Form errors.
42
+ scope.errors = {
43
+ name: {required: false}
44
+ };
45
+ // Loading indicator.
46
+ scope.loading = false;
47
+
48
+ jQuery('#bookly-customer-dialog').modal('hide')
49
+ .on('hidden.bs.modal', function () {
50
+ if (jQuery('#bookly-appointment-dialog').length) {
51
+ jQuery('body').addClass('modal-open');
52
+ }
53
+ });
54
+ };
55
+
56
+ // Run init.
57
+ init();
58
+
59
+ // On 'Cancel' button click.
60
+ scope.closeDialog = function () {
61
+ // Close the dialog.
62
+ jQuery('#bookly-customer-dialog').modal('hide');
63
+ // Re-init all properties.
64
+ init();
65
+ };
66
+
67
+ /**
68
+ * Send form to server.
69
+ */
70
+ scope.processForm = function() {
71
+ scope.errors = {};
72
+ scope.loading = true;
73
+ scope.form.phone = BooklyL10nCustDialog.intlTelInput.enabled
74
+ ? element.find('#phone').intlTelInput('getNumber')
75
+ : element.find('#phone').val();
76
+ jQuery.ajax({
77
+ url : ajaxurl,
78
+ type : 'POST',
79
+ data : jQuery.extend({ action : 'bookly_save_customer' }, scope.form),
80
+ dataType : 'json',
81
+ success : function ( response ) {
82
+ scope.$apply(function(scope) {
83
+ if (response.success) {
84
+ response.customer.custom_fields = [];
85
+ response.customer.extras = [];
86
+ response.customer.status = BooklyL10nCustDialog.default_status;
87
+ // Send new customer to the parent scope.
88
+ scope.callback({customer : response.customer});
89
+ scope.form = {
90
+ id : '',
91
+ wp_user_id : '',
92
+ name : '',
93
+ phone : '',
94
+ email : '',
95
+ notes : ''
96
+ };
97
+ // Close the dialog.
98
+ scope.closeDialog();
99
+ } else {
100
+ // Set errors.
101
+ jQuery.each(response.errors, function(field, errors) {
102
+ scope.errors[field] = {};
103
+ jQuery.each(errors, function(key, error) {
104
+ scope.errors[field][error] = true;
105
+ });
106
+ });
107
+ }
108
+ scope.loading = false;
109
+ });
110
+ },
111
+ error : function() {
112
+ scope.$apply(function(scope) {
113
+ scope.loading = false;
114
+ });
115
+ }
116
+ });
117
+ };
118
+ }
119
+ };
120
+ });
121
+
122
  })();
backend/modules/customers/templates/_customer_dialog.php CHANGED
@@ -46,12 +46,8 @@
46
  </div>
47
  <div class="modal-footer">
48
  <div ng-hide=loading>
49
- <button ng-click="processForm()" class="btn btn-lg btn-success">
50
- <?php _e( 'Create customer', 'bookly' ) ?>
51
- </button>
52
- <button type="button" class="ab-reset-form btn btn-lg btn-default" data-dismiss="modal">
53
- <?php _e( 'Cancel', 'bookly' ) ?>
54
- </button>
55
  </div>
56
  </div>
57
  </div>
46
  </div>
47
  <div class="modal-footer">
48
  <div ng-hide=loading>
49
+ <?php \BooklyLite\Lib\Utils\Common::customButton( null, 'btn-success btn-lg', __( 'Create customer', 'bookly' ), array( 'ng-click' => 'processForm()' ) ) ?>
50
+ <?php \BooklyLite\Lib\Utils\Common::customButton( null, 'btn-default btn-lg', __( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
 
 
 
 
51
  </div>
52
  </div>
53
  </div>
backend/modules/customers/templates/_import.php CHANGED
@@ -1,36 +1,36 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div id="bookly-import-customers-dialog" class="modal fade" tabindex=-1 role="dialog">
3
- <div class="modal-dialog">
4
- <form enctype="multipart/form-data" action="<?php echo \BooklyLite\Lib\Utils\Common::escAdminUrl( \BooklyLite\Backend\Modules\Customers\Controller::page_slug ) ?>" method="POST">
5
- <div class="modal-content">
6
- <div class="modal-header">
7
- <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
8
- <div class="modal-title h2"><?php _e( 'Import', 'bookly' ) ?></div>
9
- </div>
10
- <div class="modal-body">
11
- <h4><?php _e( 'Note', 'bookly' ) ?></h4>
12
- <p>
13
- <?php _e( 'You may import list of clients in CSV format. The file needs to have three columns: Name, Phone and Email.', 'bookly' ) ?>
14
- </p>
15
- <div class="form-group">
16
- <label for="import_customers_file"><?php _e( 'Select file', 'bookly' ) ?></label>
17
- <input name="import_customers_file" id="import_customers_file" type="file">
18
- </div>
19
- <div class="form-group">
20
- <label for="import_customers_delimiter"><?php _e( 'Delimiter', 'bookly' ) ?></label>
21
- <select name="import_customers_delimiter" id="import_customers_delimiter" class="form-control">
22
- <option value=","><?php _e( 'Comma (,)', 'bookly' ) ?></option>
23
- <option value=";"><?php _e( 'Semicolon (;)', 'bookly' ) ?></option>
24
- </select>
25
- </div>
26
- <input type="hidden" name="import">
27
- </div>
28
- <div class="modal-footer">
29
- <button type="submit" class="btn btn-lg btn-success ab-popup-save" name="import-customers">
30
- <?php _e( 'Import', 'bookly' ) ?>
31
- </button>
32
- </div>
33
- </div>
34
- </form>
35
- </div>
36
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div id="bookly-import-customers-dialog" class="modal fade" tabindex=-1 role="dialog">
3
+ <div class="modal-dialog">
4
+ <form enctype="multipart/form-data" action="<?php echo \BooklyLite\Lib\Utils\Common::escAdminUrl( \BooklyLite\Backend\Modules\Customers\Controller::page_slug ) ?>" method="POST">
5
+ <div class="modal-content">
6
+ <div class="modal-header">
7
+ <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
8
+ <div class="modal-title h2"><?php _e( 'Import', 'bookly' ) ?></div>
9
+ </div>
10
+ <div class="modal-body">
11
+ <h4><?php _e( 'Note', 'bookly' ) ?></h4>
12
+ <p>
13
+ <?php _e( 'You may import list of clients in CSV format. The file needs to have three columns: Name, Phone and Email.', 'bookly' ) ?>
14
+ </p>
15
+ <div class="form-group">
16
+ <label for="import_customers_file"><?php _e( 'Select file', 'bookly' ) ?></label>
17
+ <input name="import_customers_file" id="import_customers_file" type="file">
18
+ </div>
19
+ <div class="form-group">
20
+ <label for="import_customers_delimiter"><?php _e( 'Delimiter', 'bookly' ) ?></label>
21
+ <select name="import_customers_delimiter" id="import_customers_delimiter" class="form-control">
22
+ <option value=","><?php _e( 'Comma (,)', 'bookly' ) ?></option>
23
+ <option value=";"><?php _e( 'Semicolon (;)', 'bookly' ) ?></option>
24
+ </select>
25
+ </div>
26
+ <input type="hidden" name="import">
27
+ </div>
28
+ <div class="modal-footer">
29
+ <button type="submit" class="btn btn-lg btn-success ab-popup-save" name="import-customers">
30
+ <?php _e( 'Import', 'bookly' ) ?>
31
+ </button>
32
+ </div>
33
+ </div>
34
+ </form>
35
+ </div>
36
  </div>
backend/modules/customers/templates/index.php CHANGED
@@ -5,25 +5,25 @@
5
  <div class="bookly-page-title">
6
  <?php _e( 'Customers', 'bookly' ) ?>
7
  </div>
 
8
  </div>
9
  <div class="panel panel-default bookly-main">
10
  <div class="panel-body">
11
  <div class="row">
12
- <div class="form-inline bookly-margin-bottom-lg text-right">
13
  <div class="form-group">
14
- <button type="button" class="btn btn-default bookly-btn-block-xs bookly-limitation"><i class="glyphicon glyphicon-export"></i> <?php _e( 'Export to CSV', 'bookly' ) ?></button>
15
  </div>
 
 
16
  <div class="form-group">
17
- <button type="button" class="btn btn-default bookly-btn-block-xs" data-toggle="modal" data-target="#bookly-import-customers-dialog"><i class="glyphicon glyphicon-import"></i> <?php _e( 'Import', 'bookly' ) ?></button>
18
  </div>
19
  <div class="form-group">
20
- <button type="button" class="btn btn-success bookly-btn-block-xs" id="bookly-add" data-toggle="modal" data-target="#bookly-customer-dialog"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'New customer', 'bookly' ) ?></button>
21
  </div>
22
- </div>
23
-
24
- <div class="col-md-4">
25
  <div class="form-group">
26
- <input class="form-control" type="text" id="bookly-filter" placeholder="<?php esc_attr_e( 'Quick search customer', 'bookly' ) ?>" />
27
  </div>
28
  </div>
29
  </div>
@@ -31,10 +31,10 @@
31
  <table id="bookly-customers-list" class="table table-striped" width="100%">
32
  <thead>
33
  <tr>
34
- <th><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_name' ) ?></th>
35
  <th><?php _e( 'User', 'bookly' ) ?></th>
36
- <th><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_phone' ) ?></th>
37
- <th><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_email' ) ?></th>
38
  <th><?php _e( 'Notes', 'bookly' ) ?></th>
39
  <th><?php _e( 'Last appointment', 'bookly' ) ?></th>
40
  <th><?php _e( 'Total appointments', 'bookly' ) ?></th>
5
  <div class="bookly-page-title">
6
  <?php _e( 'Customers', 'bookly' ) ?>
7
  </div>
8
+ <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
9
  </div>
10
  <div class="panel panel-default bookly-main">
11
  <div class="panel-body">
12
  <div class="row">
13
+ <div class="col-md-4">
14
  <div class="form-group">
15
+ <input class="form-control" type="text" id="bookly-filter" placeholder="<?php esc_attr_e( 'Quick search customer', 'bookly' ) ?>" />
16
  </div>
17
+ </div>
18
+ <div class="col-md-8 form-inline bookly-margin-bottom-lg text-right">
19
  <div class="form-group">
20
+ <button type="button" class="btn btn-default bookly-btn-block-xs bookly-limitation" data-toggle="modal" data-target="#bookly-export-customers-dialog"><i class="glyphicon glyphicon-export"></i> <?php _e( 'Export to CSV', 'bookly' ) ?></button>
21
  </div>
22
  <div class="form-group">
23
+ <button type="button" class="btn btn-default bookly-btn-block-xs" data-toggle="modal" data-target="#bookly-import-customers-dialog"><i class="glyphicon glyphicon-import"></i> <?php _e( 'Import', 'bookly' ) ?></button>
24
  </div>
 
 
 
25
  <div class="form-group">
26
+ <button type="button" class="btn btn-success bookly-btn-block-xs" id="bookly-add" data-toggle="modal" data-target="#bookly-customer-dialog"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'New customer', 'bookly' ) ?></button>
27
  </div>
28
  </div>
29
  </div>
31
  <table id="bookly-customers-list" class="table table-striped" width="100%">
32
  <thead>
33
  <tr>
34
+ <th><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_name' ) ?></th>
35
  <th><?php _e( 'User', 'bookly' ) ?></th>
36
+ <th><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_phone' ) ?></th>
37
+ <th><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_email' ) ?></th>
38
  <th><?php _e( 'Notes', 'bookly' ) ?></th>
39
  <th><?php _e( 'Last appointment', 'bookly' ) ?></th>
40
  <th><?php _e( 'Total appointments', 'bookly' ) ?></th>
backend/modules/debug/Controller.php CHANGED
@@ -9,6 +9,7 @@ use BooklyLite\Lib;
9
  */
10
  class Controller extends Lib\Base\Controller
11
  {
 
12
 
13
  const TABLE_STATUS_OK = 1;
14
  const TABLE_STATUS_ERROR = 0;
@@ -30,67 +31,187 @@ class Controller extends Lib\Base\Controller
30
  ) );
31
 
32
  $debug = array();
33
- /** @var Lib\Base\Entity $entity */
34
- foreach ( self::getEntities() as $entity ) {
35
- $tableName = $entity::getTableName();
36
- $debug[ $tableName ] = array(
37
- 'fields' => null,
38
- 'constraints' => null,
39
- 'status' => null,
40
- );
41
- if ( self::tableExists( $tableName ) ) {
42
- $tableStructure = self::getTableStructure( $tableName );
43
- $tableConstraints = self::getTableConstraints( $tableName );
44
- $entitySchema = $entity::getSchema();
45
- $entityConstraints = $entity::getConstraints();
46
- $debug[ $tableName ]['status'] = self::TABLE_STATUS_OK;
47
- $debug[ $tableName ]['fields'] = array();
48
-
49
- // Comparing model schema with real DB schema
50
- foreach ( $entitySchema as $field => $data ) {
51
- if ( in_array( $field, $tableStructure ) ) {
52
- $debug[ $tableName ]['fields'][ $field ] = 1;
53
- } else {
54
- $debug[ $tableName ]['fields'][ $field ] = 0;
55
- $debug[ $tableName ]['status'] = self::TABLE_STATUS_WARNING;
 
 
56
  }
57
- }
58
 
59
- // Comparing model constraints with real DB constraints
60
- foreach ( $entityConstraints as $constraint ) {
61
- $key = $constraint['column_name'] . $constraint['referenced_table_name'] . $constraint['referenced_column_name'];
62
- $debug[ $tableName ]['constraints'][ $key ] = $constraint;
63
- if ( array_key_exists ( $key, $tableConstraints ) ) {
64
- $debug[ $tableName ]['constraints'][ $key ]['status'] = 1;
65
- } else {
66
- $debug[ $tableName ]['constraints'][ $key ]['status'] = 0;
67
- $debug[ $tableName ]['status'] = self::TABLE_STATUS_WARNING;
 
68
  }
69
- }
70
 
71
- } else {
72
- $debug[ $tableName ]['status'] = self::TABLE_STATUS_ERROR;
 
73
  }
74
  }
75
- $this->render( 'index', compact( 'debug', 'sql' ) );
 
 
76
  }
77
 
78
  /**
79
- * Get entity class names.
80
- *
81
- * @return array
82
  */
83
- public function getEntities()
84
  {
 
 
 
85
  $result = array();
86
- foreach ( scandir( Lib\Plugin::getDirectory() . '/lib/entities' ) as $filename ) {
87
- if ( $filename == '.' || $filename == '..' ) {
88
- continue;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  }
90
- $result[] = '\\BooklyLite\\Lib\\Entities\\' . basename( $filename, '.php' );
91
  }
92
 
93
- return apply_filters( 'bookly_tables', $result );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  }
95
 
96
  /**
@@ -99,7 +220,7 @@ class Controller extends Lib\Base\Controller
99
  * @param string $tableName
100
  * @return array
101
  */
102
- public static function getTableStructure( $tableName )
103
  {
104
  global $wpdb;
105
 
@@ -120,7 +241,7 @@ class Controller extends Lib\Base\Controller
120
  * @param string $tableName
121
  * @return array
122
  */
123
- public static function getTableConstraints( $tableName )
124
  {
125
  global $wpdb;
126
 
@@ -158,7 +279,7 @@ class Controller extends Lib\Base\Controller
158
  * @param string $tableName
159
  * @return int
160
  */
161
- public static function tableExists( $tableName )
162
  {
163
  global $wpdb;
164
 
@@ -168,14 +289,14 @@ class Controller extends Lib\Base\Controller
168
  // Protected methods.
169
 
170
  /**
171
- * Override parent method to add 'wp_ajax_ab_' prefix
172
  * so current 'execute*' methods look nicer.
173
  *
174
  * @param string $prefix
175
  */
176
  protected function registerWpActions( $prefix = '' )
177
  {
178
- parent::registerWpActions( 'wp_ajax_ab_' );
179
  }
180
 
181
  }
9
  */
10
  class Controller extends Lib\Base\Controller
11
  {
12
+ const page_slug = 'bookly-debug';
13
 
14
  const TABLE_STATUS_OK = 1;
15
  const TABLE_STATUS_ERROR = 0;
31
  ) );
32
 
33
  $debug = array();
34
+ /** @var Lib\Base\Plugin $plugin */
35
+ foreach ( apply_filters( 'bookly_plugins', array() ) as $plugin ) {
36
+ foreach ( $plugin::getEntityClasses() as $entity_class ) {
37
+ $tableName = $entity_class::getTableName();
38
+ $debug[ $tableName ] = array(
39
+ 'fields' => null,
40
+ 'constraints' => null,
41
+ 'status' => null,
42
+ );
43
+ if ( $this->_tableExists( $tableName ) ) {
44
+ $tableStructure = $this->_getTableStructure( $tableName );
45
+ $tableConstraints = $this->_getTableConstraints( $tableName );
46
+ $entitySchema = $entity_class::getSchema();
47
+ $entityConstraints = $entity_class::getConstraints();
48
+ $debug[ $tableName ]['status'] = self::TABLE_STATUS_OK;
49
+ $debug[ $tableName ]['fields'] = array();
50
+
51
+ // Comparing model schema with real DB schema
52
+ foreach ( $entitySchema as $field => $data ) {
53
+ if ( in_array( $field, $tableStructure ) ) {
54
+ $debug[ $tableName ]['fields'][ $field ] = 1;
55
+ } else {
56
+ $debug[ $tableName ]['fields'][ $field ] = 0;
57
+ $debug[ $tableName ]['status'] = self::TABLE_STATUS_WARNING;
58
+ }
59
  }
 
60
 
61
+ // Comparing model constraints with real DB constraints
62
+ foreach ( $entityConstraints as $constraint ) {
63
+ $key = $constraint['column_name'] . $constraint['referenced_table_name'] . $constraint['referenced_column_name'];
64
+ $debug[ $tableName ]['constraints'][ $key ] = $constraint;
65
+ if ( array_key_exists ( $key, $tableConstraints ) ) {
66
+ $debug[ $tableName ]['constraints'][ $key ]['status'] = 1;
67
+ } else {
68
+ $debug[ $tableName ]['constraints'][ $key ]['status'] = 0;
69
+ $debug[ $tableName ]['status'] = self::TABLE_STATUS_WARNING;
70
+ }
71
  }
 
72
 
73
+ } else {
74
+ $debug[ $tableName ]['status'] = self::TABLE_STATUS_ERROR;
75
+ }
76
  }
77
  }
78
+
79
+ $import_status = $this->getParameter( 'status' );
80
+ $this->render( 'index', compact( 'debug', 'import_status' ) );
81
  }
82
 
83
  /**
84
+ * Export database data.
 
 
85
  */
86
+ public function executeExportData()
87
  {
88
+ /** @var \wpdb $wpdb */
89
+ global $wpdb;
90
+
91
  $result = array();
92
+
93
+ foreach ( apply_filters( 'bookly_plugins', array() ) as $plugin ) {
94
+ /** @var Lib\Base\Plugin $plugin */
95
+ $installer_class = $plugin::getRootNamespace() . '\Lib\Installer';
96
+ /** @var Lib\Base\Installer $installer */
97
+ $installer = new $installer_class();
98
+
99
+ foreach ( $plugin::getEntityClasses() as $entity_class ) {
100
+ $table_name = $entity_class::getTableName();
101
+ $result['entities'][ $entity_class ] = array(
102
+ 'fields' => $this->_getTableStructure( $table_name ),
103
+ 'values' => $wpdb->get_results( 'SELECT * FROM ' . $table_name, ARRAY_N )
104
+ );
105
+ }
106
+ $plugin_prefix = $plugin::getPrefix();
107
+ $options_postfix = array( 'data_loaded', 'grace_start', 'db_version', 'installation_time' );
108
+ if ( $plugin_prefix != 'bookly_' ) {
109
+ $options_postfix[] = 'enabled';
110
+ }
111
+ foreach ( $options_postfix as $option ) {
112
+ $option_name = $plugin_prefix . $option;
113
+ $result['options'][ $option_name ] = get_option( $option_name );
114
+ }
115
+
116
+ $result['options'][ $plugin::getPurchaseCodeOption() ] = $plugin::getPurchaseCode();
117
+ foreach ( $installer->getOptions() as $option_name => $option_value ) {
118
+ $result['options'][ $option_name ] = get_option( $option_name );
119
  }
 
120
  }
121
 
122
+ header( 'Content-type: application/json' );
123
+ header( 'Content-Disposition: attachment; filename=bookly_db_export_' . date( 'YmdHis' ) . '.json' );
124
+ echo json_encode( $result );
125
+
126
+ exit ( 0 );
127
+ }
128
+
129
+ /**
130
+ * Import database data.
131
+ */
132
+ public function executeImportData()
133
+ {
134
+ /** @var \wpdb $wpdb */
135
+ global $wpdb;
136
+
137
+ if ( $file = $_FILES['import']['name'] ) {
138
+ $json = file_get_contents( $_FILES['import']['tmp_name'] );
139
+ if ( $json !== false) {
140
+ $wpdb->query( 'SET FOREIGN_KEY_CHECKS = 0' );
141
+
142
+ $data = json_decode( $json, true );
143
+
144
+ foreach ( apply_filters( 'bookly_plugins', array() ) as $plugin ) {
145
+ /** @var Lib\Base\Plugin $plugin */
146
+ $installer_class = $plugin::getRootNamespace() . '\Lib\Installer';
147
+ /** @var Lib\Base\Installer $installer */
148
+ $installer = new $installer_class();
149
+
150
+ // Drop all data and options.
151
+ $installer->removeData();
152
+ $installer->dropTables();
153
+ $installer->createTables();
154
+
155
+ // Insert tables data.
156
+ foreach ( $plugin::getEntityClasses() as $entity_class ) {
157
+ if ( isset ( $data['entities'][ $entity_class ]['values'][0] ) ) {
158
+ $table_name = $entity_class::getTableName();
159
+ $query = sprintf(
160
+ 'INSERT INTO `%s` (`%s`) VALUES (%%s)',
161
+ $table_name,
162
+ implode( '`,`', $data['entities'][ $entity_class ]['fields'] )
163
+ );
164
+ $placeholders = array();
165
+ $values = array();
166
+ $counter = 0;
167
+ foreach ( $data['entities'][ $entity_class ]['values'] as $row ) {
168
+ $params = array();
169
+ foreach ( $row as $value ) {
170
+ if ( $value === null ) {
171
+ $params[] = 'NULL';
172
+ } else {
173
+ $params[] = '%s';
174
+ $values[] = $value;
175
+ }
176
+ }
177
+ $placeholders[] = implode( ',', $params );
178
+ if ( ++ $counter > 50 ) {
179
+ // Flush.
180
+ $wpdb->query( $wpdb->prepare( sprintf( $query, implode( '),(', $placeholders ) ), $values ) );
181
+ $placeholders = array();
182
+ $values = array();
183
+ $counter = 0;
184
+ }
185
+ }
186
+ if ( ! empty ( $placeholders ) ) {
187
+ $wpdb->query( $wpdb->prepare( sprintf( $query, implode( '),(', $placeholders ) ), $values ) );
188
+ }
189
+ }
190
+ }
191
+
192
+ // Insert options data.
193
+ foreach ( $installer->getOptions() as $option_name => $option_value ) {
194
+ add_option( $option_name, $data['options'][ $option_name ] );
195
+ }
196
+
197
+ $plugin_prefix = $plugin::getPrefix();
198
+ $options_postfix = array( 'data_loaded', 'grace_start', 'db_version' );
199
+ if ( $plugin_prefix != 'bookly_' ) {
200
+ $options_postfix[] = 'enabled';
201
+ }
202
+ foreach ( $options_postfix as $option ) {
203
+ $option_name = $plugin_prefix . $option;
204
+ add_option( $option_name, $data['options'][ $option_name ] );
205
+ }
206
+ }
207
+
208
+ header( 'Location: ' . admin_url( 'admin.php?page=bookly-debug&status=imported' ) );
209
+ }
210
+ }
211
+
212
+ header( 'Location: ' . admin_url( 'admin.php?page=bookly-debug' ) );
213
+
214
+ exit ( 0 );
215
  }
216
 
217
  /**
220
  * @param string $tableName
221
  * @return array
222
  */
223
+ private function _getTableStructure( $tableName )
224
  {
225
  global $wpdb;
226
 
241
  * @param string $tableName
242
  * @return array
243
  */
244
+ private function _getTableConstraints( $tableName )
245
  {
246
  global $wpdb;
247
 
279
  * @param string $tableName
280
  * @return int
281
  */
282
+ private function _tableExists( $tableName )
283
  {
284
  global $wpdb;
285
 
289
  // Protected methods.
290
 
291
  /**
292
+ * Override parent method to add 'wp_ajax_bookly_' prefix
293
  * so current 'execute*' methods look nicer.
294
  *
295
  * @param string $prefix
296
  */
297
  protected function registerWpActions( $prefix = '' )
298
  {
299
+ parent::registerWpActions( 'wp_ajax_bookly_' );
300
  }
301
 
302
  }
backend/modules/debug/resources/css/style.css CHANGED
@@ -9,4 +9,26 @@
9
 
10
  .list-group-item{
11
  padding: 5px 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  }
9
 
10
  .list-group-item{
11
  padding: 5px 10px;
12
+ }
13
+ .btn-file {
14
+ position: relative;
15
+ overflow: hidden;
16
+ }
17
+ .btn-file input[type=file] {
18
+ position: absolute;
19
+ top: 0;
20
+ right: 0;
21
+ min-width: 100%;
22
+ min-height: 100%;
23
+ font-size: 100px;
24
+ text-align: right;
25
+ filter: alpha(opacity=0);
26
+ opacity: 0;
27
+ outline: none;
28
+ background: white;
29
+ cursor: inherit;
30
+ display: block;
31
+ }
32
+ .bookly-data-button {
33
+ display: inline-block;
34
  }
backend/modules/debug/resources/js/debug.js CHANGED
@@ -1,3 +1,9 @@
1
  jQuery(function($) {
2
  $('.collapse').collapse('hide');
 
 
 
 
 
 
3
  });
1
  jQuery(function($) {
2
  $('.collapse').collapse('hide');
3
+
4
+ $('#bookly_import_file').change(function() {
5
+ if($(this).val()) {
6
+ $('#bookly_import').submit();
7
+ }
8
+ });
9
  });
backend/modules/debug/templates/index.php CHANGED
@@ -1,6 +1,34 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
  <div id="bookly-tbs" class="wrap">
3
  <div class="bookly-tbs-body">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  <div class="page-header text-right clearfix">
5
  <div class="bookly-page-title">
6
  Database Integrity
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
  <div id="bookly-tbs" class="wrap">
3
  <div class="bookly-tbs-body">
4
+ <div class="page-header text-right clearfix">
5
+ <div class="bookly-page-title">
6
+ Data management
7
+ </div>
8
+ </div>
9
+ <?php if ( $import_status ) : ?>
10
+ <div class="alert alert-success">
11
+ Data successfully imported
12
+ </div>
13
+ <?php endif ?>
14
+
15
+ <div class="panel-group" id="data-management">
16
+ <div class="bookly-data-button">
17
+ <form action="<?php echo admin_url( 'admin-ajax.php?action=bookly_export_data' ) ?>" method="POST">
18
+ <button id="bookly-export" type="submit" class="btn btn-lg btn-success">
19
+ <span class="ladda-label">Export data</span>
20
+ </button>
21
+ </form>
22
+ </div>
23
+ <div class="bookly-data-button">
24
+ <form id="bookly_import" action="<?php echo admin_url( 'admin-ajax.php?action=bookly_import_data' ) ?>" method="POST" enctype="multipart/form-data">
25
+ <div id="bookly-import" class="btn btn-lg btn-primary btn-file">
26
+ <span class="ladda-label">Import data</span>
27
+ <input type="file" id="bookly_import_file" name="import">
28
+ </div>
29
+ </form>
30
+ </div>
31
+ </div>
32
  <div class="page-header text-right clearfix">
33
  <div class="bookly-page-title">
34
  Database Integrity
backend/modules/notifications/Controller.php CHANGED
@@ -1,108 +1,110 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Notifications;
3
-
4
- use \BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Notifications
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- public function index()
13
- {
14
- $this->enqueueStyles( array(
15
- 'frontend' => array( 'css/ladda.min.css' ),
16
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
17
- ) );
18
-
19
- $this->enqueueScripts( array(
20
- 'backend' => array(
21
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
22
- 'js/angular.min.js',
23
- 'js/help.js' => array( 'jquery' ),
24
- 'js/alert.js' => array( 'jquery' ),
25
- ),
26
- 'module' => array(
27
- 'js/notification.js' => array( 'jquery' ),
28
- 'js/ng-app.js' => array( 'jquery', 'ab-angular.min.js' ),
29
- ),
30
- 'frontend' => array(
31
- 'js/spin.min.js' => array( 'jquery' ),
32
- 'js/ladda.min.js' => array( 'jquery' ),
33
- )
34
- ) );
35
- $cron_reminder = (array) get_option( 'ab_settings_cron_reminder' );
36
- $form = new Forms\Notifications( 'email' );
37
- $alert = array( 'success' => array() );
38
- // Save action.
39
- if ( ! empty ( $_POST ) ) {
40
- $form->bind( $this->getPostParameters(), $_FILES );
41
- $form->save();
42
- $alert['success'][] = __( 'Settings saved.', 'bookly' );
43
- // sender name
44
- if ( $this->hasParameter( 'ab_settings_sender_name' ) ) {
45
- update_option( 'ab_settings_sender_name', $this->getParameter( 'ab_settings_sender_name' ) );
46
- }
47
- // sender email
48
- if ( $this->hasParameter( 'ab_settings_sender_email' ) ) {
49
- update_option( 'ab_settings_sender_email', $this->getParameter( 'ab_settings_sender_email' ) );
50
- }
51
- if ( $this->hasParameter( 'ab_email_notification_reply_to_customers' ) ) {
52
- update_option( 'ab_email_notification_reply_to_customers', $this->getParameter( 'ab_email_notification_reply_to_customers' ) );
53
- }
54
- if ( $this->hasParameter( 'ab_email_content_type' ) ) {
55
- update_option( 'ab_email_content_type', $this->getParameter( 'ab_email_content_type' ) );
56
- }
57
- foreach ( array( 'staff_agenda', 'client_follow_up', 'client_reminder' ) as $type ) {
58
- $cron_reminder[ $type ] = $this->getParameter( $type . '_cron_hour' );
59
- }
60
- update_option( 'ab_settings_cron_reminder', $cron_reminder );
61
- }
62
- $cron_path = realpath( Lib\Plugin::getDirectory() . '/lib/utils/send_notifications_cron.php' );
63
- wp_localize_script( 'ab-alert.js', 'BooklyL10n', array(
64
- 'alert' => $alert,
65
- 'sent_successfully' => __( 'Sent successfully', 'bookly' ),
66
- 'limitations' => __( '<b class="h4">This function is disabled in the Lite version of Bookly.</b><br><br>If you find the plugin useful for your business please consider buying a licence for the full version.<br>It costs just $59 and for this money you will get many useful functions, lifetime free updates and excellent support!<br>More information can be found here', 'bookly' ) . ': <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
67
- ) );
68
- $this->render( 'index', compact( 'form', 'cron_path', 'cron_reminder' ) );
69
- }
70
-
71
- public function executeGetEmailNotificationsData()
72
- {
73
- $result = array();
74
-
75
- $form = new Forms\Notifications( 'email' );
76
-
77
- $ab_settings_sender_name = get_option( 'ab_settings_sender_name' ) == '' ?
78
- get_option( 'blogname' ) : get_option( 'ab_settings_sender_name' );
79
-
80
- $ab_settings_sender_email = get_option( 'ab_settings_sender_email' ) == '' ?
81
- get_option( 'admin_email' ) : get_option( 'ab_settings_sender_email' );
82
-
83
- $result[ 'ab_notifications' ] = $form->getData();
84
- $result[ 'ab_settings_sender_name' ] = $ab_settings_sender_name;
85
- $result[ 'ab_settings_sender_email' ] = $ab_settings_sender_email;
86
- $result[ 'ab_types' ] = $form->types;
87
-
88
- wp_send_json_success( $result );
89
- }
90
-
91
- public function executeTestEmailNotifications()
92
- {
93
- }
94
-
95
- // Protected methods.
96
-
97
- /**
98
- * Override parent method to add 'wp_ajax_ab_' prefix
99
- * so current 'execute*' methods look nicer.
100
- *
101
- * @param string $prefix
102
- */
103
- protected function registerWpActions( $prefix = '' )
104
- {
105
- parent::registerWpActions( 'wp_ajax_ab_' );
106
- }
107
-
 
 
108
  }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Notifications;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Controller
8
+ * @package BooklyLite\Backend\Modules\Notifications
9
+ */
10
+ class Controller extends Lib\Base\Controller
11
+ {
12
+ const page_slug = 'bookly-notifications';
13
+
14
+ public function index()
15
+ {
16
+ $this->enqueueStyles( array(
17
+ 'frontend' => array( 'css/ladda.min.css' ),
18
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
19
+ ) );
20
+
21
+ $this->enqueueScripts( array(
22
+ 'backend' => array(
23
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
24
+ 'js/angular.min.js',
25
+ 'js/help.js' => array( 'jquery' ),
26
+ 'js/alert.js' => array( 'jquery' ),
27
+ ),
28
+ 'module' => array(
29
+ 'js/notification.js' => array( 'jquery' ),
30
+ 'js/ng-app.js' => array( 'jquery', 'bookly-angular.min.js' ),
31
+ ),
32
+ 'frontend' => array(
33
+ 'js/spin.min.js' => array( 'jquery' ),
34
+ 'js/ladda.min.js' => array( 'jquery' ),
35
+ )
36
+ ) );
37
+ $cron_reminder = (array) get_option( 'bookly_cron_reminder_times' );
38
+ $form = new Forms\Notifications( 'email' );
39
+ $alert = array( 'success' => array() );
40
+ // Save action.
41
+ if ( ! empty ( $_POST ) ) {
42
+ $form->bind( $this->getPostParameters() );
43
+ $form->save();
44
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
45
+ update_option( 'bookly_email_send_as', $this->getParameter( 'bookly_email_send_as' ) );
46
+ update_option( 'bookly_email_reply_to_customers', $this->getParameter( 'bookly_email_reply_to_customers' ) );
47
+ update_option( 'bookly_email_sender', $this->getParameter( 'bookly_email_sender' ) );
48
+ update_option( 'bookly_email_sender_name', $this->getParameter( 'bookly_email_sender_name' ) );
49
+ foreach ( array( 'staff_agenda', 'client_follow_up', 'client_reminder' ) as $type ) {
50
+ $cron_reminder[ $type ] = $this->getParameter( $type . '_cron_hour' );
51
+ }
52
+ update_option( 'bookly_cron_reminder_times', $cron_reminder );
53
+ }
54
+ $cron_path = realpath( Lib\Plugin::getDirectory() . '/lib/utils/send_notifications_cron.php' );
55
+ wp_localize_script( 'bookly-alert.js', 'BooklyL10n', array(
56
+ 'alert' => $alert,
57
+ 'sent_successfully' => __( 'Sent successfully.', 'bookly' ),
58
+ 'limitations' => __( '<b class="h4">This function is disabled in the Lite version of Bookly.</b><br><br>If you find the plugin useful for your business please consider buying a licence for the full version.<br>It costs just $59 and for this money you will get many useful functions, lifetime free updates and excellent support!<br>More information can be found here', 'bookly' ) . ': <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
59
+ ) );
60
+ $this->render( 'index', compact( 'form', 'cron_path', 'cron_reminder' ) );
61
+ }
62
+
63
+ public function executeGetEmailNotificationsData()
64
+ {
65
+ $form = new Forms\Notifications( 'email' );
66
+
67
+ $bookly_email_sender_name = get_option( 'bookly_email_sender_name' ) == '' ?
68
+ get_option( 'blogname' ) : get_option( 'bookly_email_sender_name' );
69
+
70
+ $bookly_email_sender = get_option( 'bookly_email_sender' ) == '' ?
71
+ get_option( 'admin_email' ) : get_option( 'bookly_email_sender' );
72
+
73
+ $notifications = array();
74
+ foreach ( $form->getData() as $notification ) {
75
+ $notifications[] = array(
76
+ 'type' => $notification['type'],
77
+ 'name' => $notification['name'],
78
+ 'active' => $notification['active'],
79
+ );
80
+ }
81
+
82
+ $result = array(
83
+ 'notifications' => $notifications,
84
+ 'sender_email' => $bookly_email_sender,
85
+ 'sender_name' => $bookly_email_sender_name,
86
+ 'send_as' => get_option( 'bookly_email_send_as' ),
87
+ 'reply_to_customers' => get_option( 'bookly_email_reply_to_customers' ),
88
+ );
89
+
90
+ wp_send_json_success( $result );
91
+ }
92
+
93
+ public function executeTestEmailNotifications()
94
+ {
95
+ }
96
+
97
+ // Protected methods.
98
+
99
+ /**
100
+ * Override parent method to add 'wp_ajax_bookly_' prefix
101
+ * so current 'execute*' methods look nicer.
102
+ *
103
+ * @param string $prefix
104
+ */
105
+ protected function registerWpActions( $prefix = '' )
106
+ {
107
+ parent::registerWpActions( 'wp_ajax_bookly_' );
108
+ }
109
+
110
  }
backend/modules/notifications/forms/Notifications.php CHANGED
@@ -1,213 +1,215 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Notifications\Forms;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Notifications
8
- * @package BooklyLite\Backend\Modules\Notifications\Forms
9
- */
10
- class Notifications extends Lib\Base\Form
11
- {
12
- public $types = array(
13
- 'single' => array(
14
- 'client_pending_appointment',
15
- 'staff_pending_appointment',
16
- 'client_approved_appointment',
17
- 'staff_approved_appointment',
18
- 'client_cancelled_appointment',
19
- 'staff_cancelled_appointment',
20
- 'client_new_wp_user',
21
- 'client_reminder',
22
- 'client_follow_up',
23
- 'staff_agenda',
24
- ),
25
- 'combined' => array(
26
- 'client_pending_appointment_cart',
27
- 'client_approved_appointment_cart',
28
- ),
29
- );
30
-
31
- public $gateway;
32
-
33
- /**
34
- * Constructor.
35
- *
36
- * @param string $gateway
37
- */
38
- public function __construct( $gateway = 'email' )
39
- {
40
- /*
41
- * make Visual Mode as default (instead of Text Mode)
42
- * allowed: tinymce - Visual Mode, html - Text Mode, test - no one Mode selected
43
- */
44
- add_filter( 'wp_default_editor', create_function( '', 'return \'tinymce\';' ) );
45
- $this->gateway = $gateway;
46
- if ( ! Lib\Config::areCombinedNotificationsEnabled() ) {
47
- $this->types['combined'] = array();
48
- }
49
- $this->setFields( array( 'active', 'subject', 'message', 'copy', ) );
50
- $this->load();
51
- }
52
-
53
- public function bind( array $_post = array(), array $files = array() )
54
- {
55
- foreach ( $this->types as $group ) {
56
- foreach ( $group as $type ) {
57
- foreach ( $this->fields as $field ) {
58
- if ( isset ( $_post[ $type ] [ $field ] ) ) {
59
- $this->data[ $type ][ $field ] = $_post[ $type ][ $field ];
60
- }
61
- }
62
- }
63
- }
64
- }
65
-
66
- /**
67
- * Save form.
68
- */
69
- public function save()
70
- {
71
- /** @var Lib\Entities\Notification[] $notifications */
72
- $notifications = Lib\Entities\Notification::query( 'n' )
73
- ->where( 'gateway', $this->gateway )
74
- ->indexBy( 'type' )
75
- ->find();
76
- foreach ( $this->types as $group ) {
77
- foreach ( $group as $type ) {
78
- $notifications[ $type ]->setFields( $this->data[ $type ] );
79
- $notifications[ $type ]->save();
80
- }
81
- }
82
- }
83
-
84
- public function load()
85
- {
86
- $notifications = Lib\Entities\Notification::query( 'n' )
87
- ->select( 'active, subject, message, copy, type' )
88
- ->where( 'gateway', $this->gateway )
89
- ->indexBy( 'type' )
90
- ->fetchArray();
91
- foreach ( $this->types as $group ) {
92
- foreach ( $group as $type ) {
93
- $notifications[ $type ]['name'] = Lib\Entities\Notification::getName( $type );
94
- $this->data[ $type ] = $notifications[ $type ];
95
- }
96
- }
97
- }
98
-
99
- /**
100
- * Render subject.
101
- *
102
- * @param string $type
103
- */
104
- public function renderSubject( $type )
105
- {
106
- printf(
107
- '<div class="form-group">
108
- <label for="%1$s">%2$s</label>
109
- <input type="text" class="form-control" id="%1$s" name="%3$s" value="%4$s" />
110
- </div>',
111
- $type . '_subject',
112
- __( 'Subject', 'bookly' ),
113
- $type . '[subject]',
114
- esc_attr( $this->data[ $type ]['subject'] )
115
- );
116
- }
117
-
118
- /**
119
- * Render message editor.
120
- *
121
- * @param string $type
122
- */
123
- public function renderEditor( $type )
124
- {
125
- $id = $type . '_message';
126
- $name = $type . '[message]';
127
- $value = $this->data[ $type ]['message'];
128
-
129
- if ( $this->gateway == 'sms' ) {
130
- printf(
131
- '<div class="form-group">
132
- <label for="%1$s">%2$s</label>
133
- <textarea rows="6" id="%1$s" name="%3$s" class="form-control">%4$s</textarea>
134
- </div>',
135
- $id,
136
- __( 'Message', 'bookly' ),
137
- $name,
138
- esc_textarea( $value )
139
- );
140
- } else {
141
- $settings = array(
142
- 'textarea_name' => $name,
143
- 'media_buttons' => false,
144
- 'editor_height' => 384,
145
- 'tinymce' => array(
146
- 'theme_advanced_buttons1' => 'formatselect,|,bold,italic,underline,|,'.
147
- 'bullist,blockquote,|,justifyleft,justifycenter'.
148
- ',justifyright,justifyfull,|,link,unlink,|'.
149
- ',spellchecker,wp_fullscreen,wp_adv'
150
- )
151
- );
152
-
153
- echo '<div class="form-group">
154
- <label>' . __( 'Message', 'bookly' ) . '</label>';
155
- wp_editor( $value, $id, $settings );
156
- echo '</div>';
157
- }
158
- }
159
-
160
- /**
161
- * Render copy.
162
- *
163
- * @param string $type
164
- */
165
- public function renderCopy( $type )
166
- {
167
- if ( in_array( $type, array( 'staff_pending_appointment', 'staff_approved_appointment', 'staff_cancelled_appointment' ) ) ) {
168
- printf(
169
- '<div class="form-group">
170
- <input name="%1$s" type="hidden" value="0">
171
- <div class="checkbox"><label for="%2$s"><input id="%2$s" name="%1$s" type="checkbox" value="1" %3$s> %4$s</label></div>
172
- </div>',
173
- $type . '[copy]',
174
- $type . '_copy',
175
- checked( $this->data[ $type ]['copy'], true, false ),
176
- __( 'Send copy to administrators', 'bookly' )
177
- );
178
- }
179
- }
180
-
181
- /**
182
- * Render sending time.
183
- *
184
- * @param string $type
185
- */
186
- public function renderSendingTime( $type )
187
- {
188
- if ( in_array( $type, array( 'staff_agenda', 'client_follow_up', 'client_reminder' ) ) ) {
189
- $cron_reminder = (array) get_option( 'ab_settings_cron_reminder' );
190
- printf(
191
- '<div class="form-group">
192
- <label for="%1$s">%2$s</label>
193
- <p class="help-block">%3$s</p>
194
- <select class="form-control ab-auto-w" name="%1$s" id="%1$s">
195
- %4$s
196
- </select>
197
- </div>',
198
- $type . '_cron_hour',
199
- __( 'Sending time', 'bookly' ),
200
- __( 'Set the time you want the notification to be sent.', 'bookly' ),
201
- implode( '', array_map( function ( $hour ) use ( $type, $cron_reminder ) {
202
- return sprintf(
203
- '<option value="%s" %s>%s</option>',
204
- $hour,
205
- selected( $cron_reminder[ $type ], $hour, false ),
206
- Lib\Utils\DateTime::buildTimeString( $hour * HOUR_IN_SECONDS, false )
207
- );
208
- }, range( 0, 23 ) ) )
209
- );
210
- }
211
- }
212
-
 
 
213
  }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Notifications\Forms;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Notifications
8
+ * @package BooklyLite\Backend\Modules\Notifications\Forms
9
+ */
10
+ class Notifications extends Lib\Base\Form
11
+ {
12
+ public $types = array(
13
+ 'single' => array(
14
+ 'client_pending_appointment',
15
+ 'staff_pending_appointment',
16
+ 'client_approved_appointment',
17
+ 'staff_approved_appointment',
18
+ 'client_cancelled_appointment',
19
+ 'staff_cancelled_appointment',
20
+ 'client_rejected_appointment',
21
+ 'staff_rejected_appointment',
22
+ 'client_new_wp_user',
23
+ 'client_reminder',
24
+ 'client_follow_up',
25
+ 'staff_agenda',
26
+ ),
27
+ 'combined' => array(
28
+ 'client_pending_appointment_cart',
29
+ 'client_approved_appointment_cart',
30
+ ),
31
+ );
32
+
33
+ public $gateway;
34
+
35
+ /**
36
+ * Constructor.
37
+ *
38
+ * @param string $gateway
39
+ */
40
+ public function __construct( $gateway = 'email' )
41
+ {
42
+ /*
43
+ * make Visual Mode as default (instead of Text Mode)
44
+ * allowed: tinymce - Visual Mode, html - Text Mode, test - no one Mode selected
45
+ */
46
+ add_filter( 'wp_default_editor', create_function( '', 'return \'tinymce\';' ) );
47
+ $this->types = apply_filters( 'bookly_prepare_notification_types', $this->types );
48
+ $this->gateway = $gateway;
49
+ if ( ! Lib\Config::areCombinedNotificationsEnabled() ) {
50
+ $this->types['combined'] = array();
51
+ }
52
+ $this->setFields( array( 'active', 'subject', 'message', 'copy', ) );
53
+ $this->load();
54
+ }
55
+
56
+ public function bind( array $_post = array(), array $files = array() )
57
+ {
58
+ foreach ( $this->types as $group ) {
59
+ foreach ( $group as $type ) {
60
+ foreach ( $this->fields as $field ) {
61
+ if ( isset ( $_post[ $type ] [ $field ] ) ) {
62
+ $this->data[ $type ][ $field ] = $_post[ $type ][ $field ];
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Save form.
71
+ */
72
+ public function save()
73
+ {
74
+ /** @var Lib\Entities\Notification[] $notifications */
75
+ $notifications = Lib\Entities\Notification::query( 'n' )
76
+ ->where( 'gateway', $this->gateway )
77
+ ->indexBy( 'type' )
78
+ ->find();
79
+ foreach ( $this->types as $group ) {
80
+ foreach ( $group as $type ) {
81
+ $notifications[ $type ]->setFields( $this->data[ $type ] );
82
+ $notifications[ $type ]->save();
83
+ }
84
+ }
85
+ }
86
+
87
+ public function load()
88
+ {
89
+ $notifications = Lib\Entities\Notification::query( 'n' )
90
+ ->select( 'active, subject, message, copy, type' )
91
+ ->where( 'gateway', $this->gateway )
92
+ ->indexBy( 'type' )
93
+ ->fetchArray();
94
+ foreach ( $this->types as $group ) {
95
+ foreach ( $group as $type ) {
96
+ $notifications[ $type ]['name'] = Lib\Entities\Notification::getName( $type );
97
+ $this->data[ $type ] = $notifications[ $type ];
98
+ }
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Render subject.
104
+ *
105
+ * @param string $type
106
+ */
107
+ public function renderSubject( $type )
108
+ {
109
+ printf(
110
+ '<div class="form-group">
111
+ <label for="%1$s">%2$s</label>
112
+ <input type="text" class="form-control" id="%1$s" name="%3$s" value="%4$s" />
113
+ </div>',
114
+ $type . '_subject',
115
+ __( 'Subject', 'bookly' ),
116
+ $type . '[subject]',
117
+ esc_attr( $this->data[ $type ]['subject'] )
118
+ );
119
+ }
120
+
121
+ /**
122
+ * Render message editor.
123
+ *
124
+ * @param string $type
125
+ */
126
+ public function renderEditor( $type )
127
+ {
128
+ $id = $type . '_message';
129
+ $name = $type . '[message]';
130
+ $value = $this->data[ $type ]['message'];
131
+
132
+ if ( $this->gateway == 'sms' ) {
133
+ printf(
134
+ '<div class="form-group">
135
+ <label for="%1$s">%2$s</label>
136
+ <textarea rows="6" id="%1$s" name="%3$s" class="form-control">%4$s</textarea>
137
+ </div>',
138
+ $id,
139
+ __( 'Message', 'bookly' ),
140
+ $name,
141
+ esc_textarea( $value )
142
+ );
143
+ } else {
144
+ $settings = array(
145
+ 'textarea_name' => $name,
146
+ 'media_buttons' => false,
147
+ 'editor_height' => 384,
148
+ 'tinymce' => array(
149
+ 'theme_advanced_buttons1' => 'formatselect,|,bold,italic,underline,|,'.
150
+ 'bullist,blockquote,|,justifyleft,justifycenter'.
151
+ ',justifyright,justifyfull,|,link,unlink,|'.
152
+ ',spellchecker,wp_fullscreen,wp_adv'
153
+ )
154
+ );
155
+
156
+ echo '<div class="form-group"><label>' . __( 'Message', 'bookly' ) . '</label>';
157
+ wp_editor( $value, $id, $settings );
158
+ echo '</div>';
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Render copy.
164
+ *
165
+ * @param string $type
166
+ */
167
+ public function renderCopy( $type )
168
+ {
169
+ if ( in_array( $type, array( 'staff_pending_appointment', 'staff_approved_appointment', 'staff_cancelled_appointment', 'staff_rejected_appointment', 'staff_pending_recurring_appointment', 'staff_approved_recurring_appointment', 'staff_cancelled_recurring_appointment' ) ) ) {
170
+ printf(
171
+ '<div class="form-group">
172
+ <input name="%1$s" type="hidden" value="0">
173
+ <div class="checkbox"><label for="%2$s"><input id="%2$s" name="%1$s" type="checkbox" value="1" %3$s> %4$s</label></div>
174
+ </div>',
175
+ $type . '[copy]',
176
+ $type . '_copy',
177
+ checked( $this->data[ $type ]['copy'], true, false ),
178
+ __( 'Send copy to administrators', 'bookly' )
179
+ );
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Render sending time.
185
+ *
186
+ * @param string $type
187
+ */
188
+ public function renderSendingTime( $type )
189
+ {
190
+ if ( in_array( $type, array( 'staff_agenda', 'client_follow_up', 'client_reminder' ) ) ) {
191
+ $cron_reminder = (array) get_option( 'bookly_cron_reminder_times' );
192
+ printf(
193
+ '<div class="form-group">
194
+ <label for="%1$s">%2$s</label>
195
+ <p class="help-block">%3$s</p>
196
+ <select class="form-control" name="%1$s" id="%1$s">
197
+ %4$s
198
+ </select>
199
+ </div>',
200
+ $type . '_cron_hour',
201
+ __( 'Sending time', 'bookly' ),
202
+ __( 'Set the time you want the notification to be sent.', 'bookly' ),
203
+ implode( '', array_map( function ( $hour ) use ( $type, $cron_reminder ) {
204
+ return sprintf(
205
+ '<option value="%s" %s>%s</option>',
206
+ $hour,
207
+ selected( $cron_reminder[ $type ], $hour, false ),
208
+ Lib\Utils\DateTime::buildTimeString( $hour * HOUR_IN_SECONDS, false )
209
+ );
210
+ }, range( 0, 23 ) ) )
211
+ );
212
+ }
213
+ }
214
+
215
  }
backend/modules/notifications/resources/js/ng-app.js CHANGED
@@ -3,25 +3,25 @@
3
 
4
  module.factory('dataSource', function($q, $rootScope) {
5
  var ds = {
6
- ab_settings_sender_name : '',
7
- ab_settings_sender_email : '',
8
  reply_to_customers : false,
9
- content_type : 'html',
10
  notifications : [],
11
  loadData : function(params) {
12
  var deferred = $q.defer();
13
  jQuery.ajax({
14
  url : ajaxurl,
15
  type : 'POST',
16
- data : jQuery.extend({ action : 'ab_get_email_notifications_data' }, params),
17
  dataType : 'json',
18
  success : function(response) {
19
  if (response.success) {
20
- ds.ab_settings_sender_name = response.data.ab_settings_sender_name;
21
- ds.ab_settings_sender_email = response.data.ab_settings_sender_email;
22
  ds.reply_to_customers = response.data.reply_to_customers;
23
- ds.content_type = response.data.sender_email;
24
- ds.notifications = response.data.ab_notifications;
25
  }
26
  $rootScope.$apply(deferred.resolve);
27
  },
3
 
4
  module.factory('dataSource', function($q, $rootScope) {
5
  var ds = {
6
+ sender_name : '',
7
+ sender_email : '',
8
  reply_to_customers : false,
9
+ send_as : 'html',
10
  notifications : [],
11
  loadData : function(params) {
12
  var deferred = $q.defer();
13
  jQuery.ajax({
14
  url : ajaxurl,
15
  type : 'POST',
16
+ data : jQuery.extend({ action : 'bookly_get_email_notifications_data' }, params),
17
  dataType : 'json',
18
  success : function(response) {
19
  if (response.success) {
20
+ ds.sender_name = response.data.sender_name;
21
+ ds.sender_email = response.data.sender_email;
22
  ds.reply_to_customers = response.data.reply_to_customers;
23
+ ds.send_as = response.data.send_as;
24
+ ds.notifications = response.data.notifications;
25
  }
26
  $rootScope.$apply(deferred.resolve);
27
  },
backend/modules/notifications/resources/js/notification.js CHANGED
@@ -1,34 +1,34 @@
1
- jQuery(function($) {
2
-
3
- Ladda.bind( 'button[type=submit]' );
4
-
5
- // menu fix for WP 3.8.1
6
- $('#toplevel_page_ab-system > ul').css('margin-left', '0px');
7
-
8
- /* exclude checkboxes in form */
9
- var $checkboxes = $('.bookly-js-collapse .panel-title > input:checkbox');
10
-
11
- $checkboxes.change(function () {
12
- $(this).parents('.panel-heading').next().collapse(this.checked ? 'show' : 'hide');
13
- });
14
-
15
- $('[data-toggle="popover"]').popover({
16
- html: true,
17
- placement: 'top',
18
- trigger: 'hover',
19
- template: '<div class="popover bookly-font-xs" style="width: 220px" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
20
- });
21
-
22
- booklyAlert(BooklyL10n.alert);
23
-
24
- $(':checkbox').on('change', function () {
25
- if ($(this).prop('checked')) {
26
- booklyAlert({error: [BooklyL10n.limitations]});
27
- $(this).prop('checked', false).prop('readonly', true);
28
- }
29
- });
30
-
31
- $('.ab-test-email-notifications').on('click',function () {
32
- booklyAlert({error: [BooklyL10n.limitations]});
33
- });
34
  });
1
+ jQuery(function($) {
2
+
3
+ Ladda.bind( 'button[type=submit]' );
4
+
5
+ // menu fix for WP 3.8.1
6
+ $('#toplevel_page_ab-system > ul').css('margin-left', '0px');
7
+
8
+ /* exclude checkboxes in form */
9
+ var $checkboxes = $('.bookly-js-collapse .panel-title > input:checkbox');
10
+
11
+ $checkboxes.change(function () {
12
+ $(this).parents('.panel-heading').next().collapse(this.checked ? 'show' : 'hide');
13
+ });
14
+
15
+ $('[data-toggle="popover"]').popover({
16
+ html: true,
17
+ placement: 'top',
18
+ trigger: 'hover',
19
+ template: '<div class="popover bookly-font-xs" style="width: 220px" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
20
+ });
21
+
22
+ booklyAlert(BooklyL10n.alert);
23
+
24
+ $(':checkbox').on('change', function () {
25
+ if ($(this).prop('checked')) {
26
+ booklyAlert({error: [BooklyL10n.limitations]});
27
+ $(this).prop('checked', false).prop('readonly', true);
28
+ }
29
+ });
30
+
31
+ $('.ab-test-email-notifications').on('click',function () {
32
+ booklyAlert({error: [BooklyL10n.limitations]});
33
+ });
34
  });
backend/modules/notifications/templates/_codes.php CHANGED
@@ -6,6 +6,7 @@ $codes = array(
6
  array( 'code' => 'approve_appointment_url', 'description' => esc_html__( 'URL of approve appointment link (to use inside <a> tag)', 'bookly' ) ),
7
  array( 'code' => 'cancel_appointment', 'description' => __( 'cancel appointment link', 'bookly' ) ),
8
  array( 'code' => 'cancel_appointment_url', 'description' => esc_html__( 'URL of cancel appointment link (to use inside <a> tag)', 'bookly' ) ),
 
9
  array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) ),
10
  array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) ),
11
  array( 'code' => 'client_name', 'description' => __( 'name of client', 'bookly' ) ),
@@ -30,4 +31,4 @@ $codes = array(
30
  array( 'code' => 'staff_photo', 'description' => __( 'photo of staff', 'bookly' ) ),
31
  array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) ),
32
  );
33
- \BooklyLite\Lib\Utils\Common::Codes( apply_filters( 'bookly_notification_short_codes', $codes ) );
6
  array( 'code' => 'approve_appointment_url', 'description' => esc_html__( 'URL of approve appointment link (to use inside <a> tag)', 'bookly' ) ),
7
  array( 'code' => 'cancel_appointment', 'description' => __( 'cancel appointment link', 'bookly' ) ),
8
  array( 'code' => 'cancel_appointment_url', 'description' => esc_html__( 'URL of cancel appointment link (to use inside <a> tag)', 'bookly' ) ),
9
+ array( 'code' => 'cancellation_reason', 'description' => __( 'reason you mentioned while deleting appointment', 'bookly' ) ),
10
  array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) ),
11
  array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) ),
12
  array( 'code' => 'client_name', 'description' => __( 'name of client', 'bookly' ) ),
31
  array( 'code' => 'staff_photo', 'description' => __( 'photo of staff', 'bookly' ) ),
32
  array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) ),
33
  );
34
+ \BooklyLite\Lib\Utils\Common::Codes( apply_filters( 'bookly_prepare_notification_short_codes', $codes ) );
backend/modules/notifications/templates/_codes_cart.php CHANGED
@@ -1,16 +1,16 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- $codes = array(
3
- array( 'code' => 'cart_info', 'description' => __( 'cart information', 'bookly' ) ),
4
- array( 'code' => 'cart_info_c', 'description' => __( 'cart information with cancel', 'bookly' ) ),
5
- array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) ),
6
- array( 'code' => 'client_name', 'description' => __( 'name of client', 'bookly' ) ),
7
- array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) ),
8
- array( 'code' => 'company_name', 'description' => __( 'name of company', 'bookly' ) ),
9
- array( 'code' => 'company_logo', 'description' => __( 'company logo', 'bookly' ) ),
10
- array( 'code' => 'company_address', 'description' => __( 'address of company', 'bookly' ) ),
11
- array( 'code' => 'company_phone', 'description' => __( 'company phone', 'bookly' ) ),
12
- array( 'code' => 'company_website', 'description' => __( 'company web-site address', 'bookly' ) ),
13
- array( 'code' => 'payment_type', 'description' => __( 'payment type', 'bookly' ) ),
14
- array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) ),
15
- );
16
- \BooklyLite\Lib\Utils\Common::Codes( apply_filters( 'bookly_deposit_notification_short_codes', $codes ) );
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ $codes = array(
3
+ array( 'code' => 'cart_info', 'description' => __( 'cart information', 'bookly' ) ),
4
+ array( 'code' => 'cart_info_c', 'description' => __( 'cart information with cancel', 'bookly' ) ),
5
+ array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) ),
6
+ array( 'code' => 'client_name', 'description' => __( 'name of client', 'bookly' ) ),
7
+ array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) ),
8
+ array( 'code' => 'company_name', 'description' => __( 'name of company', 'bookly' ) ),
9
+ array( 'code' => 'company_logo', 'description' => __( 'company logo', 'bookly' ) ),
10
+ array( 'code' => 'company_address', 'description' => __( 'address of company', 'bookly' ) ),
11
+ array( 'code' => 'company_phone', 'description' => __( 'company phone', 'bookly' ) ),
12
+ array( 'code' => 'company_website', 'description' => __( 'company web-site address', 'bookly' ) ),
13
+ array( 'code' => 'payment_type', 'description' => __( 'payment type', 'bookly' ) ),
14
+ array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) ),
15
+ );
16
+ \BooklyLite\Lib\Utils\Common::Codes( apply_filters( 'bookly_prepare_cart_notification_short_codes', $codes ) );
backend/modules/notifications/templates/_test_email_notifications_modal.php CHANGED
@@ -13,34 +13,40 @@
13
  </div>
14
  <div class="modal-body">
15
  <div class="form-group">
16
- <label for="ab_to_mail"><?php _e( 'To email', 'bookly' ) ?></label>
17
- <input id="ab_to_mail" class="form-control" type="text" ng-model="toEmail"/>
18
  </div>
19
  <div class="row">
20
  <div class="col-sm-6">
21
  <div class="form-group">
22
- <label for="ab_sender_name"><?php _e( 'Sender name', 'bookly' ) ?></label>
23
- <input id="ab_sender_name" class="form-control" type="text" ng-model="dataSource.ab_settings_sender_name"/>
24
  </div>
25
  </div>
26
  <div class="col-sm-6">
27
  <div class="form-group">
28
- <label for="ab_sender_email"><?php _e( 'Sender email', 'bookly' ) ?></label>
29
- <input id="ab_sender_email" class="form-control" type="text" ng-model="dataSource.ab_settings_sender_email"/>
30
  </div>
31
  </div>
32
  </div>
33
  <div class="row">
34
  <div class="col-sm-6">
35
  <div class="form-group">
36
- <label><?php _e( 'Reply directly to customers', 'bookly' ) ?></label>
37
- <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'ab_email_notification_reply_to_customers' ) ?>
 
 
 
38
  </div>
39
  </div>
40
  <div class="col-sm-6">
41
  <div class="form-group">
42
- <label><?php _e( 'Send emails as', 'bookly' ) ?></label>
43
- <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'ab_email_content_type', array( 't' => array( 'html', __( 'HTML', 'bookly' ) ), 'f' => array( 'plain', __( 'Text', 'bookly' ) ) ) ) ?>
 
 
 
44
  </div>
45
  </div>
46
  </div>
@@ -56,7 +62,7 @@
56
  <li class="bookly-padding-horizontal-md">
57
  <div class="checkbox">
58
  <label>
59
- <input type="checkbox" id="bookly-check-all-entities" ng-model="allNotifications" ng-change="toggleAllNotifications()">
60
  <?php _e( 'All templates', 'bookly' ) ?>
61
  </label>
62
  </div>
@@ -66,12 +72,11 @@
66
  <li class="bookly-padding-horizontal-md" ng-repeat="notification in dataSource.notifications">
67
  <div class="checkbox">
68
  <label>
69
- <input type="checkbox" id="" value="0" data-staff_name="" class="bookly-js-check-entity"
70
- ng-model="notification.active" ng-true-value="'1'" ng-false-value="'0'" ng-change="notificationChecked()"/>
71
  {{notification.name}}
72
  </label>
73
  </div>
74
-
75
  </li>
76
  </ul>
77
  </div>
13
  </div>
14
  <div class="modal-body">
15
  <div class="form-group">
16
+ <label for="bookly_test_to_email"><?php _e( 'To email', 'bookly' ) ?></label>
17
+ <input id="bookly_test_to_email" class="form-control" type="text" ng-model="toEmail"/>
18
  </div>
19
  <div class="row">
20
  <div class="col-sm-6">
21
  <div class="form-group">
22
+ <label for="bookly_test_sender_name"><?php _e( 'Sender name', 'bookly' ) ?></label>
23
+ <input id="bookly_test_sender_name" class="form-control" type="text" ng-model="dataSource.sender_name" />
24
  </div>
25
  </div>
26
  <div class="col-sm-6">
27
  <div class="form-group">
28
+ <label for="bookly_test_sender_email"><?php _e( 'Sender email', 'bookly' ) ?></label>
29
+ <input id="bookly_test_sender_email" class="form-control" type="text" ng-model="dataSource.sender_email" />
30
  </div>
31
  </div>
32
  </div>
33
  <div class="row">
34
  <div class="col-sm-6">
35
  <div class="form-group">
36
+ <label for="bookly_test_reply_to_customers"><?php _e( 'Reply directly to customers', 'bookly' ) ?></label>
37
+ <select id="bookly_test_reply_to_customers" class="form-control" ng-model="dataSource.reply_to_customers">
38
+ <option value="0"><?php _e( 'Disabled', 'bookly' ) ?></option>
39
+ <option value="1"><?php _e( 'Enabled', 'bookly' ) ?></option>
40
+ </select>
41
  </div>
42
  </div>
43
  <div class="col-sm-6">
44
  <div class="form-group">
45
+ <label for="bookly_test_send_as"><?php _e( 'Send emails as', 'bookly' ) ?></label>
46
+ <select id="bookly_test_send_as" class="form-control" ng-model="dataSource.send_as">
47
+ <option value="html"><?php _e( 'HTML', 'bookly' ) ?></option>
48
+ <option value="text"><?php _e( 'Text', 'bookly' ) ?></option>
49
+ </select>
50
  </div>
51
  </div>
52
  </div>
62
  <li class="bookly-padding-horizontal-md">
63
  <div class="checkbox">
64
  <label>
65
+ <input type="checkbox" id="bookly-check-all-entities" ng-model="allNotifications" ng-change="toggleAllNotifications()" />
66
  <?php _e( 'All templates', 'bookly' ) ?>
67
  </label>
68
  </div>
72
  <li class="bookly-padding-horizontal-md" ng-repeat="notification in dataSource.notifications">
73
  <div class="checkbox">
74
  <label>
75
+ <input type="checkbox" ng-model="notification.active"
76
+ ng-true-value="'1'" ng-false-value="'0'" ng-change="notificationChecked()" />
77
  {{notification.name}}
78
  </label>
79
  </div>
 
80
  </li>
81
  </ul>
82
  </div>
backend/modules/notifications/templates/index.php CHANGED
@@ -1,151 +1,143 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- $ab_settings_sender_name = get_option( 'ab_settings_sender_name' ) == '' ?
3
- get_option( 'blogname' ) : get_option( 'ab_settings_sender_name' );
4
- $ab_settings_sender_email = get_option( 'ab_settings_sender_email' ) == '' ?
5
- get_option( 'admin_email' ) : get_option( 'ab_settings_sender_email' );
6
- $collapse_id = 0;
7
- $form_data = $form->getData();
8
- ?>
9
- <div id="bookly-tbs" class="wrap">
10
- <div class="bookly-tbs-body" ng-app="notifications">
11
- <div class="page-header text-right clearfix">
12
- <div class="bookly-page-title">
13
- <?php _e( 'Email Notifications', 'bookly' ) ?>
14
- </div>
15
- </div>
16
- <form method="post" id="form1" action="">
17
- <div class="panel panel-default bookly-main" ng-controller="emailNotifications">
18
- <div class="panel-body">
19
- <div class="row">
20
- <div class="col-md-6">
21
- <div class="form-group">
22
- <label for="sender_name"><?php _e( 'Sender name', 'bookly' ) ?></label>
23
- <input id="sender_name" name="ab_settings_sender_name" class="form-control" type="text" value="<?php echo esc_attr( $ab_settings_sender_name ) ?>">
24
- </div>
25
- </div>
26
- <div class="col-md-6">
27
- <div class="form-group">
28
- <label for="sender_email"><?php _e( 'Sender email', 'bookly' ) ?></label>
29
- <input id="sender_email" name="ab_settings_sender_email" class="form-control ab-inline-block ab-auto-w ab-sender" type="text" value="<?php echo esc_attr( $ab_settings_sender_email ) ?>">
30
- </div>
31
- </div>
32
- <div class="clearfix visible-md-block"></div>
33
- <div class="col-md-6">
34
- <div class="form-group">
35
- <label for="ab_email_notification_reply_to_customers">
36
- <?php _e( 'Reply directly to customers', 'bookly' ) ?>
37
- </label>
38
- <p class="help-block" style="display: none;"><?php _e( 'If this option is enabled then the email address of the customer is used as a sender email address for notifications sent to staff members and administrators.', 'bookly' ) ?></p>
39
- <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'ab_email_notification_reply_to_customers' ) ?>
40
- </div>
41
- </div>
42
- <div class="col-md-6">
43
- <div class="form-group">
44
- <label for="ab_email_content_type">
45
- <?php _e( 'Send emails as', 'bookly' ) ?>
46
- </label>
47
- <p class="help-block" style="display: none;"><?php _e( 'HTML allows formatting, colors, fonts, positioning, etc. With Text you must use Text mode of rich-text editors below. On some servers only text emails are sent successfully.', 'bookly' ) ?></p>
48
- <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'ab_email_content_type', array( 't' => array( 'html', __( 'HTML', 'bookly' ) ), 'f' => array( 'plain', __( 'Text', 'bookly' ) ) ) ) ?>
49
- </div>
50
- </div>
51
- </div>
52
- <?php if ( $form->types['combined'] ) : ?>
53
- <h4 class="bookly-block-head bookly-color-gray"><?php _e( 'Single', 'bookly' ) ?></h4>
54
- <?php endif ?>
55
- <div class="panel-group bookly-margin-vertical-xlg" id="single">
56
- <?php foreach ( $form->types['single'] as $type ) : ?>
57
- <div class="panel panel-default bookly-js-collapse">
58
- <div class="panel-heading" role="tab">
59
- <div class="checkbox bookly-margin-remove">
60
- <label>
61
- <input name="<?php echo $type ?>[active]" value="0" type="checkbox" checked="checked" class="hidden">
62
- <input id="<?php echo $type ?>_active" name="<?php echo $type ?>[active]" value="1" type="checkbox" <?php checked( $form_data[ $type ]['active'] ) ?>>
63
- <a href="#collapse_<?php echo ++ $collapse_id ?>" class="collapsed panel-title" role="button" data-toggle="collapse" data-parent="#single">
64
- <?php echo $form_data[ $type ]['name'] ?>
65
- </a>
66
- </label>
67
- </div>
68
- </div>
69
- <div id="collapse_<?php echo $collapse_id ?>" class="panel-collapse collapse">
70
- <div class="panel-body">
71
-
72
- <?php $form->renderSendingTime( $type ) ?>
73
- <?php $form->renderSubject( $type ) ?>
74
- <?php $form->renderEditor( $type ) ?>
75
- <?php $form->renderCopy( $type ) ?>
76
-
77
- <div class="form-group">
78
- <label><?php _e( 'Codes', 'bookly' ) ?></label>
79
- <?php switch ( $type ) :
80
- case 'staff_agenda': include '_codes_staff_agenda.php'; break;
81
- case 'client_new_wp_user': include '_codes_client_new_wp_user.php'; break;
82
- default: include '_codes.php';
83
- endswitch ?>
84
- </div>
85
-
86
- </div>
87
- </div>
88
- </div>
89
- <?php endforeach ?>
90
- </div>
91
-
92
- <?php if ( $form->types['combined'] ) : ?>
93
- <h4 class="bookly-block-head bookly-color-gray"><?php _e( 'Combined', 'bookly' ) ?></h4>
94
- <div class="panel-group bookly-margin-vertical-xlg" id="combined">
95
- <?php foreach ( $form->types['combined'] as $type ) : ?>
96
- <div class="panel panel-default bookly-js-collapse">
97
- <div class="panel-heading" role="tab">
98
- <div class="checkbox bookly-margin-remove">
99
- <label>
100
- <input name="<?php echo $type ?>[active]" value="0" type="checkbox" checked="checked" class="hidden">
101
- <input id="<?php echo $type ?>_active" name="<?php echo $type ?>[active]" value="1" type="checkbox" <?php checked( $form_data[ $type ]['active'] ) ?>>
102
- <a href="#collapse_<?php echo ++ $collapse_id ?>" class="collapsed panel-title" role="button" data-toggle="collapse" data-parent="#combined">
103
- <?php echo $form_data[ $type ]['name'] ?>
104
- </a>
105
- </label>
106
- </div>
107
- </div>
108
- <div id="collapse_<?php echo $collapse_id ?>" class="panel-collapse collapse">
109
- <div class="panel-body">
110
- <?php $form->renderSubject( $type ) ?>
111
- <?php $form->renderEditor( $type ) ?>
112
- <?php $form->renderCopy( $type ) ?>
113
-
114
- <div class="form-group">
115
- <label><?php _e( 'Codes', 'bookly' ) ?></label>
116
- <?php include '_codes_cart.php' ?>
117
-
118
- </div>
119
- </div>
120
- </div>
121
- </div>
122
- <?php endforeach ?>
123
- </div>
124
- <?php endif ?>
125
-
126
- <div class="alert alert-info">
127
- <?php if ( is_multisite() ) : ?>
128
- <p><?php printf( __( 'To send scheduled notifications please refer to <a href="%1$s">Bookly Multisite</a> add-on <a href="%2$s">message</a>.', 'bookly' ), 'http://codecanyon.net/item/bookly-multisite-addon/13903524?ref=ladela', network_admin_url( 'admin.php?page=bookly-multisite-network' ) ) ?></p>
129
- <?php else : ?>
130
- <p><?php _e( 'To send scheduled notifications please execute the following script hourly with your cron:', 'bookly' ) ?></p><br />
131
- <code class="bookly-text-wrap">php -f <?php echo $cron_path ?></code>
132
- <?php endif ?>
133
- </div>
134
- </div>
135
-
136
- <div class="panel-footer">
137
- <?php \BooklyLite\Lib\Utils\Common::submitButton() ?>
138
- <?php \BooklyLite\Lib\Utils\Common::resetButton() ?>
139
-
140
- <div class="pull-left">
141
- <button type="button" class="btn btn-default ab-test-email-notifications btn-lg">
142
- <?php _e( 'Test Email Notifications', 'bookly' ) ?>
143
- </button>
144
- </div>
145
- </div>
146
- </div>
147
- </form>
148
-
149
- <?php include '_test_email_notifications_modal.php' ?>
150
- </div>
151
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ $bookly_email_sender_name = get_option( 'bookly_email_sender_name' ) == '' ?
3
+ get_option( 'blogname' ) : get_option( 'bookly_email_sender_name' );
4
+ $bookly_email_sender = get_option( 'bookly_email_sender' ) == '' ?
5
+ get_option( 'admin_email' ) : get_option( 'bookly_email_sender' );
6
+ $collapse_id = 0;
7
+ $form_data = $form->getData();
8
+ ?>
9
+ <div id="bookly-tbs" class="wrap">
10
+ <div class="bookly-tbs-body" ng-app="notifications">
11
+ <div class="page-header text-right clearfix">
12
+ <div class="bookly-page-title">
13
+ <?php _e( 'Email Notifications', 'bookly' ) ?>
14
+ </div>
15
+ <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
16
+ </div>
17
+ <form method="post" action="">
18
+ <div class="panel panel-default bookly-main" ng-controller="emailNotifications">
19
+ <div class="panel-body">
20
+ <div class="row">
21
+ <div class="col-md-6">
22
+ <div class="form-group">
23
+ <label for="sender_name"><?php _e( 'Sender name', 'bookly' ) ?></label>
24
+ <input id="sender_name" name="bookly_email_sender_name" class="form-control" type="text" value="<?php echo esc_attr( $bookly_email_sender_name ) ?>">
25
+ </div>
26
+ </div>
27
+ <div class="col-md-6">
28
+ <div class="form-group">
29
+ <label for="sender_email"><?php _e( 'Sender email', 'bookly' ) ?></label>
30
+ <input id="sender_email" name="bookly_email_sender" class="form-control ab-sender" type="text" value="<?php echo esc_attr( $bookly_email_sender ) ?>">
31
+ </div>
32
+ </div>
33
+ <div class="col-md-6">
34
+ <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'bookly_email_send_as', __( 'Send emails as', 'bookly' ), __( 'HTML allows formatting, colors, fonts, positioning, etc. With Text you must use Text mode of rich-text editors below. On some servers only text emails are sent successfully.', 'bookly' ),
35
+ array( array( 'html', __( 'HTML', 'bookly' ) ), array( 'text', __( 'Text', 'bookly' ) ) )
36
+ ) ?>
37
+ </div>
38
+ <div class="col-md-6">
39
+ <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'bookly_email_reply_to_customers', __( 'Reply directly to customers', 'bookly' ), __( 'If this option is enabled then the email address of the customer is used as a sender email address for notifications sent to staff members and administrators.', 'bookly' ) ) ?>
40
+ </div>
41
+ </div>
42
+ <?php if ( $form->types['combined'] || \BooklyLite\Lib\Utils\Common::isPluginActive( 'bookly-addon-recurring-appointments/main.php' ) ) : ?>
43
+ <h4 class="bookly-block-head bookly-color-gray"><?php _e( 'Single', 'bookly' ) ?></h4>
44
+ <?php endif ?>
45
+ <div class="panel-group bookly-margin-vertical-xlg" id="single">
46
+ <?php foreach ( $form->types['single'] as $type ) : ?>
47
+ <div class="panel panel-default bookly-js-collapse">
48
+ <div class="panel-heading" role="tab">
49
+ <div class="checkbox bookly-margin-remove">
50
+ <label>
51
+ <input name="<?php echo $type ?>[active]" value="0" type="checkbox" checked="checked" class="hidden">
52
+ <input id="<?php echo $type ?>_active" name="<?php echo $type ?>[active]" value="1" type="checkbox" <?php checked( $form_data[ $type ]['active'] ) ?>>
53
+ <a href="#collapse_<?php echo ++ $collapse_id ?>" class="collapsed panel-title" role="button" data-toggle="collapse" data-parent="#single">
54
+ <?php echo $form_data[ $type ]['name'] ?>
55
+ </a>
56
+ </label>
57
+ </div>
58
+ </div>
59
+ <div id="collapse_<?php echo $collapse_id ?>" class="panel-collapse collapse">
60
+ <div class="panel-body">
61
+
62
+ <?php $form->renderSendingTime( $type ) ?>
63
+ <?php $form->renderSubject( $type ) ?>
64
+ <?php $form->renderEditor( $type ) ?>
65
+ <?php $form->renderCopy( $type ) ?>
66
+
67
+ <div class="form-group">
68
+ <label><?php _e( 'Codes', 'bookly' ) ?></label>
69
+ <?php switch ( $type ) :
70
+ case 'staff_agenda': include '_codes_staff_agenda.php'; break;
71
+ case 'client_new_wp_user': include '_codes_client_new_wp_user.php'; break;
72
+ default: include '_codes.php';
73
+ endswitch ?>
74
+ </div>
75
+
76
+ </div>
77
+ </div>
78
+ </div>
79
+ <?php endforeach ?>
80
+ </div>
81
+
82
+ <?php if ( $form->types['combined'] ) : ?>
83
+ <h4 class="bookly-block-head bookly-color-gray"><?php _e( 'Combined', 'bookly' ) ?></h4>
84
+ <div class="panel-group bookly-margin-vertical-xlg" id="combined">
85
+ <?php foreach ( $form->types['combined'] as $type ) : ?>
86
+ <div class="panel panel-default bookly-js-collapse">
87
+ <div class="panel-heading" role="tab">
88
+ <div class="checkbox bookly-margin-remove">
89
+ <label>
90
+ <input name="<?php echo $type ?>[active]" value="0" type="checkbox" checked="checked" class="hidden">
91
+ <input id="<?php echo $type ?>_active" name="<?php echo $type ?>[active]" value="1" type="checkbox" <?php checked( $form_data[ $type ]['active'] ) ?>>
92
+ <a href="#collapse_<?php echo ++ $collapse_id ?>" class="collapsed panel-title" role="button" data-toggle="collapse" data-parent="#combined">
93
+ <?php echo $form_data[ $type ]['name'] ?>
94
+ </a>
95
+ </label>
96
+ </div>
97
+ </div>
98
+ <div id="collapse_<?php echo $collapse_id ?>" class="panel-collapse collapse">
99
+ <div class="panel-body">
100
+ <?php $form->renderSubject( $type ) ?>
101
+ <?php $form->renderEditor( $type ) ?>
102
+ <?php $form->renderCopy( $type ) ?>
103
+
104
+ <div class="form-group">
105
+ <label><?php _e( 'Codes', 'bookly' ) ?></label>
106
+ <?php include '_codes_cart.php' ?>
107
+
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ <?php endforeach ?>
113
+ </div>
114
+ <?php endif ?>
115
+
116
+ <?php do_action( 'bookly_render_email_notifications', $form ) ?>
117
+
118
+ <div class="alert alert-info">
119
+ <?php if ( is_multisite() ) : ?>
120
+ <p><?php printf( __( 'To send scheduled notifications please refer to <a href="%1$s">Bookly Multisite</a> add-on <a href="%2$s">message</a>.', 'bookly' ), 'http://codecanyon.net/item/bookly-multisite-addon/13903524?ref=ladela', network_admin_url( 'admin.php?page=bookly-multisite-network' ) ) ?></p>
121
+ <?php else : ?>
122
+ <p><?php _e( 'To send scheduled notifications please execute the following script hourly with your cron:', 'bookly' ) ?></p><br />
123
+ <code class="bookly-text-wrap">php -f <?php echo $cron_path ?></code>
124
+ <?php endif ?>
125
+ </div>
126
+ </div>
127
+
128
+ <div class="panel-footer">
129
+ <?php \BooklyLite\Lib\Utils\Common::submitButton() ?>
130
+ <?php \BooklyLite\Lib\Utils\Common::resetButton() ?>
131
+
132
+ <div class="pull-left">
133
+ <button type="button" class="btn btn-default ab-test-email-notifications btn-lg">
134
+ <?php _e( 'Test Email Notifications', 'bookly' ) ?>
135
+ </button>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ </form>
140
+
141
+ <?php include '_test_email_notifications_modal.php' ?>
142
+ </div>
 
 
 
 
 
 
 
 
143
  </div>
backend/modules/payments/Components.php CHANGED
@@ -25,7 +25,7 @@ class Components extends Lib\Base\Components
25
  'js/spin.min.js' => array( 'jquery' ),
26
  'js/ladda.min.js' => array( 'jquery' ),
27
  ),
28
- 'module' => array( 'js/ng-payment_details_dialog.js' => array( 'ab-angular.min.js' ), ),
29
  ) );
30
 
31
  $this->render( '_payment_details_dialog' );
25
  'js/spin.min.js' => array( 'jquery' ),
26
  'js/ladda.min.js' => array( 'jquery' ),
27
  ),
28
+ 'module' => array( 'js/ng-payment_details_dialog.js' => array( 'bookly-angular.min.js' ), ),
29
  ) );
30
 
31
  $this->render( '_payment_details_dialog' );
backend/modules/payments/Controller.php CHANGED
@@ -1,263 +1,266 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Payments;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Payments
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- /**
13
- * @return array
14
- */
15
- protected function getPermissions()
16
- {
17
- return array(
18
- 'executeGetPaymentDetails' => 'user',
19
- 'executeCompletePayment' => 'user',
20
- );
21
- }
22
-
23
- public function index()
24
- {
25
- /** @var \WP_Locale $wp_locale */
26
- global $wp_locale;
27
-
28
- $this->enqueueStyles( array(
29
- 'backend' => array(
30
- 'bootstrap/css/bootstrap-theme.min.css',
31
- 'css/daterangepicker.css',
32
- ),
33
- 'frontend' => array( 'css/ladda.min.css', ),
34
- ) );
35
-
36
- $this->enqueueScripts( array(
37
- 'backend' => array(
38
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
39
- 'js/datatables.min.js' => array( 'jquery' ),
40
- 'js/moment.min.js',
41
- 'js/daterangepicker.js' => array( 'jquery' ),
42
- 'js/chosen.jquery.min.js' => array( 'jquery' ),
43
- ),
44
- 'frontend' => array(
45
- 'js/spin.min.js' => array( 'jquery' ),
46
- 'js/ladda.min.js' => array( 'jquery' ),
47
- ),
48
- 'module' => array( 'js/payments.js' => array( 'ab-datatables.min.js', 'ab-ng-payment_details_dialog.js' ) ),
49
- ) );
50
-
51
- wp_localize_script( 'ab-daterangepicker.js', 'BooklyL10n', array(
52
- 'today' => __( 'Today', 'bookly' ),
53
- 'yesterday' => __( 'Yesterday', 'bookly' ),
54
- 'last_7' => __( 'Last 7 Days', 'bookly' ),
55
- 'last_30' => __( 'Last 30 Days', 'bookly' ),
56
- 'this_month' => __( 'This Month', 'bookly' ),
57
- 'last_month' => __( 'Last Month', 'bookly' ),
58
- 'custom_range' => __( 'Custom Range', 'bookly' ),
59
- 'apply' => __( 'Apply', 'bookly' ),
60
- 'cancel' => __( 'Cancel', 'bookly' ),
61
- 'to' => __( 'To', 'bookly' ),
62
- 'from' => __( 'From', 'bookly' ),
63
- 'calendar' => array(
64
- 'longMonths' => array_values( $wp_locale->month ),
65
- 'shortMonths' => array_values( $wp_locale->month_abbrev ),
66
- 'longDays' => array_values( $wp_locale->weekday ),
67
- 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
68
- ),
69
- 'startOfWeek' => (int) get_option( 'start_of_week' ),
70
- 'mjsDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
71
- 'zeroRecords' => __( 'No payments for selected period and criteria.', 'bookly' ),
72
- 'processing' => __( 'Processing...', 'bookly' ),
73
- 'details' => __( 'Details', 'bookly' ),
74
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
75
- ) );
76
-
77
- $types = array(
78
- Lib\Entities\Payment::TYPE_LOCAL,
79
- Lib\Entities\Payment::TYPE_2CHECKOUT,
80
- Lib\Entities\Payment::TYPE_PAYPAL,
81
- Lib\Entities\Payment::TYPE_AUTHORIZENET,
82
- Lib\Entities\Payment::TYPE_STRIPE,
83
- Lib\Entities\Payment::TYPE_PAYULATAM,
84
- Lib\Entities\Payment::TYPE_PAYSON,
85
- Lib\Entities\Payment::TYPE_MOLLIE,
86
- Lib\Entities\Payment::TYPE_COUPON,
87
- );
88
- $providers = Lib\Entities\Staff::query()->select( 'id, full_name' )->sortBy( 'full_name' )->fetchArray();
89
- $services = Lib\Entities\Service::query()->select( 'id, title' )->sortBy( 'title' )->fetchArray();
90
-
91
- $this->render( 'index', compact( 'types', 'providers', 'services' ) );
92
- }
93
-
94
- /**
95
- * Get payments.
96
- */
97
- public function executeGetPayments()
98
- {
99
- $columns = $this->getParameter( 'columns' );
100
- $order = $this->getParameter( 'order' );
101
- $filter = $this->getParameter( 'filter' );
102
-
103
- $query = Lib\Entities\Payment::query( 'p' )
104
- ->select( 'p.id, p.created, p.type, p.paid, p.total, p.status, p.details, c.name customer, st.full_name provider, s.title service, a.start_date' )
105
- ->leftJoin( 'CustomerAppointment', 'ca', 'ca.payment_id = p.id' )
106
- ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
107
- ->leftJoin( 'Appointment', 'a', 'a.id = ca.appointment_id' )
108
- ->leftJoin( 'Service', 's', 's.id = COALESCE(ca.compound_service_id, a.service_id)' )
109
- ->leftJoin( 'Staff', 'st', 'st.id = a.staff_id' )
110
- ->groupBy( 'p.id' );
111
-
112
- // Filters.
113
- list ( $start, $end ) = explode( ' - ', $filter['created'], 2 );
114
- $end = date( 'Y-m-d', strtotime( '+1 day', strtotime( $end ) ) );
115
-
116
- $query->whereBetween( 'p.created', $start, $end );
117
-
118
- if ( $filter['type'] != -1 ) {
119
- $query->where( 'p.type', $filter['type'] );
120
- }
121
-
122
- if ( $filter['staff'] != -1 ) {
123
- $query->where( 'st.id', $filter['staff'] );
124
- }
125
-
126
- if ( $filter['service'] != -1 ) {
127
- $query->where( 's.id', $filter['service'] );
128
- }
129
-
130
- foreach ( $order as $sort_by ) {
131
- $query->sortBy( $columns[ $sort_by['column'] ]['data'] )
132
- ->order( $sort_by['dir'] == 'desc' ? Lib\Query::ORDER_DESCENDING : Lib\Query::ORDER_ASCENDING );
133
- }
134
-
135
- $payments = $query->fetchArray();
136
-
137
- $data = array();
138
- $total = 0;
139
- foreach ( $payments as $payment ) {
140
- $details = json_decode( $payment['details'], true );
141
- $multiple = count( $details['items'] ) > 1
142
- ? ' <span class="glyphicon glyphicon-shopping-cart" title="' . esc_attr( __( 'See details for more items', 'bookly' ) ) . '"></span>'
143
- : '' ;
144
-
145
- $paid_title = Lib\Utils\Common::formatPrice( $payment['paid'] );
146
- if ( $payment['paid'] != $payment['total'] ) {
147
- $paid_title = sprintf( __( '%s of %s', 'bookly' ), $paid_title, Lib\Utils\Common::formatPrice( $payment['total'] ) );
148
- }
149
-
150
- $data[] = array(
151
- 'id' => $payment['id'],
152
- 'created' => Lib\Utils\DateTime::formatDateTime( $payment['created'] ),
153
- 'type' => Lib\Entities\Payment::typeToString( $payment['type'] ),
154
- 'customer' => $payment['customer'] ?: $details['customer'],
155
- 'provider' => ( $payment['provider'] ?: $details['items'][0]['staff_name'] ) . $multiple,
156
- 'service' => ( $payment['service'] ?: $details['items'][0]['service_name'] ) . $multiple,
157
- 'start_date' => ( $payment['start_date']
158
- ? Lib\Utils\DateTime::formatDateTime( $payment['start_date'] )
159
- : Lib\Utils\DateTime::formatDateTime( $details['items'][0]['appointment_date'] ) ) . $multiple,
160
- 'paid' => $paid_title,
161
- 'status' => Lib\Entities\Payment::statusToString( $payment['status'] ),
162
-
163
- );
164
-
165
- $total += $payment['paid'];
166
- }
167
-
168
- wp_send_json( array(
169
- 'draw' => ( int ) $this->getParameter( 'draw' ),
170
- 'recordsTotal' => count( $data ),
171
- 'recordsFiltered' => count( $data ),
172
- 'data' => $data,
173
- 'total' => Lib\Utils\Common::formatPrice( $total ),
174
- ) );
175
- }
176
-
177
- /**
178
- * Get payment details.
179
- *
180
- * @throws \Exception
181
- */
182
- public function executeGetPaymentDetails()
183
- {
184
- $data = array();
185
- $payment = Lib\Entities\Payment::query( 'p' )
186
- ->select( 'p.total,
187
- p.status,
188
- p.created AS created,
189
- p.type,
190
- p.details,
191
- p.paid,
192
- c.name AS customer' )
193
- ->leftJoin( 'CustomerAppointment', 'ca', 'ca.payment_id = p.id' )
194
- ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
195
- ->where( 'p.id', $this->getParameter( 'payment_id' ) )
196
- ->fetchRow();
197
- if ( $payment ) {
198
- $details = json_decode( $payment['details'], true );
199
- $data = array(
200
- 'payment' => array(
201
- 'status' => $payment['status'],
202
- 'type' => $payment['type'],
203
- 'coupon' => $details['coupon'],
204
- 'created' => $payment['created'],
205
- 'customer' => empty ( $payment['customer'] ) ? $details['customer'] : $payment['customer'],
206
- 'total' => $payment['total'],
207
- 'paid' => $payment['paid'],
208
- ),
209
- 'items' => $details['items'],
210
- 'deposit_enabled' => Lib\Config::depositEnabled()
211
- );
212
- }
213
-
214
- wp_send_json_success( array( 'html' => $this->render( 'details', $data, false ) ) );
215
- }
216
-
217
- /**
218
- * Delete payments.
219
- */
220
- public function executeDeletePayments()
221
- {
222
- $payment_ids = array_map( 'intval', $this->getParameter( 'data', array() ) );
223
- Lib\Entities\Payment::query()->delete()->whereIn( 'id', $payment_ids )->execute();
224
- wp_send_json_success();
225
- }
226
-
227
- /**
228
- * Complete payment.
229
- */
230
- public function executeCompletePayment()
231
- {
232
- $payment = Lib\Entities\Payment::find( $this->getParameter( 'payment_id' ) );
233
- $payment
234
- ->set( 'paid', $payment->get( 'total' ) )
235
- ->set( 'status', Lib\Entities\Payment::STATUS_COMPLETED )
236
- ->save();
237
-
238
- $payment_title = Lib\Utils\Common::formatPrice( $payment->get( 'paid' ) );
239
- if ( $payment->get( 'paid' ) != $payment->get( 'total' ) ) {
240
- $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Common::formatPrice( $payment->get( 'total' ) ) );
241
- }
242
- $payment_title .= sprintf(
243
- ' %s <span%s>%s</span>',
244
- Lib\Entities\Payment::typeToString( $payment->get( 'type' ) ),
245
- $payment->get( 'status' ) == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
246
- Lib\Entities\Payment::statusToString( $payment->get( 'status' ) )
247
- );
248
-
249
- wp_send_json_success( array( 'payment_title' => $payment_title ) );
250
- }
251
-
252
- /**
253
- * Override parent method to add 'wp_ajax_ab_' prefix
254
- * so current 'execute*' methods look nicer.
255
- *
256
- * @param string $prefix
257
- */
258
- protected function registerWpActions( $prefix = '' )
259
- {
260
- parent::registerWpActions( 'wp_ajax_ab_' );
261
- }
262
-
 
 
 
263
  }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Payments;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Controller
8
+ * @package BooklyLite\Backend\Modules\Payments
9
+ */
10
+ class Controller extends Lib\Base\Controller
11
+ {
12
+ const page_slug = 'bookly-payments';
13
+
14
+ /**
15
+ * @return array
16
+ */
17
+ protected function getPermissions()
18
+ {
19
+ return array(
20
+ 'executeGetPaymentDetails' => 'user',
21
+ 'executeCompletePayment' => 'user',
22
+ );
23
+ }
24
+
25
+ public function index()
26
+ {
27
+ /** @var \WP_Locale $wp_locale */
28
+ global $wp_locale;
29
+
30
+ $this->enqueueStyles( array(
31
+ 'backend' => array(
32
+ 'bootstrap/css/bootstrap-theme.min.css',
33
+ 'css/daterangepicker.css',
34
+ ),
35
+ 'frontend' => array( 'css/ladda.min.css', ),
36
+ ) );
37
+
38
+ $this->enqueueScripts( array(
39
+ 'backend' => array(
40
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
41
+ 'js/datatables.min.js' => array( 'jquery' ),
42
+ 'js/moment.min.js',
43
+ 'js/daterangepicker.js' => array( 'jquery' ),
44
+ 'js/chosen.jquery.min.js' => array( 'jquery' ),
45
+ ),
46
+ 'frontend' => array(
47
+ 'js/spin.min.js' => array( 'jquery' ),
48
+ 'js/ladda.min.js' => array( 'jquery' ),
49
+ ),
50
+ 'module' => array( 'js/payments.js' => array( 'bookly-datatables.min.js', 'bookly-ng-payment_details_dialog.js' ) ),
51
+ ) );
52
+
53
+ wp_localize_script( 'bookly-daterangepicker.js', 'BooklyL10n', array(
54
+ 'today' => __( 'Today', 'bookly' ),
55
+ 'yesterday' => __( 'Yesterday', 'bookly' ),
56
+ 'last_7' => __( 'Last 7 Days', 'bookly' ),
57
+ 'last_30' => __( 'Last 30 Days', 'bookly' ),
58
+ 'this_month' => __( 'This Month', 'bookly' ),
59
+ 'last_month' => __( 'Last Month', 'bookly' ),
60
+ 'custom_range' => __( 'Custom Range', 'bookly' ),
61
+ 'apply' => __( 'Apply', 'bookly' ),
62
+ 'cancel' => __( 'Cancel', 'bookly' ),
63
+ 'to' => __( 'To', 'bookly' ),
64
+ 'from' => __( 'From', 'bookly' ),
65
+ 'calendar' => array(
66
+ 'longMonths' => array_values( $wp_locale->month ),
67
+ 'shortMonths' => array_values( $wp_locale->month_abbrev ),
68
+ 'longDays' => array_values( $wp_locale->weekday ),
69
+ 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
70
+ ),
71
+ 'startOfWeek' => (int) get_option( 'start_of_week' ),
72
+ 'mjsDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
73
+ 'zeroRecords' => __( 'No payments for selected period and criteria.', 'bookly' ),
74
+ 'processing' => __( 'Processing...', 'bookly' ),
75
+ 'details' => __( 'Details', 'bookly' ),
76
+ 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
77
+ ) );
78
+
79
+ $types = array(
80
+ Lib\Entities\Payment::TYPE_LOCAL,
81
+ Lib\Entities\Payment::TYPE_2CHECKOUT,
82
+ Lib\Entities\Payment::TYPE_PAYPAL,
83
+ Lib\Entities\Payment::TYPE_AUTHORIZENET,
84
+ Lib\Entities\Payment::TYPE_STRIPE,
85
+ Lib\Entities\Payment::TYPE_PAYULATAM,
86
+ Lib\Entities\Payment::TYPE_PAYSON,
87
+ Lib\Entities\Payment::TYPE_MOLLIE,
88
+ Lib\Entities\Payment::TYPE_COUPON,
89
+ Lib\Entities\Payment::WOO_COMMERCE,
90
+ );
91
+ $providers = Lib\Entities\Staff::query()->select( 'id, full_name' )->sortBy( 'full_name' )->fetchArray();
92
+ $services = Lib\Entities\Service::query()->select( 'id, title' )->sortBy( 'title' )->fetchArray();
93
+
94
+ $this->render( 'index', compact( 'types', 'providers', 'services' ) );
95
+ }
96
+
97
+ /**
98
+ * Get payments.
99
+ */
100
+ public function executeGetPayments()
101
+ {
102
+ $columns = $this->getParameter( 'columns' );
103
+ $order = $this->getParameter( 'order' );
104
+ $filter = $this->getParameter( 'filter' );
105
+
106
+ $query = Lib\Entities\Payment::query( 'p' )
107
+ ->select( 'p.id, p.created, p.type, p.paid, p.total, p.status, p.details, c.name customer, st.full_name provider, s.title service, a.start_date' )
108
+ ->leftJoin( 'CustomerAppointment', 'ca', 'ca.payment_id = p.id' )
109
+ ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
110
+ ->leftJoin( 'Appointment', 'a', 'a.id = ca.appointment_id' )
111
+ ->leftJoin( 'Service', 's', 's.id = COALESCE(ca.compound_service_id, a.service_id)' )
112
+ ->leftJoin( 'Staff', 'st', 'st.id = a.staff_id' )
113
+ ->groupBy( 'p.id' );
114
+
115
+ // Filters.
116
+ list ( $start, $end ) = explode( ' - ', $filter['created'], 2 );
117
+ $end = date( 'Y-m-d', strtotime( '+1 day', strtotime( $end ) ) );
118
+
119
+ $query->whereBetween( 'p.created', $start, $end );
120
+
121
+ if ( $filter['type'] != -1 ) {
122
+ $query->where( 'p.type', $filter['type'] );
123
+ }
124
+
125
+ if ( $filter['staff'] != -1 ) {
126
+ $query->where( 'st.id', $filter['staff'] );
127
+ }
128
+
129
+ if ( $filter['service'] != -1 ) {
130
+ $query->where( 's.id', $filter['service'] );
131
+ }
132
+
133
+ foreach ( $order as $sort_by ) {
134
+ $query->sortBy( $columns[ $sort_by['column'] ]['data'] )
135
+ ->order( $sort_by['dir'] == 'desc' ? Lib\Query::ORDER_DESCENDING : Lib\Query::ORDER_ASCENDING );
136
+ }
137
+
138
+ $payments = $query->fetchArray();
139
+
140
+ $data = array();
141
+ $total = 0;
142
+ foreach ( $payments as $payment ) {
143
+ $details = json_decode( $payment['details'], true );
144
+ $multiple = count( $details['items'] ) > 1
145
+ ? ' <span class="glyphicon glyphicon-shopping-cart" title="' . esc_attr( __( 'See details for more items', 'bookly' ) ) . '"></span>'
146
+ : '' ;
147
+
148
+ $paid_title = Lib\Utils\Common::formatPrice( $payment['paid'] );
149
+ if ( $payment['paid'] != $payment['total'] ) {
150
+ $paid_title = sprintf( __( '%s of %s', 'bookly' ), $paid_title, Lib\Utils\Common::formatPrice( $payment['total'] ) );
151
+ }
152
+
153
+ $data[] = array(
154
+ 'id' => $payment['id'],
155
+ 'created' => Lib\Utils\DateTime::formatDateTime( $payment['created'] ),
156
+ 'type' => Lib\Entities\Payment::typeToString( $payment['type'] ),
157
+ 'customer' => $payment['customer'] ?: $details['customer'],
158
+ 'provider' => ( $payment['provider'] ?: $details['items'][0]['staff_name'] ) . $multiple,
159
+ 'service' => ( $payment['service'] ?: $details['items'][0]['service_name'] ) . $multiple,
160
+ 'start_date' => ( $payment['start_date']
161
+ ? Lib\Utils\DateTime::formatDateTime( $payment['start_date'] )
162
+ : Lib\Utils\DateTime::formatDateTime( $details['items'][0]['appointment_date'] ) ) . $multiple,
163
+ 'paid' => $paid_title,
164
+ 'status' => Lib\Entities\Payment::statusToString( $payment['status'] ),
165
+
166
+ );
167
+
168
+ $total += $payment['paid'];
169
+ }
170
+
171
+ wp_send_json( array(
172
+ 'draw' => ( int ) $this->getParameter( 'draw' ),
173
+ 'recordsTotal' => count( $data ),
174
+ 'recordsFiltered' => count( $data ),
175
+ 'data' => $data,
176
+ 'total' => Lib\Utils\Common::formatPrice( $total ),
177
+ ) );
178
+ }
179
+
180
+ /**
181
+ * Get payment details.
182
+ *
183
+ * @throws \Exception
184
+ */
185
+ public function executeGetPaymentDetails()
186
+ {
187
+ $data = array();
188
+ $payment = Lib\Entities\Payment::query( 'p' )
189
+ ->select( 'p.total,
190
+ p.status,
191
+ p.created AS created,
192
+ p.type,
193
+ p.details,
194
+ p.paid,
195
+ c.name AS customer' )
196
+ ->leftJoin( 'CustomerAppointment', 'ca', 'ca.payment_id = p.id' )
197
+ ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
198
+ ->where( 'p.id', $this->getParameter( 'payment_id' ) )
199
+ ->fetchRow();
200
+ if ( $payment ) {
201
+ $details = json_decode( $payment['details'], true );
202
+ $data = array(
203
+ 'payment' => array(
204
+ 'status' => $payment['status'],
205
+ 'type' => $payment['type'],
206
+ 'coupon' => $details['coupon'],
207
+ 'created' => $payment['created'],
208
+ 'customer' => empty ( $payment['customer'] ) ? $details['customer'] : $payment['customer'],
209
+ 'total' => $payment['total'],
210
+ 'paid' => $payment['paid'],
211
+ ),
212
+ 'items' => $details['items'],
213
+ 'deposit_enabled' => Lib\Config::isDepositPaymentsEnabled()
214
+ );
215
+ }
216
+
217
+ wp_send_json_success( array( 'html' => $this->render( 'details', $data, false ) ) );
218
+ }
219
+
220
+ /**
221
+ * Delete payments.
222
+ */
223
+ public function executeDeletePayments()
224
+ {
225
+ $payment_ids = array_map( 'intval', $this->getParameter( 'data', array() ) );
226
+ Lib\Entities\Payment::query()->delete()->whereIn( 'id', $payment_ids )->execute();
227
+ wp_send_json_success();
228
+ }
229
+
230
+ /**
231
+ * Complete payment.
232
+ */
233
+ public function executeCompletePayment()
234
+ {
235
+ $payment = Lib\Entities\Payment::find( $this->getParameter( 'payment_id' ) );
236
+ $payment
237
+ ->set( 'paid', $payment->get( 'total' ) )
238
+ ->set( 'status', Lib\Entities\Payment::STATUS_COMPLETED )
239
+ ->save();
240
+
241
+ $payment_title = Lib\Utils\Common::formatPrice( $payment->get( 'paid' ) );
242
+ if ( $payment->get( 'paid' ) != $payment->get( 'total' ) ) {
243
+ $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Common::formatPrice( $payment->get( 'total' ) ) );
244
+ }
245
+ $payment_title .= sprintf(
246
+ ' %s <span%s>%s</span>',
247
+ Lib\Entities\Payment::typeToString( $payment->get( 'type' ) ),
248
+ $payment->get( 'status' ) == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
249
+ Lib\Entities\Payment::statusToString( $payment->get( 'status' ) )
250
+ );
251
+
252
+ wp_send_json_success( array( 'payment_title' => $payment_title ) );
253
+ }
254
+
255
+ /**
256
+ * Override parent method to add 'wp_ajax_bookly_' prefix
257
+ * so current 'execute*' methods look nicer.
258
+ *
259
+ * @param string $prefix
260
+ */
261
+ protected function registerWpActions( $prefix = '' )
262
+ {
263
+ parent::registerWpActions( 'wp_ajax_bookly_' );
264
+ }
265
+
266
  }
backend/modules/payments/resources/js/ng-payment_details_dialog.js CHANGED
@@ -16,11 +16,11 @@
16
  element
17
  .on('show.bs.modal', function (e, payment_id) {
18
  if (payment_id === undefined) {
19
- payment_id = jQuery(e.relatedTarget).data('payment_id');
20
  }
21
  jQuery.ajax({
22
  url: ajaxurl,
23
- data: {action: 'ab_get_payment_details', payment_id: payment_id},
24
  dataType: 'json',
25
  success: function (response) {
26
  if (response.success) {
@@ -30,7 +30,7 @@
30
  ladda.start();
31
  jQuery.ajax({
32
  url: ajaxurl,
33
- data: {action: 'ab_complete_payment', payment_id: payment_id},
34
  dataType: 'json',
35
  type: 'POST',
36
  success: function (response) {
16
  element
17
  .on('show.bs.modal', function (e, payment_id) {
18
  if (payment_id === undefined) {
19
+ payment_id = e.relatedTarget.getAttribute('data-payment_id');
20
  }
21
  jQuery.ajax({
22
  url: ajaxurl,
23
+ data: {action: 'bookly_get_payment_details', payment_id: payment_id},
24
  dataType: 'json',
25
  success: function (response) {
26
  if (response.success) {
30
  ladda.start();
31
  jQuery.ajax({
32
  url: ajaxurl,
33
+ data: {action: 'bookly_complete_payment', payment_id: payment_id},
34
  dataType: 'json',
35
  type: 'POST',
36
  success: function (response) {
backend/modules/payments/resources/js/payments.js CHANGED
@@ -1,184 +1,184 @@
1
- jQuery(function($) {
2
-
3
- var
4
- $payments_list = $('#bookly-payments-list'),
5
- $check_all_button = $('#bookly-check-all'),
6
- $date_filter = $('#bookly-filter-date'),
7
- $type_filter = $('#bookly-filter-type'),
8
- $staff_filter = $('#bookly-filter-staff'),
9
- $service_filter = $('#bookly-filter-service'),
10
- $payment_total = $('#bookly-payment-total'),
11
- $delete_button = $('#bookly-delete')
12
- ;
13
-
14
- /**
15
- * Init DataTables.
16
- */
17
- var dt = $payments_list.DataTable({
18
- order: [[ 0, 'asc' ]],
19
- paging: false,
20
- info: false,
21
- searching: false,
22
- processing: true,
23
- responsive: true,
24
- serverSide: true,
25
- ajax: {
26
- url: ajaxurl,
27
- data: function ( d ) {
28
- return $.extend( {}, d, {
29
- action: 'ab_get_payments',
30
- filter: {
31
- created: $date_filter.data('date'),
32
- type: $type_filter.val(),
33
- staff: $staff_filter.val(),
34
- service: $service_filter.val()
35
- }
36
- } );
37
- },
38
- dataSrc: function (json) {
39
- $payment_total.html(json.total);
40
-
41
- return json.data;
42
- }
43
- },
44
- columns: [
45
- { data: 'created' },
46
- { data: 'type' },
47
- { data: 'customer' },
48
- { data: 'provider' },
49
- { data: 'service' },
50
- { data: 'start_date' },
51
- { data: 'paid' },
52
- { data: 'status' },
53
- {
54
- responsivePriority: 1,
55
- orderable: false,
56
- searchable: false,
57
- render: function ( data, type, row, meta ) {
58
- return '<button type="button" class="btn btn-default" data-toggle="modal" data-target="#bookly-payment-details-modal" data-payment_id="' + row.id + '"><i class="glyphicon glyphicon-list-alt"></i> ' + BooklyL10n.details + '</a>';
59
- }
60
- },
61
- {
62
- responsivePriority: 1,
63
- orderable: false,
64
- searchable: false,
65
- render: function ( data, type, row, meta ) {
66
- return '<input type="checkbox" value="' + row.id + '">';
67
- }
68
- }
69
- ],
70
- language: {
71
- zeroRecords: BooklyL10n.zeroRecords,
72
- processing: BooklyL10n.processing
73
- }
74
- });
75
-
76
- /**
77
- * Select all coupons.
78
- */
79
- $check_all_button.on('change', function () {
80
- $payments_list.find('tbody input:checkbox').prop('checked', this.checked);
81
- });
82
-
83
- /**
84
- * On coupon select.
85
- */
86
- $payments_list.on('change', 'tbody input:checkbox', function () {
87
- $check_all_button.prop('checked', $payments_list.find('tbody input:not(:checked)').length == 0);
88
- });
89
- /**
90
- * Init date range picker.
91
- */
92
- moment.locale('en', {
93
- months: BooklyL10n.calendar.longMonths,
94
- monthsShort: BooklyL10n.calendar.shortMonths,
95
- weekdays: BooklyL10n.calendar.longDays,
96
- weekdaysShort: BooklyL10n.calendar.shortDays,
97
- weekdaysMin: BooklyL10n.calendar.shortDays
98
- });
99
-
100
- var picker_ranges = {};
101
- picker_ranges[BooklyL10n.yesterday] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
102
- picker_ranges[BooklyL10n.today] = [moment(), moment()];
103
- picker_ranges[BooklyL10n.last_7] = [moment().subtract(7, 'days'), moment()];
104
- picker_ranges[BooklyL10n.last_30] = [moment().subtract(30, 'days'), moment()];
105
- picker_ranges[BooklyL10n.this_month] = [moment().startOf('month'), moment().endOf('month')];
106
- picker_ranges[BooklyL10n.last_month] = [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')];
107
-
108
- $date_filter.daterangepicker(
109
- {
110
- parentEl: $date_filter.parent(),
111
- startDate: moment().subtract(30, 'days'), // by default selected is "Last 30 days"
112
- ranges: picker_ranges,
113
- locale: {
114
- applyLabel: BooklyL10n.apply,
115
- cancelLabel: BooklyL10n.cancel,
116
- fromLabel: BooklyL10n.from,
117
- toLabel: BooklyL10n.to,
118
- customRangeLabel: BooklyL10n.custom_range,
119
- daysOfWeek: BooklyL10n.calendar.shortDays,
120
- monthNames: BooklyL10n.calendar.longMonths,
121
- firstDay: parseInt(BooklyL10n.startOfWeek),
122
- format: BooklyL10n.mjsDateFormat
123
- }
124
- },
125
- function(start, end) {
126
- var format = 'YYYY-MM-DD';
127
- $date_filter
128
- .data('date', start.format(format) + ' - ' + end.format(format))
129
- .find('span')
130
- .html(start.format(BooklyL10n.mjsDateFormat) + ' - ' + end.format(BooklyL10n.mjsDateFormat));
131
- }
132
- );
133
-
134
- /**
135
- * On filters change.
136
- */
137
- $('.bookly-js-chosen-select').chosen({
138
- allow_single_deselect: true,
139
- disable_search_threshold: 10
140
- });
141
- $date_filter.on('apply.daterangepicker', function () { dt.ajax.reload(); });
142
- $type_filter.on('change', function () { dt.ajax.reload(); });
143
- $staff_filter.on('change', function () { dt.ajax.reload(); });
144
- $service_filter.on('change', function () { dt.ajax.reload(); });
145
-
146
- /**
147
- * Delete payments.
148
- */
149
- $delete_button.on('click', function () {
150
- if (confirm(BooklyL10n.are_you_sure)) {
151
- var ladda = Ladda.create(this);
152
- ladda.start();
153
-
154
- var data = [];
155
- var $checkboxes = $payments_list.find('tbody input:checked');
156
- $checkboxes.each(function () {
157
- data.push(this.value);
158
- });
159
-
160
- $.ajax({
161
- url : ajaxurl,
162
- type : 'POST',
163
- data : {
164
- action : 'ab_delete_payments',
165
- data : data
166
- },
167
- dataType : 'json',
168
- success : function(response) {
169
- ladda.stop();
170
- if (response.success) {
171
- dt.rows($checkboxes.closest('td')).remove().draw();
172
- } else {
173
- alert(response.data.message);
174
- }
175
- }
176
- });
177
- }
178
- });
179
- });
180
-
181
- (function() {
182
- var module = angular.module('paymentDetails', ['paymentDetailsDialog']);
183
- module.controller('paymentDetailsCtrl', function($scope) {});
184
  })();
1
+ jQuery(function($) {
2
+
3
+ var
4
+ $payments_list = $('#bookly-payments-list'),
5
+ $check_all_button = $('#bookly-check-all'),
6
+ $date_filter = $('#bookly-filter-date'),
7
+ $type_filter = $('#bookly-filter-type'),
8
+ $staff_filter = $('#bookly-filter-staff'),
9
+ $service_filter = $('#bookly-filter-service'),
10
+ $payment_total = $('#bookly-payment-total'),
11
+ $delete_button = $('#bookly-delete')
12
+ ;
13
+
14
+ /**
15
+ * Init DataTables.
16
+ */
17
+ var dt = $payments_list.DataTable({
18
+ order: [[ 0, 'asc' ]],
19
+ paging: false,
20
+ info: false,
21
+ searching: false,
22
+ processing: true,
23
+ responsive: true,
24
+ serverSide: true,
25
+ ajax: {
26
+ url: ajaxurl,
27
+ data: function ( d ) {
28
+ return $.extend( {}, d, {
29
+ action: 'bookly_get_payments',
30
+ filter: {
31
+ created: $date_filter.data('date'),
32
+ type: $type_filter.val(),
33
+ staff: $staff_filter.val(),
34
+ service: $service_filter.val()
35
+ }
36
+ } );
37
+ },
38
+ dataSrc: function (json) {
39
+ $payment_total.html(json.total);
40
+
41
+ return json.data;
42
+ }
43
+ },
44
+ columns: [
45
+ { data: 'created' },
46
+ { data: 'type' },
47
+ { data: 'customer', render: $.fn.dataTable.render.text() },
48
+ { data: 'provider' },
49
+ { data: 'service' },
50
+ { data: 'start_date' },
51
+ { data: 'paid' },
52
+ { data: 'status' },
53
+ {
54
+ responsivePriority: 1,
55
+ orderable: false,
56
+ searchable: false,
57
+ render: function ( data, type, row, meta ) {
58
+ return '<button type="button" class="btn btn-default" data-toggle="modal" data-target="#bookly-payment-details-modal" data-payment_id="' + row.id + '"><i class="glyphicon glyphicon-list-alt"></i> ' + BooklyL10n.details + '</a>';
59
+ }
60
+ },
61
+ {
62
+ responsivePriority: 1,
63
+ orderable: false,
64
+ searchable: false,
65
+ render: function ( data, type, row, meta ) {
66
+ return '<input type="checkbox" value="' + row.id + '">';
67
+ }
68
+ }
69
+ ],
70
+ language: {
71
+ zeroRecords: BooklyL10n.zeroRecords,
72
+ processing: BooklyL10n.processing
73
+ }
74
+ });
75
+
76
+ /**
77
+ * Select all coupons.
78
+ */
79
+ $check_all_button.on('change', function () {
80
+ $payments_list.find('tbody input:checkbox').prop('checked', this.checked);
81
+ });
82
+
83
+ /**
84
+ * On coupon select.
85
+ */
86
+ $payments_list.on('change', 'tbody input:checkbox', function () {
87
+ $check_all_button.prop('checked', $payments_list.find('tbody input:not(:checked)').length == 0);
88
+ });
89
+ /**
90
+ * Init date range picker.
91
+ */
92
+ moment.locale('en', {
93
+ months: BooklyL10n.calendar.longMonths,
94
+ monthsShort: BooklyL10n.calendar.shortMonths,
95
+ weekdays: BooklyL10n.calendar.longDays,
96
+ weekdaysShort: BooklyL10n.calendar.shortDays,
97
+ weekdaysMin: BooklyL10n.calendar.shortDays
98
+ });
99
+
100
+ var picker_ranges = {};
101
+ picker_ranges[BooklyL10n.yesterday] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
102
+ picker_ranges[BooklyL10n.today] = [moment(), moment()];
103
+ picker_ranges[BooklyL10n.last_7] = [moment().subtract(7, 'days'), moment()];
104
+ picker_ranges[BooklyL10n.last_30] = [moment().subtract(30, 'days'), moment()];
105
+ picker_ranges[BooklyL10n.this_month] = [moment().startOf('month'), moment().endOf('month')];
106
+ picker_ranges[BooklyL10n.last_month] = [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')];
107
+
108
+ $date_filter.daterangepicker(
109
+ {
110
+ parentEl: $date_filter.parent(),
111
+ startDate: moment().subtract(30, 'days'), // by default selected is "Last 30 days"
112
+ ranges: picker_ranges,
113
+ locale: {
114
+ applyLabel: BooklyL10n.apply,
115
+ cancelLabel: BooklyL10n.cancel,
116
+ fromLabel: BooklyL10n.from,
117
+ toLabel: BooklyL10n.to,
118
+ customRangeLabel: BooklyL10n.custom_range,
119
+ daysOfWeek: BooklyL10n.calendar.shortDays,
120
+ monthNames: BooklyL10n.calendar.longMonths,
121
+ firstDay: parseInt(BooklyL10n.startOfWeek),
122
+ format: BooklyL10n.mjsDateFormat
123
+ }
124
+ },
125
+ function(start, end) {
126
+ var format = 'YYYY-MM-DD';
127
+ $date_filter
128
+ .data('date', start.format(format) + ' - ' + end.format(format))
129
+ .find('span')
130
+ .html(start.format(BooklyL10n.mjsDateFormat) + ' - ' + end.format(BooklyL10n.mjsDateFormat));
131
+ }
132
+ );
133
+
134
+ /**
135
+ * On filters change.
136
+ */
137
+ $('.bookly-js-chosen-select').chosen({
138
+ allow_single_deselect: true,
139
+ disable_search_threshold: 10
140
+ });
141
+ $date_filter.on('apply.daterangepicker', function () { dt.ajax.reload(); });
142
+ $type_filter.on('change', function () { dt.ajax.reload(); });
143
+ $staff_filter.on('change', function () { dt.ajax.reload(); });
144
+ $service_filter.on('change', function () { dt.ajax.reload(); });
145
+
146
+ /**
147
+ * Delete payments.
148
+ */
149
+ $delete_button.on('click', function () {
150
+ if (confirm(BooklyL10n.are_you_sure)) {
151
+ var ladda = Ladda.create(this);
152
+ ladda.start();
153
+
154
+ var data = [];
155
+ var $checkboxes = $payments_list.find('tbody input:checked');
156
+ $checkboxes.each(function () {
157
+ data.push(this.value);
158
+ });
159
+
160
+ $.ajax({
161
+ url : ajaxurl,
162
+ type : 'POST',
163
+ data : {
164
+ action : 'bookly_delete_payments',
165
+ data : data
166
+ },
167
+ dataType : 'json',
168
+ success : function(response) {
169
+ ladda.stop();
170
+ if (response.success) {
171
+ dt.rows($checkboxes.closest('td')).remove().draw();
172
+ } else {
173
+ alert(response.data.message);
174
+ }
175
+ }
176
+ });
177
+ }
178
+ });
179
+ });
180
+
181
+ (function() {
182
+ var module = angular.module('paymentDetails', ['paymentDetailsDialog']);
183
+ module.controller('paymentDetailsCtrl', function($scope) {});
184
  })();
backend/modules/payments/templates/_payment_details_dialog.php CHANGED
@@ -1,21 +1,19 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <script type="text/ng-template" id="bookly-payment-details-dialog.tpl">
3
- <div class="modal fade" id="bookly-payment-details-modal" tabindex="-1" role="dialog">
4
- <div class="modal-dialog" role="document">
5
- <div class="modal-content">
6
- <div class="modal-header">
7
- <button type="button" class="close" data-dismiss="modal" aria-label="<?php esc_attr_e( 'Close', 'bookly' ) ?>"><span aria-hidden="true">&times;</span></button>
8
- <div class="modal-title h2"><?php _e( 'Payment', 'bookly' ) ?></div>
9
- </div>
10
- <div class="modal-body">
11
- <div class="bookly-loading"></div>
12
- </div>
13
- <div class="modal-footer">
14
- <button type="button" class="btn btn-default" data-dismiss="modal">
15
- <?php _e( 'Close', 'bookly' ) ?>
16
- </button>
17
- </div>
18
- </div>
19
- </div>
20
- </div>
21
  </script>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <script type="text/ng-template" id="bookly-payment-details-dialog.tpl">
3
+ <div class="modal fade" id="bookly-payment-details-modal" tabindex="-1" role="dialog">
4
+ <div class="modal-dialog" role="document">
5
+ <div class="modal-content">
6
+ <div class="modal-header">
7
+ <button type="button" class="close" data-dismiss="modal" aria-label="<?php esc_attr_e( 'Close', 'bookly' ) ?>"><span aria-hidden="true">&times;</span></button>
8
+ <div class="modal-title h2"><?php _e( 'Payment', 'bookly' ) ?></div>
9
+ </div>
10
+ <div class="modal-body">
11
+ <div class="bookly-loading"></div>
12
+ </div>
13
+ <div class="modal-footer">
14
+ <?php \BooklyLite\Lib\Utils\Common::customButton( null, 'btn-lg btn-default', __( 'Close', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </div>
 
 
19
  </script>
backend/modules/payments/templates/details.php CHANGED
@@ -1,132 +1,132 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- $subtotal = 0;
3
- $subtotal_deposit = 0;
4
- ?>
5
- <?php if ( $payment ) : ?>
6
- <div class="table-responsive">
7
- <table class="table table-bordered">
8
- <thead>
9
- <tr>
10
- <th width="50%"><?php _e( 'Customer', 'bookly' ) ?></th>
11
- <th width="50%"><?php _e( 'Payment', 'bookly' ) ?></th>
12
- </tr>
13
- </thead>
14
- <tbody>
15
- <tr>
16
- <td><?php echo $payment['customer'] ?></td>
17
- <td>
18
- <div><?php _e( 'Date', 'bookly' ) ?>: <?php echo \BooklyLite\Lib\Utils\DateTime::formatDateTime( $payment['created'] ) ?></div>
19
- <div><?php _e( 'Type', 'bookly' ) ?>: <?php echo \BooklyLite\Lib\Entities\Payment::typeToString( $payment['type'] ) ?></div>
20
- <div><?php _e( 'Status', 'bookly' ) ?>: <?php echo \BooklyLite\Lib\Entities\Payment::statusToString( $payment['status'] ) ?></div>
21
- </td>
22
- </tr>
23
- </tbody>
24
- </table>
25
- </div>
26
-
27
- <div class="table-responsive">
28
- <table class="table table-bordered">
29
- <thead>
30
- <tr>
31
- <th><?php _e( 'Service', 'bookly' ) ?></th>
32
- <th><?php _e( 'Date', 'bookly' ) ?></th>
33
- <th><?php _e( 'Provider', 'bookly' ) ?></th>
34
- <?php if ( $deposit_enabled ): ?>
35
- <th class="text-right"><?php _e( 'Deposit', 'bookly' ) ?></th>
36
- <?php endif ?>
37
- <th class="text-right"><?php _e( 'Price', 'bookly' ) ?></th>
38
- </tr>
39
- </thead>
40
- <tbody>
41
- <?php foreach ( $items as $item ) :
42
- $extras_price = 0; ?>
43
- <tr>
44
- <td>
45
- <?php if ( $item['number_of_persons'] > 1 ) echo $item['number_of_persons'] . '&nbsp;&times;&nbsp;' ?><?php echo $item['service_name'] ?>
46
- <?php if ( ! empty ( $item['extras'] ) ) : ?>
47
- <ul class="bookly-list list-dots">
48
- <?php foreach ( $item['extras'] as $extra ) : ?>
49
- <li><?php if ( $extra['quantity'] > 1 ) echo $extra['quantity'] . '&nbsp;&times;&nbsp;' ?><?php echo $extra['title'] ?></li>
50
- <?php $extras_price += $extra['price'] * $extra['quantity'] ?>
51
- <?php endforeach ?>
52
- </ul>
53
- <?php endif ?>
54
- </td>
55
- <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatDateTime( $item['appointment_date'] ) ?></td>
56
- <td><?php echo $item['staff_name'] ?></td>
57
- <?php $deposit = apply_filters( 'bookly_deposit_get_deposit_amount', $item['number_of_persons'] * ( $item['service_price'] + $extras_price ), $item['deposit'], $item['number_of_persons'] ) ?>
58
- <?php if ( $deposit_enabled ) : ?>
59
- <td class="text-right"><?php echo apply_filters( 'bookly_deposit_format_deposit', $deposit, $item['deposit'] ) ?></td>
60
- <?php endif ?>
61
- <td class="text-right">
62
- <?php $service_price = \BooklyLite\Lib\Utils\Common::formatPrice( $item['service_price'] ) ?>
63
- <?php if ( $item['number_of_persons'] > 1 ) $service_price = $item['number_of_persons'] . '&nbsp;&times;&nbsp' . $service_price ?>
64
- <?php echo $service_price ?>
65
- <ul class="bookly-list">
66
- <?php foreach ( $item['extras'] as $extra ) : ?>
67
- <li>
68
- <?php printf( '%s%s%s',
69
- ( $item['number_of_persons'] > 1 ) ? $item['number_of_persons'] . '&nbsp;&times;&nbsp;' : '',
70
- ( $extra['quantity'] > 1 ) ? $extra['quantity'] . '&nbsp;&times;&nbsp;' : '',
71
- \BooklyLite\Lib\Utils\Common::formatPrice( $extra['price'] )
72
- ) ?>
73
- </li>
74
- <?php $subtotal += $item['number_of_persons'] * $extra['price'] * $extra['quantity'] ?>
75
- <?php endforeach ?>
76
- </ul>
77
- </td>
78
- </tr>
79
- <?php $subtotal += $item['number_of_persons'] * $item['service_price'] ?>
80
- <?php $subtotal_deposit += $deposit ?>
81
- <?php endforeach ?>
82
- </tbody>
83
- <tfoot>
84
- <tr>
85
- <th rowspan="3" style="border-left-color: white; border-bottom-color: white;"></th>
86
- <th colspan="2"><?php _e( 'Subtotal', 'bookly' ) ?></th>
87
- <?php if ( $deposit_enabled ) : ?>
88
- <th class="text-right"><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $subtotal_deposit ) ?></th>
89
- <?php endif ?>
90
- <th class="text-right"><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $subtotal ) ?></th>
91
- </tr>
92
- <tr>
93
- <th colspan="<?php echo 2 + (int) $deposit_enabled ?>">
94
- <?php _e( 'Discount', 'bookly' ) ?>
95
- <?php if ( $payment['coupon'] ) : ?><div><small>(<?php echo $payment['coupon']['code'] ?>)</small></div><?php endif ?>
96
- </th>
97
- <th class="text-right">
98
- <?php if ( $payment['coupon'] ) : ?>
99
- <?php if ( $payment['coupon']['discount'] ) : ?>
100
- <div>-<?php echo $payment['coupon']['discount'] ?>%</div>
101
- <?php endif ?>
102
- <?php if ( $payment['coupon']['deduction'] ) : ?>
103
- <div><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( - $payment['coupon']['deduction'] ) ?></div>
104
- <?php endif ?>
105
- <?php else : ?>
106
- <?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 0 ) ?>
107
- <?php endif ?>
108
- </th>
109
- </tr>
110
- <tr>
111
- <th colspan="<?php echo 2 + (int) $deposit_enabled ?>"><?php _e( 'Total', 'bookly' ) ?></th>
112
- <th class="text-right"><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $payment['total'] ) ?></th>
113
- </tr>
114
- <?php if ( $payment['total'] != $payment['paid'] ) : ?>
115
- <tr>
116
- <td rowspan="2" style="border-left-color:#fff;border-bottom-color:#fff;"></td>
117
- <td colspan="<?php echo 2 + (int) $deposit_enabled ?>"><i><?php _e( 'Paid', 'bookly' ) ?></i></td>
118
- <td class="text-right"><i><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $payment['paid'] ) ?></i></td>
119
- </tr>
120
- <tr>
121
- <td colspan="<?php echo 2 + (int) $deposit_enabled ?>"><i><?php _e( 'Due', 'bookly' ) ?></i></td>
122
- <td class="text-right"><i><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $payment['total'] - $payment['paid'] ) ?></i></td>
123
- </tr>
124
- <tr>
125
- <td style="border-left-color:#fff;border-bottom-color:#fff;"></td>
126
- <td colspan="<?php echo 3 + (int) $deposit_enabled ?>" class="text-right"><button type="button" class="btn btn-success ladda-button" id="bookly-complete-payment" data-spinner-size="40" data-style="zoom-in"><i><?php _e( 'Complete payment', 'bookly' ) ?></i></button></td>
127
- </tr>
128
- <?php endif ?>
129
- </tfoot>
130
- </table>
131
- </div>
132
  <?php endif ?>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ $subtotal = 0;
3
+ $subtotal_deposit = 0;
4
+ ?>
5
+ <?php if ( $payment ) : ?>
6
+ <div class="table-responsive">
7
+ <table class="table table-bordered">
8
+ <thead>
9
+ <tr>
10
+ <th width="50%"><?php _e( 'Customer', 'bookly' ) ?></th>
11
+ <th width="50%"><?php _e( 'Payment', 'bookly' ) ?></th>
12
+ </tr>
13
+ </thead>
14
+ <tbody>
15
+ <tr>
16
+ <td><?php echo $payment['customer'] ?></td>
17
+ <td>
18
+ <div><?php _e( 'Date', 'bookly' ) ?>: <?php echo \BooklyLite\Lib\Utils\DateTime::formatDateTime( $payment['created'] ) ?></div>
19
+ <div><?php _e( 'Type', 'bookly' ) ?>: <?php echo \BooklyLite\Lib\Entities\Payment::typeToString( $payment['type'] ) ?></div>
20
+ <div><?php _e( 'Status', 'bookly' ) ?>: <?php echo \BooklyLite\Lib\Entities\Payment::statusToString( $payment['status'] ) ?></div>
21
+ </td>
22
+ </tr>
23
+ </tbody>
24
+ </table>
25
+ </div>
26
+
27
+ <div class="table-responsive">
28
+ <table class="table table-bordered">
29
+ <thead>
30
+ <tr>
31
+ <th><?php _e( 'Service', 'bookly' ) ?></th>
32
+ <th><?php _e( 'Date', 'bookly' ) ?></th>
33
+ <th><?php _e( 'Provider', 'bookly' ) ?></th>
34
+ <?php if ( $deposit_enabled ): ?>
35
+ <th class="text-right"><?php _e( 'Deposit', 'bookly' ) ?></th>
36
+ <?php endif ?>
37
+ <th class="text-right"><?php _e( 'Price', 'bookly' ) ?></th>
38
+ </tr>
39
+ </thead>
40
+ <tbody>
41
+ <?php foreach ( $items as $item ) :
42
+ $extras_price = 0; ?>
43
+ <tr>
44
+ <td>
45
+ <?php if ( $item['number_of_persons'] > 1 ) echo $item['number_of_persons'] . '&nbsp;&times;&nbsp;' ?><?php echo $item['service_name'] ?>
46
+ <?php if ( ! empty ( $item['extras'] ) ) : ?>
47
+ <ul class="bookly-list list-dots">
48
+ <?php foreach ( $item['extras'] as $extra ) : ?>
49
+ <li><?php if ( $extra['quantity'] > 1 ) echo $extra['quantity'] . '&nbsp;&times;&nbsp;' ?><?php echo $extra['title'] ?></li>
50
+ <?php $extras_price += $extra['price'] * $extra['quantity'] ?>
51
+ <?php endforeach ?>
52
+ </ul>
53
+ <?php endif ?>
54
+ </td>
55
+ <td><?php echo \BooklyLite\Lib\Utils\DateTime::formatDateTime( $item['appointment_date'] ) ?></td>
56
+ <td><?php echo $item['staff_name'] ?></td>
57
+ <?php $deposit = apply_filters( 'bookly_deposit_payments_get_deposit_amount', $item['number_of_persons'] * ( $item['service_price'] + $extras_price ), $item['deposit'], $item['number_of_persons'] ) ?>
58
+ <?php if ( $deposit_enabled ) : ?>
59
+ <td class="text-right"><?php echo apply_filters( 'bookly_deposit_payments_format_deposit', $deposit, $item['deposit'] ) ?></td>
60
+ <?php endif ?>
61
+ <td class="text-right">
62
+ <?php $service_price = \BooklyLite\Lib\Utils\Common::formatPrice( $item['service_price'] ) ?>
63
+ <?php if ( $item['number_of_persons'] > 1 ) $service_price = $item['number_of_persons'] . '&nbsp;&times;&nbsp' . $service_price ?>
64
+ <?php echo $service_price ?>
65
+ <ul class="bookly-list">
66
+ <?php foreach ( $item['extras'] as $extra ) : ?>
67
+ <li>
68
+ <?php printf( '%s%s%s',
69
+ ( $item['number_of_persons'] > 1 ) ? $item['number_of_persons'] . '&nbsp;&times;&nbsp;' : '',
70
+ ( $extra['quantity'] > 1 ) ? $extra['quantity'] . '&nbsp;&times;&nbsp;' : '',
71
+ \BooklyLite\Lib\Utils\Common::formatPrice( $extra['price'] )
72
+ ) ?>
73
+ </li>
74
+ <?php $subtotal += $item['number_of_persons'] * $extra['price'] * $extra['quantity'] ?>
75
+ <?php endforeach ?>
76
+ </ul>
77
+ </td>
78
+ </tr>
79
+ <?php $subtotal += $item['number_of_persons'] * $item['service_price'] ?>
80
+ <?php $subtotal_deposit += $deposit ?>
81
+ <?php endforeach ?>
82
+ </tbody>
83
+ <tfoot>
84
+ <tr>
85
+ <th rowspan="3" style="border-left-color: white; border-bottom-color: white;"></th>
86
+ <th colspan="2"><?php _e( 'Subtotal', 'bookly' ) ?></th>
87
+ <?php if ( $deposit_enabled ) : ?>
88
+ <th class="text-right"><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $subtotal_deposit ) ?></th>
89
+ <?php endif ?>
90
+ <th class="text-right"><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $subtotal ) ?></th>
91
+ </tr>
92
+ <tr>
93
+ <th colspan="<?php echo 2 + (int) $deposit_enabled ?>">
94
+ <?php _e( 'Discount', 'bookly' ) ?>
95
+ <?php if ( $payment['coupon'] ) : ?><div><small>(<?php echo $payment['coupon']['code'] ?>)</small></div><?php endif ?>
96
+ </th>
97
+ <th class="text-right">
98
+ <?php if ( $payment['coupon'] ) : ?>
99
+ <?php if ( $payment['coupon']['discount'] ) : ?>
100
+ <div>-<?php echo $payment['coupon']['discount'] ?>%</div>
101
+ <?php endif ?>
102
+ <?php if ( $payment['coupon']['deduction'] ) : ?>
103
+ <div><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( - $payment['coupon']['deduction'] ) ?></div>
104
+ <?php endif ?>
105
+ <?php else : ?>
106
+ <?php echo \BooklyLite\Lib\Utils\Common::formatPrice( 0 ) ?>
107
+ <?php endif ?>
108
+ </th>
109
+ </tr>
110
+ <tr>
111
+ <th colspan="<?php echo 2 + (int) $deposit_enabled ?>"><?php _e( 'Total', 'bookly' ) ?></th>
112
+ <th class="text-right"><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $payment['total'] ) ?></th>
113
+ </tr>
114
+ <?php if ( $payment['total'] != $payment['paid'] ) : ?>
115
+ <tr>
116
+ <td rowspan="2" style="border-left-color:#fff;border-bottom-color:#fff;"></td>
117
+ <td colspan="<?php echo 2 + (int) $deposit_enabled ?>"><i><?php _e( 'Paid', 'bookly' ) ?></i></td>
118
+ <td class="text-right"><i><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $payment['paid'] ) ?></i></td>
119
+ </tr>
120
+ <tr>
121
+ <td colspan="<?php echo 2 + (int) $deposit_enabled ?>"><i><?php _e( 'Due', 'bookly' ) ?></i></td>
122
+ <td class="text-right"><i><?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $payment['total'] - $payment['paid'] ) ?></i></td>
123
+ </tr>
124
+ <tr>
125
+ <td style="border-left-color:#fff;border-bottom-color:#fff;"></td>
126
+ <td colspan="<?php echo 3 + (int) $deposit_enabled ?>" class="text-right"><button type="button" class="btn btn-success ladda-button" id="bookly-complete-payment" data-spinner-size="40" data-style="zoom-in"><i><?php _e( 'Complete payment', 'bookly' ) ?></i></button></td>
127
+ </tr>
128
+ <?php endif ?>
129
+ </tfoot>
130
+ </table>
131
+ </div>
132
  <?php endif ?>
backend/modules/payments/templates/index.php CHANGED
@@ -1,89 +1,90 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div id="bookly-tbs" class="wrap">
3
- <div class="bookly-tbs-body">
4
- <div class="page-header text-right clearfix">
5
- <div class="bookly-page-title">
6
- <?php _e( 'Payments', 'bookly' ) ?>
7
- </div>
8
- </div>
9
- <div class="panel panel-default bookly-main">
10
- <div class="panel-body">
11
- <div class="row">
12
- <div class="col-md-4 col-lg-3">
13
- <div class="bookly-margin-bottom-lg bookly-relative">
14
- <button type="button" class="btn btn-block btn-default" id="bookly-filter-date" data-date="<?php echo date( 'Y-m-d', strtotime( '-30 day' ) ) ?> - <?php echo date( 'Y-m-d' ) ?>">
15
- <i class="dashicons dashicons-calendar-alt"></i>
16
- <span>
17
- <?php echo date_i18n( get_option( 'date_format' ), strtotime( '-30 day' ) ) ?> - <?php echo date_i18n( get_option( 'date_format' ) ) ?>
18
- </span>
19
- </button>
20
- </div>
21
- </div>
22
- <div class="col-md-2 col-lg-2">
23
- <div class="form-group">
24
- <select id="bookly-filter-type" class="form-control bookly-js-chosen-select" data-placeholder="<?php esc_attr_e( 'Type', 'bookly' ) ?>">
25
- <option value="-1"></option>
26
- <?php foreach ( $types as $type ) : ?>
27
- <option value="<?php echo esc_attr( $type ) ?>">
28
- <?php echo \BooklyLite\Lib\Entities\Payment::typeToString( $type ) ?>
29
- </option>
30
- <?php endforeach ?>
31
- </select>
32
- </div>
33
- </div>
34
- <div class="col-md-3 col-lg-2">
35
- <div class="form-group">
36
- <select id="bookly-filter-staff" class="form-control bookly-js-chosen-select" data-placeholder="<?php esc_attr_e( 'Provider', 'bookly' ) ?>">
37
- <option value="-1"></option>
38
- <?php foreach ( $providers as $provider ) : ?>
39
- <option value="<?php echo $provider['id'] ?>"><?php echo esc_html( $provider['full_name'] ) ?></option>
40
- <?php endforeach ?>
41
- </select>
42
- </div>
43
- </div>
44
- <div class="col-md-3 col-lg-2">
45
- <div class="form-group">
46
- <select id="bookly-filter-service" class="form-control bookly-js-chosen-select" data-placeholder="<?php esc_attr_e( 'Service', 'bookly' ) ?>">
47
- <option value="-1"></option>
48
- <?php foreach ( $services as $service ) : ?>
49
- <option value="<?php echo $service['id'] ?>"><?php echo esc_html( $service['title'] ) ?></option>
50
- <?php endforeach ?>
51
- </select>
52
- </div>
53
- </div>
54
- </div>
55
-
56
- <table id="bookly-payments-list" class="table table-striped" width="100%">
57
- <thead>
58
- <tr>
59
- <th><?php _e( 'Date', 'bookly' ) ?></th>
60
- <th><?php _e( 'Type', 'bookly' ) ?></th>
61
- <th><?php _e( 'Customer', 'bookly' ) ?></th>
62
- <th><?php _e( 'Provider', 'bookly' ) ?></th>
63
- <th><?php _e( 'Service', 'bookly' ) ?></th>
64
- <th><?php _e( 'Appointment Date', 'bookly' ) ?></th>
65
- <th><?php _e( 'Amount', 'bookly' ) ?></th>
66
- <th><?php _e( 'Status', 'bookly' ) ?></th>
67
- <th></th>
68
- <th width="16"><input type="checkbox" id="bookly-check-all"></th>
69
- </tr>
70
- </thead>
71
- <tfoot>
72
- <tr>
73
- <th colspan="6"><div class="pull-right"><?php _e( 'Total', 'bookly' ) ?>:</div></th>
74
- <th colspan="4"><span id="bookly-payment-total"></span></th>
75
- </tr>
76
- </tfoot>
77
- </table>
78
- <div class="text-right bookly-margin-top-lg">
79
- <?php \BooklyLite\Lib\Utils\Common::deleteButton() ?>
80
- </div>
81
- </div>
82
- </div>
83
-
84
- <div ng-app="paymentDetails" ng-controller="paymentDetailsCtrl">
85
- <div payment-details-dialog></div>
86
- <?php \BooklyLite\Backend\Modules\Payments\Components::getInstance()->renderPaymentDetailsDialog() ?>
87
- </div>
88
- </div>
89
- </div>
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div id="bookly-tbs" class="wrap">
3
+ <div class="bookly-tbs-body">
4
+ <div class="page-header text-right clearfix">
5
+ <div class="bookly-page-title">
6
+ <?php _e( 'Payments', 'bookly' ) ?>
7
+ </div>
8
+ <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
9
+ </div>
10
+ <div class="panel panel-default bookly-main">
11
+ <div class="panel-body">
12
+ <div class="row">
13
+ <div class="col-md-4 col-lg-3">
14
+ <div class="bookly-margin-bottom-lg bookly-relative">
15
+ <button type="button" class="btn btn-block btn-default" id="bookly-filter-date" data-date="<?php echo date( 'Y-m-d', strtotime( '-30 day' ) ) ?> - <?php echo date( 'Y-m-d' ) ?>">
16
+ <i class="dashicons dashicons-calendar-alt"></i>
17
+ <span>
18
+ <?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( '-30 days' ) ?> - <?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( 'today' ) ?>
19
+ </span>
20
+ </button>
21
+ </div>
22
+ </div>
23
+ <div class="col-md-2 col-lg-2">
24
+ <div class="form-group">
25
+ <select id="bookly-filter-type" class="form-control bookly-js-chosen-select" data-placeholder="<?php esc_attr_e( 'Type', 'bookly' ) ?>">
26
+ <option value="-1"></option>
27
+ <?php foreach ( $types as $type ) : ?>
28
+ <option value="<?php echo esc_attr( $type ) ?>">
29
+ <?php echo \BooklyLite\Lib\Entities\Payment::typeToString( $type ) ?>
30
+ </option>
31
+ <?php endforeach ?>
32
+ </select>
33
+ </div>
34
+ </div>
35
+ <div class="col-md-3 col-lg-2">
36
+ <div class="form-group">
37
+ <select id="bookly-filter-staff" class="form-control bookly-js-chosen-select" data-placeholder="<?php esc_attr_e( 'Provider', 'bookly' ) ?>">
38
+ <option value="-1"></option>
39
+ <?php foreach ( $providers as $provider ) : ?>
40
+ <option value="<?php echo $provider['id'] ?>"><?php echo esc_html( $provider['full_name'] ) ?></option>
41
+ <?php endforeach ?>
42
+ </select>
43
+ </div>
44
+ </div>
45
+ <div class="col-md-3 col-lg-2">
46
+ <div class="form-group">
47
+ <select id="bookly-filter-service" class="form-control bookly-js-chosen-select" data-placeholder="<?php esc_attr_e( 'Service', 'bookly' ) ?>">
48
+ <option value="-1"></option>
49
+ <?php foreach ( $services as $service ) : ?>
50
+ <option value="<?php echo $service['id'] ?>"><?php echo esc_html( $service['title'] ) ?></option>
51
+ <?php endforeach ?>
52
+ </select>
53
+ </div>
54
+ </div>
55
+ </div>
56
+
57
+ <table id="bookly-payments-list" class="table table-striped" width="100%">
58
+ <thead>
59
+ <tr>
60
+ <th><?php _e( 'Date', 'bookly' ) ?></th>
61
+ <th><?php _e( 'Type', 'bookly' ) ?></th>
62
+ <th><?php _e( 'Customer', 'bookly' ) ?></th>
63
+ <th><?php _e( 'Provider', 'bookly' ) ?></th>
64
+ <th><?php _e( 'Service', 'bookly' ) ?></th>
65
+ <th><?php _e( 'Appointment Date', 'bookly' ) ?></th>
66
+ <th><?php _e( 'Amount', 'bookly' ) ?></th>
67
+ <th><?php _e( 'Status', 'bookly' ) ?></th>
68
+ <th></th>
69
+ <th width="16"><input type="checkbox" id="bookly-check-all"></th>
70
+ </tr>
71
+ </thead>
72
+ <tfoot>
73
+ <tr>
74
+ <th colspan="6"><div class="pull-right"><?php _e( 'Total', 'bookly' ) ?>:</div></th>
75
+ <th colspan="4"><span id="bookly-payment-total"></span></th>
76
+ </tr>
77
+ </tfoot>
78
+ </table>
79
+ <div class="text-right bookly-margin-top-lg">
80
+ <?php \BooklyLite\Lib\Utils\Common::deleteButton() ?>
81
+ </div>
82
+ </div>
83
+ </div>
84
+
85
+ <div ng-app="paymentDetails" ng-controller="paymentDetailsCtrl">
86
+ <div payment-details-dialog></div>
87
+ <?php \BooklyLite\Backend\Modules\Payments\Components::getInstance()->renderPaymentDetailsDialog() ?>
88
+ </div>
89
+ </div>
90
+ </div>
backend/modules/services/Controller.php CHANGED
@@ -1,288 +1,293 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Services;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Services
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- const page_slug = 'ab-services';
13
-
14
- /**
15
- * Index page.
16
- */
17
- public function index()
18
- {
19
- wp_enqueue_media();
20
- $this->enqueueStyles( array(
21
- 'wp' => array( 'wp-color-picker' ),
22
- 'frontend' => array( 'css/ladda.min.css' ),
23
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css' ),
24
- ) );
25
-
26
- $this->enqueueScripts( array(
27
- 'wp' => array( 'wp-color-picker' ),
28
- 'backend' => array(
29
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
30
- 'js/help.js' => array( 'jquery' ),
31
- 'js/alert.js' => array( 'jquery' ),
32
- ),
33
- 'module' => array( 'js/service.js' => array( 'jquery-ui-sortable', 'jquery' ) ),
34
- 'frontend' => array(
35
- 'js/spin.min.js' => array( 'jquery' ),
36
- 'js/ladda.min.js' => array( 'ab-spin.min.js', 'jquery' ),
37
- )
38
- ) );
39
-
40
- wp_localize_script( 'ab-service.js', 'BooklyL10n', array(
41
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
42
- 'saved' => __( 'Settings saved.', 'bookly' ),
43
- 'selector' => array( 'nothing_selected' => __( 'No staff selected', 'bookly' ), ),
44
- 'limitations' => __( '<b class="h4">This function is disabled in the Lite version of Bookly.</b><br><br>If you find the plugin useful for your business please consider buying a licence for the full version.<br>It costs just $59 and for this money you will get many useful functions, lifetime free updates and excellent support!<br>More information can be found here', 'bookly' ) . ': <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
45
- ) );
46
-
47
- $staff_collection = $this->getStaffCollection();
48
- $category_collection = $this->getCategoryCollection();
49
- $service_collection = $this->getServiceCollection();
50
- $this->render( 'index', compact( 'staff_collection', 'category_collection', 'service_collection' ) );
51
- }
52
-
53
- /**
54
- *
55
- */
56
- public function executeGetCategoryServices()
57
- {
58
- $this->setDataForServiceList();
59
- wp_send_json_success( $this->render( '_list', array(), false ) );
60
- }
61
-
62
- /**
63
- *
64
- */
65
- public function executeCategoryForm()
66
- {
67
- if ( ! empty ( $_POST ) ) {
68
- $form = new Forms\Category();
69
- $form->bind( $this->getPostParameters() );
70
- if ( $category = $form->save() ) {
71
- $this->render( '_category_item', array( 'category' => $category->getFields() ) );
72
- }
73
- }
74
- exit;
75
- }
76
-
77
- /**
78
- * Update category.
79
- */
80
- public function executeUpdateCategory()
81
- {
82
- $form = new Forms\Category();
83
- $form->bind( $this->getPostParameters() );
84
- $form->save();
85
- }
86
-
87
- /**
88
- * Update category position.
89
- */
90
- public function executeUpdateCategoryPosition()
91
- {
92
- $category_sorts = $this->getParameter( 'position' );
93
- foreach ( $category_sorts as $position => $category_id ) {
94
- $category_sort = new Lib\Entities\Category();
95
- $category_sort->load( $category_id );
96
- $category_sort->set( 'position', $position );
97
- $category_sort->save();
98
- }
99
- }
100
-
101
- /**
102
- * Update services position.
103
- */
104
- public function executeUpdateServicesPosition()
105
- {
106
- $services_sorts = $this->getParameter( 'position' );
107
- foreach ( $services_sorts as $position => $service_ids ) {
108
- $services_sort = new Lib\Entities\Service();
109
- $services_sort->load( $service_ids );
110
- $services_sort->set( 'position', $position );
111
- $services_sort->save();
112
- }
113
- }
114
-
115
- /**
116
- * Delete category.
117
- */
118
- public function executeDeleteCategory()
119
- {
120
- $category = new Lib\Entities\Category();
121
- $category->set( 'id', $this->getParameter( 'id', 0 ) );
122
- $category->delete();
123
- }
124
-
125
- public function executeAddService()
126
- {
127
- $form = new Forms\Service();
128
- $form->bind( $this->getPostParameters() );
129
- $form->getObject()->set( 'duration', Lib\Config::getTimeSlotLength() );
130
- $service = $form->save();
131
- $this->setDataForServiceList( $service->get( 'category_id' ) );
132
- wp_send_json_success( array( 'html' => $this->render( '_list', array(), false ), 'service_id' => $service->get( 'id' ) ) );
133
- }
134
-
135
- public function executeRemoveServices()
136
- {
137
- $service_ids = $this->getParameter( 'service_ids', array() );
138
- if ( is_array( $service_ids ) && ! empty ( $service_ids ) ) {
139
- Lib\Entities\Service::query( 's' )->delete()->whereIn( 's.id', $service_ids )->execute();
140
- }
141
- }
142
-
143
- /**
144
- * Update service parameters and assign staff
145
- */
146
- public function executeUpdateService()
147
- {
148
- /** @var \wpdb $wpdb */
149
- global $wpdb;
150
-
151
- $form = new Forms\Service();
152
- $form->bind( $this->getPostParameters() );
153
- $service = $form->save();
154
-
155
- $staff_ids = $this->getParameter( 'staff_ids', array() );
156
- if ( empty( $staff_ids ) ) {
157
- Lib\Entities\StaffService::query()->delete()->where( 'service_id', $service->get( 'id' ) )->execute();
158
- } else {
159
- Lib\Entities\StaffService::query()->delete()->where( 'service_id', $service->get( 'id' ) )->whereNotIn( 'staff_id', $staff_ids )->execute();
160
- if ( $this->getParameter( 'update_staff', false ) ) {
161
- $wpdb->update( Lib\Entities\StaffService::getTableName(), array( 'price' => $this->getParameter( 'price' ) ), array( 'service_id' => $this->getParameter( 'id' ) ) );
162
- $wpdb->update( Lib\Entities\StaffService::getTableName(), array( 'capacity' => 1 ), array( 'service_id' => $this->getParameter( 'id' ) ) );
163
- }
164
- $service_staff_exists = Lib\Entities\StaffService::query()->select( 'staff_id' )->where( 'service_id', $service->get( 'id' ) )->fetchArray();
165
- $service_staff = array();
166
- foreach ( $service_staff_exists as $staff ) {
167
- $service_staff[] = $staff['staff_id'];
168
- }
169
- foreach ( $staff_ids as $staff_id ) {
170
- if ( ! in_array( $staff_id, $service_staff ) ) {
171
- $staff_service = new Lib\Entities\StaffService();
172
- $staff_service->set( 'staff_id', $staff_id );
173
- $staff_service->set( 'service_id', $service->get( 'id' ) );
174
- $staff_service->set( 'price', $service->get( 'price' ) );
175
- $staff_service->set( 'capacity', 1 );
176
- $staff_service->save();
177
- }
178
- }
179
- }
180
-
181
- do_action( 'bookly_update_service', $service, $this->getPostParameters() );
182
-
183
- /** @var \BooklyServiceExtras\Lib\Entities\ServiceExtra[] $service_extras
184
- * @var \BooklyServiceExtras\Lib\Entities\ServiceExtra[] $db_extras
185
- */
186
- $service_extras = apply_filters( 'bookly_extras_find_by_service_id', array(), $service->get( 'id' ) );
187
- $db_extras = array();
188
- foreach ( $service_extras as $extra ) {
189
- $db_extras[ $extra->get( 'id' ) ] = $extra;
190
- }
191
- $new_extras = array();
192
- // Find id for new extras.
193
- foreach ( $this->getParameter( 'extras', array() ) as $_post_id => $_post_extra ) {
194
- if ( isset( $db_extras[ $_post_id ] ) ) {
195
- unset( $db_extras[ $_post_id ] );
196
- } else {
197
- foreach ( $db_extras as $id => $extra ) {
198
- if ( $extra->get( 'title' ) == $_post_extra['title']
199
- && $extra->get( 'price' ) == $_post_extra['price']
200
- && $extra->get( 'duration' ) == $_post_extra['duration']
201
- ) {
202
- $new_extras[ $_post_id ] = $id;
203
- unset( $db_extras[ $id ] );
204
- break;
205
- }
206
- }
207
- }
208
- }
209
-
210
- $price = Lib\Utils\Common::formatPrice( $service->get( 'price' ) );
211
- if ( $service->get( 'type' ) == Lib\Entities\Service::TYPE_SIMPLE ) {
212
- $nice_duration = Lib\Utils\DateTime::secondsToInterval( $service->get( 'duration' ) );
213
- } else {
214
- $nice_duration = sprintf( _n( '%d service', '%d services', count( json_decode( $service->get( 'sub_services' ), true ) ), 'bookly' ), count( json_decode( $service->get( 'sub_services' ), true ) ) );
215
- }
216
-
217
- wp_send_json_success( array( 'title' => $service->get( 'title' ), 'price' => $price, 'color' => $service->get( 'color' ), 'nice_duration' => $nice_duration, 'new_extras' => $new_extras ) );
218
- }
219
-
220
- /**
221
- * @param int $category_id
222
- */
223
- private function setDataForServiceList( $category_id = 0 )
224
- {
225
- if ( ! $category_id ) {
226
- $category_id = $this->getParameter( 'category_id', 0 );
227
- }
228
-
229
- $this->service_collection = $this->getServiceCollection( $category_id );
230
- $this->staff_collection = $this->getStaffCollection();
231
- $this->category_collection = $this->getCategoryCollection();
232
- }
233
-
234
- /**
235
- * @return array
236
- */
237
- private function getCategoryCollection()
238
- {
239
- return Lib\Entities\Category::query()->sortBy( 'position' )->fetchArray();
240
- }
241
-
242
- /**
243
- * @return array
244
- */
245
- private function getStaffCollection()
246
- {
247
- return Lib\Entities\Staff::query()->fetchArray();
248
- }
249
-
250
- /**
251
- * @param int $id
252
- * @return array
253
- */
254
- private function getServiceCollection( $id = 0 )
255
- {
256
- $services = Lib\Entities\Service::query( 's' )
257
- ->select( 's.*, COUNT(staff.id) AS total_staff, GROUP_CONCAT(DISTINCT staff.id) AS staff_ids' )
258
- ->leftJoin( 'StaffService', 'ss', 'ss.service_id = s.id' )
259
- ->leftJoin( 'Staff', 'staff', 'staff.id = ss.staff_id' )
260
- ->whereRaw( 's.category_id = %d OR !%d', array( $id, $id ) )
261
- ->groupBy( 's.id' )
262
- ->indexBy( 'id' )
263
- ->sortBy( 's.position' );
264
-
265
- return $services->fetchArray();
266
- }
267
-
268
- public function executeUpdateExtraPosition()
269
- {
270
- do_action( 'bookly_extras_reorder', $this->getParameter( 'position' ) );
271
-
272
- wp_send_json_success();
273
- }
274
-
275
- // Protected methods.
276
-
277
- /**
278
- * Override parent method to add 'wp_ajax_ab_' prefix
279
- * so current 'execute*' methods look nicer.
280
- *
281
- * @param string $prefix
282
- */
283
- protected function registerWpActions( $prefix = '' )
284
- {
285
- parent::registerWpActions( 'wp_ajax_ab_' );
286
- }
287
-
 
 
 
 
 
288
  }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Services;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Controller
8
+ * @package BooklyLite\Backend\Modules\Services
9
+ */
10
+ class Controller extends Lib\Base\Controller
11
+ {
12
+ const page_slug = 'bookly-services';
13
+
14
+ /**
15
+ * Index page.
16
+ */
17
+ public function index()
18
+ {
19
+ wp_enqueue_media();
20
+ $this->enqueueStyles( array(
21
+ 'wp' => array( 'wp-color-picker' ),
22
+ 'frontend' => array( 'css/ladda.min.css' ),
23
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css' ),
24
+ ) );
25
+
26
+ $this->enqueueScripts( array(
27
+ 'wp' => array( 'wp-color-picker' ),
28
+ 'backend' => array(
29
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
30
+ 'js/help.js' => array( 'jquery' ),
31
+ 'js/alert.js' => array( 'jquery' ),
32
+ 'js/range_tools.js' => array( 'jquery' ),
33
+ ),
34
+ 'module' => array( 'js/service.js' => array( 'jquery-ui-sortable', 'jquery' ) ),
35
+ 'frontend' => array(
36
+ 'js/spin.min.js' => array( 'jquery' ),
37
+ 'js/ladda.min.js' => array( 'bookly-spin.min.js', 'jquery' ),
38
+ )
39
+ ) );
40
+
41
+ wp_localize_script( 'bookly-service.js', 'BooklyL10n', array(
42
+ 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
43
+ 'saved' => __( 'Settings saved.', 'bookly' ),
44
+ 'selector' => array( 'nothing_selected' => __( 'No staff selected', 'bookly' ), ),
45
+ 'limitations' => __( '<b class="h4">This function is disabled in the Lite version of Bookly.</b><br><br>If you find the plugin useful for your business please consider buying a licence for the full version.<br>It costs just $59 and for this money you will get many useful functions, lifetime free updates and excellent support!<br>More information can be found here', 'bookly' ) . ': <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
46
+ ) );
47
+
48
+ // Allow add-ons to enqueue their assets.
49
+ do_action( 'bookly_enqueue_assets_for_services' );
50
+
51
+ $staff_collection = $this->getStaffCollection();
52
+ $category_collection = $this->getCategoryCollection();
53
+ $service_collection = $this->getServiceCollection();
54
+ $this->render( 'index', compact( 'staff_collection', 'category_collection', 'service_collection' ) );
55
+ }
56
+
57
+ /**
58
+ *
59
+ */
60
+ public function executeGetCategoryServices()
61
+ {
62
+ $this->setDataForServiceList();
63
+ wp_send_json_success( $this->render( '_list', array(), false ) );
64
+ }
65
+
66
+ /**
67
+ *
68
+ */
69
+ public function executeCategoryForm()
70
+ {
71
+ if ( ! empty ( $_POST ) ) {
72
+ $form = new Forms\Category();
73
+ $form->bind( $this->getPostParameters() );
74
+ if ( $category = $form->save() ) {
75
+ $this->render( '_category_item', array( 'category' => $category->getFields() ) );
76
+ }
77
+ }
78
+ exit;
79
+ }
80
+
81
+ /**
82
+ * Update category.
83
+ */
84
+ public function executeUpdateCategory()
85
+ {
86
+ $form = new Forms\Category();
87
+ $form->bind( $this->getPostParameters() );
88
+ $form->save();
89
+ }
90
+
91
+ /**
92
+ * Update category position.
93
+ */
94
+ public function executeUpdateCategoryPosition()
95
+ {
96
+ $category_sorts = $this->getParameter( 'position' );
97
+ foreach ( $category_sorts as $position => $category_id ) {
98
+ $category_sort = new Lib\Entities\Category();
99
+ $category_sort->load( $category_id );
100
+ $category_sort->set( 'position', $position );
101
+ $category_sort->save();
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Update services position.
107
+ */
108
+ public function executeUpdateServicesPosition()
109
+ {
110
+ $services_sorts = $this->getParameter( 'position' );
111
+ foreach ( $services_sorts as $position => $service_ids ) {
112
+ $services_sort = new Lib\Entities\Service();
113
+ $services_sort->load( $service_ids );
114
+ $services_sort->set( 'position', $position );
115
+ $services_sort->save();
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Delete category.
121
+ */
122
+ public function executeDeleteCategory()
123
+ {
124
+ $category = new Lib\Entities\Category();
125
+ $category->set( 'id', $this->getParameter( 'id', 0 ) );
126
+ $category->delete();
127
+ }
128
+
129
+ public function executeAddService()
130
+ {
131
+ $form = new Forms\Service();
132
+ $form->bind( $this->getPostParameters() );
133
+ $form->getObject()->set( 'duration', Lib\Config::getTimeSlotLength() );
134
+ $service = $form->save();
135
+ $this->setDataForServiceList( $service->get( 'category_id' ) );
136
+ do_action( 'bookly_service_created', $service, $this->getPostParameters() );
137
+ wp_send_json_success( array( 'html' => $this->render( '_list', array(), false ), 'service_id' => $service->get( 'id' ) ) );
138
+ }
139
+
140
+ public function executeRemoveServices()
141
+ {
142
+ $service_ids = $this->getParameter( 'service_ids', array() );
143
+ if ( is_array( $service_ids ) && ! empty ( $service_ids ) ) {
144
+ Lib\Entities\Service::query( 's' )->delete()->whereIn( 's.id', $service_ids )->execute();
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Update service parameters and assign staff
150
+ */
151
+ public function executeUpdateService()
152
+ {
153
+ /** @var \wpdb $wpdb */
154
+ global $wpdb;
155
+
156
+ $form = new Forms\Service();
157
+ $form->bind( $this->getPostParameters() );
158
+ $service = $form->save();
159
+
160
+ $staff_ids = $this->getParameter( 'staff_ids', array() );
161
+ if ( empty( $staff_ids ) ) {
162
+ Lib\Entities\StaffService::query()->delete()->where( 'service_id', $service->get( 'id' ) )->execute();
163
+ } else {
164
+ Lib\Entities\StaffService::query()->delete()->where( 'service_id', $service->get( 'id' ) )->whereNotIn( 'staff_id', $staff_ids )->execute();
165
+ if ( $this->getParameter( 'update_staff', false ) ) {
166
+ $wpdb->update( Lib\Entities\StaffService::getTableName(), array( 'price' => $this->getParameter( 'price' ) ), array( 'service_id' => $this->getParameter( 'id' ) ) );
167
+ $wpdb->update( Lib\Entities\StaffService::getTableName(), array( 'capacity' => 1 ), array( 'service_id' => $this->getParameter( 'id' ) ) );
168
+ }
169
+ $service_staff_exists = Lib\Entities\StaffService::query()->select( 'staff_id' )->where( 'service_id', $service->get( 'id' ) )->fetchArray();
170
+ $service_staff = array();
171
+ foreach ( $service_staff_exists as $staff ) {
172
+ $service_staff[] = $staff['staff_id'];
173
+ }
174
+ foreach ( $staff_ids as $staff_id ) {
175
+ if ( ! in_array( $staff_id, $service_staff ) ) {
176
+ $staff_service = new Lib\Entities\StaffService();
177
+ $staff_service->set( 'staff_id', 1 );
178
+ $staff_service->set( 'service_id', $service->get( 'id' ) );
179
+ $staff_service->set( 'price', $service->get( 'price' ) );
180
+ $staff_service->set( 'capacity', 1 );
181
+ $staff_service->save();
182
+ }
183
+ }
184
+ }
185
+
186
+ do_action( 'bookly_update_service', $service, $this->getPostParameters() );
187
+
188
+ /** @var \BooklyServiceExtras\Lib\Entities\ServiceExtra[] $service_extras
189
+ * @var \BooklyServiceExtras\Lib\Entities\ServiceExtra[] $db_extras
190
+ */
191
+ $service_extras = apply_filters( 'bookly_service_extras_find_by_service_id', array(), $service->get( 'id' ) );
192
+ $db_extras = array();
193
+ foreach ( $service_extras as $extra ) {
194
+ $db_extras[ $extra->get( 'id' ) ] = $extra;
195
+ }
196
+ $new_extras = array();
197
+ // Find id for new extras.
198
+ foreach ( $this->getParameter( 'extras', array() ) as $_post_id => $_post_extra ) {
199
+ if ( isset( $db_extras[ $_post_id ] ) ) {
200
+ unset( $db_extras[ $_post_id ] );
201
+ } else {
202
+ foreach ( $db_extras as $id => $extra ) {
203
+ if ( $extra->get( 'title' ) == $_post_extra['title']
204
+ && $extra->get( 'price' ) == $_post_extra['price']
205
+ && $extra->get( 'duration' ) == $_post_extra['duration']
206
+ ) {
207
+ $new_extras[ $_post_id ] = $id;
208
+ unset( $db_extras[ $id ] );
209
+ break;
210
+ }
211
+ }
212
+ }
213
+ }
214
+
215
+ $price = Lib\Utils\Common::formatPrice( $service->get( 'price' ) );
216
+ if ( $service->get( 'type' ) == Lib\Entities\Service::TYPE_SIMPLE ) {
217
+ $nice_duration = Lib\Utils\DateTime::secondsToInterval( $service->get( 'duration' ) );
218
+ } else {
219
+ $nice_duration = sprintf( _n( '%d service', '%d services', count( json_decode( $service->get( 'sub_services' ), true ) ), 'bookly' ), count( json_decode( $service->get( 'sub_services' ), true ) ) );
220
+ }
221
+
222
+ wp_send_json_success( array( 'title' => $service->get( 'title' ), 'price' => $price, 'color' => $service->get( 'color' ), 'nice_duration' => $nice_duration, 'new_extras' => $new_extras ) );
223
+ }
224
+
225
+ /**
226
+ * @param int $category_id
227
+ */
228
+ private function setDataForServiceList( $category_id = 0 )
229
+ {
230
+ if ( ! $category_id ) {
231
+ $category_id = $this->getParameter( 'category_id', 0 );
232
+ }
233
+
234
+ $this->service_collection = $this->getServiceCollection( $category_id );
235
+ $this->staff_collection = $this->getStaffCollection();
236
+ $this->category_collection = $this->getCategoryCollection();
237
+ }
238
+
239
+ /**
240
+ * @return array
241
+ */
242
+ private function getCategoryCollection()
243
+ {
244
+ return Lib\Entities\Category::query()->sortBy( 'position' )->fetchArray();
245
+ }
246
+
247
+ /**
248
+ * @return array
249
+ */
250
+ private function getStaffCollection()
251
+ {
252
+ return Lib\Entities\Staff::query()->fetchArray();
253
+ }
254
+
255
+ /**
256
+ * @param int $id
257
+ * @return array
258
+ */
259
+ private function getServiceCollection( $id = 0 )
260
+ {
261
+ $services = Lib\Entities\Service::query( 's' )
262
+ ->select( 's.*, COUNT(staff.id) AS total_staff, GROUP_CONCAT(DISTINCT staff.id) AS staff_ids' )
263
+ ->leftJoin( 'StaffService', 'ss', 'ss.service_id = s.id' )
264
+ ->leftJoin( 'Staff', 'staff', 'staff.id = ss.staff_id' )
265
+ ->whereRaw( 's.category_id = %d OR !%d', array( $id, $id ) )
266
+ ->groupBy( 's.id' )
267
+ ->indexBy( 'id' )
268
+ ->sortBy( 's.position' );
269
+
270
+ return $services->fetchArray();
271
+ }
272
+
273
+ public function executeUpdateExtraPosition()
274
+ {
275
+ do_action( 'bookly_service_extras_reorder', $this->getParameter( 'position' ) );
276
+
277
+ wp_send_json_success();
278
+ }
279
+
280
+ // Protected methods.
281
+
282
+ /**
283
+ * Override parent method to add 'wp_ajax_bookly_' prefix
284
+ * so current 'execute*' methods look nicer.
285
+ *
286
+ * @param string $prefix
287
+ */
288
+ protected function registerWpActions( $prefix = '' )
289
+ {
290
+ parent::registerWpActions( 'wp_ajax_bookly_' );
291
+ }
292
+
293
  }
backend/modules/services/forms/Category.php CHANGED
@@ -1,22 +1,22 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Services\Forms;
3
-
4
- use \BooklyLite\Lib;
5
-
6
- /**
7
- * Class Category
8
- * @package BooklyLite\Backend\Modules\Services\Forms
9
- */
10
- class Category extends Lib\Base\Form
11
- {
12
- protected static $entity_class = 'Category';
13
-
14
- /**
15
- * Configure the form.
16
- */
17
- public function configure()
18
- {
19
- $this->setFields( array( 'name' ) );
20
- }
21
-
22
- }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Services\Forms;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Category
8
+ * @package BooklyLite\Backend\Modules\Services\Forms
9
+ */
10
+ class Category extends Lib\Base\Form
11
+ {
12
+ protected static $entity_class = 'Category';
13
+
14
+ /**
15
+ * Configure the form.
16
+ */
17
+ public function configure()
18
+ {
19
+ $this->setFields( array( 'name' ) );
20
+ }
21
+
22
+ }
backend/modules/services/forms/Service.php CHANGED
@@ -1,52 +1,75 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Services\Forms;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Service
8
- * @package BooklyLite\Backend\Modules\Services\Forms
9
- */
10
- class Service extends Lib\Base\Form
11
- {
12
- protected static $entity_class = 'Service';
13
-
14
- public function configure()
15
- {
16
- $this->setFields( array( 'id', 'title', 'duration', 'price', 'category_id', 'color', 'capacity', 'info', 'type', 'sub_services', 'visibility' ) );
17
- }
18
-
19
- /**
20
- * Bind values to form.
21
- *
22
- * @param array $_post
23
- * @param array $files
24
- */
25
- public function bind( array $_post, array $files = array() )
26
- {
27
- if ( array_key_exists( 'category_id', $_post ) && ! $_post['category_id'] ) {
28
- $_post['category_id'] = null;
29
- }
30
- parent::bind( $_post, $files );
31
- }
32
-
33
- /**
34
- * @return \BooklyLite\Lib\Entities\Service
35
- */
36
- public function save()
37
- {
38
- if ( $this->isNew() ) {
39
- // When adding new service - set its color randomly.
40
- $this->data['color'] = sprintf( '#%06X', mt_rand( 0, 0x64FFFF ) );
41
- }
42
-
43
- if ( $this->data['type'] == Lib\Entities\Service::TYPE_SIMPLE || ! array_key_exists( 'sub_services', $this->data ) || empty( $this->data['sub_services'] ) ) {
44
- $this->data['sub_services'] = '[]';
45
- } elseif ( is_array( $this->data['sub_services'] ) ) {
46
- $this->data['sub_services'] = json_encode( (array) $this->data['sub_services'] );
47
- }
48
-
49
- return parent::save();
50
- }
51
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  }
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Services\Forms;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Service
8
+ * @package BooklyLite\Backend\Modules\Services\Forms
9
+ */
10
+ class Service extends Lib\Base\Form
11
+ {
12
+ protected static $entity_class = 'Service';
13
+
14
+ public function configure()
15
+ {
16
+ $fields = array(
17
+ 'id',
18
+ 'title',
19
+ 'duration',
20
+ 'price',
21
+ 'category_id',
22
+ 'color',
23
+ 'capacity',
24
+ 'padding_left',
25
+ 'padding_right',
26
+ 'info',
27
+ 'type',
28
+ 'sub_services',
29
+ 'visibility',
30
+ );
31
+ if ( Lib\Config::isServiceScheduleEnabled() ) {
32
+ $fields[] = 'start_time';
33
+ $fields[] = 'end_time';
34
+ }
35
+
36
+ $this->setFields( $fields );
37
+ }
38
+
39
+ /**
40
+ * Bind values to form.
41
+ *
42
+ * @param array $_post
43
+ * @param array $files
44
+ */
45
+ public function bind( array $_post, array $files = array() )
46
+ {
47
+ // Fields with NULL
48
+ foreach ( array( 'category_id', 'start_time', 'end_time' ) as $field_name ) {
49
+ if ( array_key_exists( $field_name, $_post ) && ! $_post[ $field_name ] ) {
50
+ $_post[ $field_name ] = null;
51
+ }
52
+ }
53
+ parent::bind( $_post, $files );
54
+ }
55
+
56
+ /**
57
+ * @return \BooklyLite\Lib\Entities\Service
58
+ */
59
+ public function save()
60
+ {
61
+ if ( $this->isNew() ) {
62
+ // When adding new service - set its color randomly.
63
+ $this->data['color'] = sprintf( '#%06X', mt_rand( 0, 0x64FFFF ) );
64
+ }
65
+
66
+ if ( $this->data['type'] == Lib\Entities\Service::TYPE_SIMPLE || ! array_key_exists( 'sub_services', $this->data ) || empty( $this->data['sub_services'] ) ) {
67
+ $this->data['sub_services'] = '[]';
68
+ } elseif ( is_array( $this->data['sub_services'] ) ) {
69
+ $this->data['sub_services'] = json_encode( (array) $this->data['sub_services'] );
70
+ }
71
+
72
+ return parent::save();
73
+ }
74
+
75
  }
backend/modules/services/resources/js/service.js CHANGED
@@ -1,511 +1,511 @@
1
- jQuery(function($) {
2
- var $no_result = $('#bookly-services-wrapper .no-result');
3
- // Remember user choice in the modal dialog.
4
- var update_staff_choice = null,
5
- $new_category_popover = $('#bookly-new-category'),
6
- $new_category_form = $('#new-category-form'),
7
- $new_category_name = $('#bookly-category-name');
8
-
9
- $new_category_popover.popover({
10
- html: true,
11
- placement: 'bottom',
12
- template: '<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>',
13
- content: $new_category_form.show().detach(),
14
- trigger: 'manual'
15
- }).on('click', function () {
16
- $(this).popover('toggle');
17
- }).on('shown.bs.popover', function () {
18
- // focus input
19
- $new_category_name.focus();
20
- }).on('hidden.bs.popover', function (e) {
21
- //clear input
22
- $new_category_name.val('');
23
- });
24
-
25
- // Save new category.
26
- $new_category_form.on('submit', function() {
27
- var data = $(this).serialize();
28
-
29
- $.post(ajaxurl, data, function(response) {
30
- $('#bookly-category-item-list').append(response);
31
- var $new_category = $('.bookly-category-item:last');
32
- // add created category to services
33
- $('select[name="category_id"]').append('<option value="' + $new_category.data('category-id') + '">' + $new_category.find('input').val() + '</option>');
34
- });
35
- $new_category_popover.popover('hide');
36
- return false;
37
- });
38
-
39
- // Cancel button.
40
- $new_category_form.on('click', 'button[type="button"]', function (e) {
41
- $new_category_popover.popover('hide');
42
- });
43
-
44
- // Categories list delegated events.
45
- $('#bookly-categories-list')
46
-
47
- // On category item click.
48
- .on('click', '.bookly-category-item', function(e) {
49
- if ($(e.target).is('.bookly-js-handle')) return;
50
- $('#ab-services-list').html('<div class="bookly-loading"></div>');
51
- var $clicked = $(this);
52
-
53
- $.get(ajaxurl, {action:'ab_get_category_services', category_id: $clicked.data('category-id')}, function(response) {
54
- if ( response.success ) {
55
- $('.bookly-category-item').not($clicked).removeClass('active');
56
- $clicked.addClass('active');
57
- $('.bookly-category-title').text($clicked.text());
58
- refreshList(response.data, 0);
59
- }
60
- });
61
- })
62
-
63
- // On edit category click.
64
- .on('click', '.bookly-js-edit', function(e) {
65
- // Keep category item click from being executed.
66
- e.stopPropagation();
67
- // Prevent navigating to '#'.
68
- e.preventDefault();
69
- var $this = $(this).closest('.bookly-category-item');
70
- $this.find('.displayed-value').hide();
71
- $this.find('input').show().focus();
72
- })
73
-
74
- // On blur save changes.
75
- .on('blur', 'input', function() {
76
- var $this = $(this),
77
- $item = $this.closest('.bookly-category-item'),
78
- field = $this.attr('name'),
79
- value = $this.val(),
80
- id = $item.data('category-id'),
81
- data = { action: 'ab_update_category', id: id };
82
- data[field] = value;
83
- $.post(ajaxurl, data, function(response) {
84
- // Hide input field.
85
- $item.find('input').hide();
86
- $item.find('.displayed-value').show();
87
- // Show modified category name.
88
- $item.find('.displayed-value').text(value);
89
- // update edited category's name for services
90
- $('select[name="category_id"] option[value="' + id + '"]').text(value);
91
- });
92
- })
93
-
94
- // On press Enter save changes.
95
- .on('keypress', 'input', function (e) {
96
- var code = e.keyCode || e.which;
97
- if (code == 13) {
98
- $(this).blur();
99
- }
100
- })
101
-
102
- // On delete category click.
103
- .on('click', '.bookly-js-delete', function(e) {
104
- // Keep category item click from being executed.
105
- e.stopPropagation();
106
- // Prevent navigating to '#'.
107
- e.preventDefault();
108
- // Ask user if he is sure.
109
- if (confirm(BooklyL10n.are_you_sure)) {
110
- var $item = $(this).closest('.bookly-category-item');
111
- var data = { action: 'ab_delete_category', id: $item.data('category-id') };
112
- $.post(ajaxurl, data, function(response) {
113
- // Remove category item from Services
114
- $('select[name="category_id"] option[value="' + $item.data('category-id') + '"]').remove();
115
- // Remove category item from DOM.
116
- $item.remove();
117
- if ($item.is('.active')) {
118
- $('.bookly-js-all-services').click();
119
- }
120
- });
121
- }
122
- })
123
-
124
- .on('click', 'input', function(e) {
125
- e.stopPropagation();
126
- });
127
-
128
- // Services list delegated events.
129
- $('#bookly-services-wrapper')
130
- // On click on 'Add Service' button.
131
- .on('click', '.add-service', function(e) {
132
- e.preventDefault();
133
- var ladda = Ladda.create(this);
134
- ladda.start();
135
- var selected_category_id = $('#bookly-categories-list .active').data('category-id'),
136
- data = { action: 'ab_add_service' };
137
- if (selected_category_id) {
138
- data['category_id'] = selected_category_id;
139
- }
140
- $.post(ajaxurl, data, function(response) {
141
- refreshList(response.data.html, response.data.service_id);
142
- ladda.stop();
143
- });
144
- })
145
- // On click on 'Delete' button.
146
- .on('click', '#bookly-delete', function(e) {
147
- if (confirm(BooklyL10n.are_you_sure)) {
148
- var ladda = Ladda.create(this);
149
- ladda.start();
150
-
151
- var $for_delete = $('.service-checker:checked'),
152
- data = { action: 'ab_remove_services' },
153
- services = [],
154
- $panels = [];
155
-
156
- $for_delete.each(function(){
157
- var panel = $(this).parents('.bookly-js-collapse');
158
- $panels.push(panel);
159
- services.push(this.value);
160
- });
161
- data['service_ids[]'] = services;
162
- $.post(ajaxurl, data, function() {
163
- ladda.stop();
164
- $.each($panels.reverse(), function (index) {
165
- $(this).delay(500 * index).fadeOut(200, function () {
166
- $(this).remove();
167
- });
168
- });
169
- });
170
- }
171
- })
172
-
173
- .on('change', 'input.bookly-check-all-entities, input.bookly-js-check-entity', function () {
174
- var $panel = $(this).parents('.bookly-js-collapse');
175
- if ($(this).hasClass('bookly-check-all-entities')) {
176
- $panel.find('.bookly-js-check-entity').prop('checked', $(this).prop('checked'));
177
- } else {
178
- $panel.find('.bookly-check-all-entities').prop('checked', $panel.find('.bookly-js-check-entity:not(:checked)').length == 0);
179
- }
180
- updateStaffButton($panel);
181
- });
182
-
183
- // Modal window events.
184
- var $modal = $('#ab-staff-update');
185
- $modal
186
- .on('click', '.ab-yes', function() {
187
- $modal.modal('hide');
188
- if ( $('#ab-remember-my-choice').prop('checked') ) {
189
- update_staff_choice = true;
190
- }
191
- submitServiceFrom($modal.data('input'),true);
192
- })
193
- .on('click', '.ab-no', function() {
194
- if ( $('#ab-remember-my-choice').prop('checked') ) {
195
- update_staff_choice = false;
196
- }
197
- submitServiceFrom($modal.data('input'),false);
198
- });
199
-
200
- function refreshList(response,service_id) {
201
- var $list = $('#ab-services-list');
202
- $list.html(response);
203
- if (response.indexOf('panel') >= 0) {
204
- $no_result.hide();
205
- makeServicesSortable();
206
- onCollapseInitChildren();
207
- $list.booklyHelp();
208
- } else {
209
- $no_result.show();
210
- }
211
- $('#service_' + service_id).collapse('show');
212
- $('#service_' + service_id).find('input[name=title]').focus();
213
- }
214
-
215
- function initColorPicker($jquery_collection) {
216
- $jquery_collection.each(function(){
217
- $(this).data('last-color', $(this).val());
218
- });
219
- $jquery_collection.wpColorPicker({
220
- width: 200
221
- });
222
- }
223
-
224
- $('#ab-services-list').on('change', '[name=capacity]', function(){
225
- if ($(this).val() > 1) {
226
- booklyAlert({error: [BooklyL10n.limitations]});
227
- $(this).val('1').prop('readonly',true);
228
- }
229
- }).on('change', '[name=padding_left],[name=padding_right]', function(){
230
- if ($(this).val() > 0) {
231
- booklyAlert({error: [BooklyL10n.limitations]});
232
- $(this).val('0').prop('readonly', true);
233
- $(this).find('option:gt(0)').prop('disabled', true);
234
- }
235
- });
236
-
237
- function submitServiceFrom($form, update_staff) {
238
- $form.find('input[name=update_staff]').val(update_staff ? 1 : 0);
239
- var ladda = Ladda.create($form.find('button[type=submit]').get(0)),
240
- data = $form.serializeArray();
241
- ladda.start();
242
- if ($form.find('input[name=type]:checked').val() == 'compound') {
243
- $form.find('li[data-sub-service-id]').each(function () {
244
- data.push({name: 'sub_services[]', value: $(this).data('sub-service-id')});
245
- });
246
- } else {
247
- data.push({name: 'type', value: 'simple'});
248
- data.push({name: 'sub_services[]', value: false});
249
- }
250
- $.post(ajaxurl, data, function (response) {
251
- if (response.success) {
252
- var $panel = $form.parents('.bookly-js-collapse'),
253
- $price = $form.find('[name=price]'),
254
- $capacity = $form.find('[name=capacity]');
255
- $panel.find('.bookly-js-service-color').css('background-color', response.data.color);
256
- $panel.find('.bookly-js-service-title').html(response.data.title);
257
- $panel.find('.bookly-js-service-duration').html(response.data.nice_duration);
258
- $panel.find('.bookly-js-service-price').html(response.data.price);
259
-
260
-
261
- $price.data('last_value', $price.val());
262
- $capacity.data('last_value', $capacity.val());
263
- booklyAlert({success : [BooklyL10n.saved]});
264
- $.each(response.data.new_extras, function (front_id, real_id) {
265
- var $li = $('li.extra.new[data-extra-id="' + front_id + '"]', $form);
266
- $('[name^="extras"]', $li).each(function () {
267
- var name = $(this).attr('name');
268
- name = name.replace('[' + front_id + ']', '[' + real_id + ']');
269
- $(this).attr('name', name);
270
- });
271
- $li.data('extra-id', real_id).removeClass('new');
272
- $li.append('<input type="hidden" value="' + real_id + '" name="extras[' + real_id + '][id]">');
273
- });
274
- } else {
275
- booklyAlert({error: [response.data.message]});
276
- }
277
- }, 'json').always(function() {
278
- ladda.stop();
279
- });
280
- }
281
-
282
- function updateStaffButton($panel) {
283
- var staff_checked = $panel.find('.bookly-js-check-entity:checked').length;
284
- if (staff_checked == 0) {
285
- $panel.find('.bookly-entity-counter').text(BooklyL10n.selector.nothing_selected);
286
- } else if (staff_checked == 1) {
287
- $panel.find('.bookly-entity-counter').text($panel.find('.bookly-js-check-entity:checked').data('staff_name'));
288
- } else {
289
- $panel.find('.bookly-entity-counter').text(staff_checked + '/' + $panel.find('.bookly-js-check-entity').length);
290
- }
291
- }
292
-
293
- var $category = $('#bookly-category-item-list');
294
- $category.sortable({
295
- axis : 'y',
296
- handle : '.bookly-js-handle',
297
- update : function( event, ui ) {
298
- var data = [];
299
- $category.children('li').each(function() {
300
- var $this = $(this);
301
- var position = $this.data('category-id');
302
- data.push(position);
303
- });
304
- $.ajax({
305
- type : 'POST',
306
- url : ajaxurl,
307
- data : { action: 'ab_update_category_position', position: data }
308
- });
309
- }
310
- });
311
-
312
- function makeServicesSortable() {
313
- if ($('.bookly-js-all-services').hasClass('active')) {
314
- var $services = $('#services_list'),
315
- fixHelper = function(e, ui) {
316
- ui.children().each(function() {
317
- $(this).width($(this).width());
318
- });
319
- return ui;
320
- };
321
- $services.sortable({
322
- helper : fixHelper,
323
- axis : 'y',
324
- handle : '.bookly-js-handle',
325
- update : function( event, ui ) {
326
- var data = [];
327
- $services.children('div').each(function() {
328
- data.push($(this).data('service-id'));
329
- });
330
- $.ajax({
331
- type : 'POST',
332
- url : ajaxurl,
333
- data : { action: 'ab_update_services_position', position: data }
334
- });
335
- }
336
- });
337
- } else {
338
- $('#services_list .bookly-js-handle').hide();
339
- }
340
- }
341
-
342
- function onCollapseInitChildren() {
343
- $('.panel-collapse').on('show.bs.collapse.bookly', function () {
344
- var $panel = $(this);
345
- var $sub_services = $('.ab--service-list', $panel);
346
- initColorPicker($panel.find('.bookly-js-color-picker'));
347
- $('input[name=type]', $panel).on( 'click', function(){
348
- if ($(this).val() == 'simple') {
349
- $('.ab--for-simple', $panel).show();
350
- $('.ab--for-compound', $panel).hide();
351
- } else {
352
- $('.ab--for-simple', $panel).hide();
353
- $('.ab--for-compound', $panel).show();
354
- }
355
- });
356
- $('input[name=type]:checked', $panel).trigger('click');
357
-
358
- $('[data-toggle="popover"]').popover({
359
- html: true,
360
- placement: 'top',
361
- trigger: 'hover',
362
- template: '<div class="popover bookly-font-xs" style="width: 220px" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
363
- });
364
-
365
- var initSubServicesLi = function ($li) {
366
- $("option[value=" + $li.data('sub-service-id') + "]", $sub_services).prop('disabled', true);
367
- $('.ab--sub-service-remove', $li).click(function () {
368
- $('li.list-group-item[data-sub-service-id="' + $li.data('sub-service-id') + '"]', $panel).remove();
369
- $("option[value=" + $li.data('sub-service-id') + "]", $sub_services).prop('disabled', false);
370
- });
371
- };
372
-
373
- $('.ab--sub-services li.list-group-item[data-sub-service-id]', $panel).each(function () {
374
- initSubServicesLi($(this));
375
- });
376
-
377
- $sub_services.on('change', function () {
378
- if ($(this).val()) {
379
- var $li = $('.ab--templates.services .template_' + $sub_services.val() + ' li').clone();
380
- $li.insertBefore($(this).parents('li'));
381
- initSubServicesLi($li);
382
- $(this).val(0);
383
- }
384
- });
385
-
386
- $('.ab--sub-services', $panel).sortable({axis: 'y', items: "[data-sub-service-id]"});
387
-
388
- updateStaffButton($(this).parents('.bookly-js-collapse'));
389
-
390
- $panel.find('.ajax-service-send').on('click', function (e) {
391
- e.preventDefault();
392
- var $form = $(this).parents('form'),
393
- show_modal = false;
394
- if(update_staff_choice === null) {
395
- $('.ab-question', $form).each(function () {
396
- if ($(this).data('last_value') != $(this).val()) {
397
- show_modal = true;
398
- }
399
- });
400
- }
401
- if (show_modal) {
402
- $modal.data('input', $form).modal('show');
403
- } else {
404
- submitServiceFrom($form, update_staff_choice);
405
- }
406
- });
407
-
408
- $panel.find('.js-reset').on('click', function () {
409
- $(this).parents('form').trigger('reset');
410
- var $color = $(this).parents('form').find('.wp-color-picker'),
411
- $panel = $(this).parents('.bookly-js-collapse');
412
- $color.val($color.data('last-color')).trigger('change');
413
- updateStaffButton($panel);
414
- });
415
- $panel.find('.ab-question').each(function () {
416
- $(this).data('last_value', $(this).val());
417
- });
418
- $panel.unbind('show.bs.collapse.bookly');
419
- });
420
- }
421
- makeServicesSortable();
422
- onCollapseInitChildren();
423
-
424
- /*<Extras>*/
425
- $('.extras-container').sortable({
426
- axis : 'y',
427
- handle : '.bookly-js-handle',
428
- update : function( event, ui ) {
429
- var data = [];
430
- $(this).find('.extra').each(function() {
431
- data.push($(this).data('extra-id'));
432
- });
433
- $.ajax({
434
- type : 'POST',
435
- url : ajaxurl,
436
- data : { action: 'ab_update_extra_position', position: data }
437
- });
438
- }
439
- });
440
-
441
- $(document).on('click', '.bookly-js-collapse .extra-new', function (e) {
442
- e.preventDefault();
443
- e.stopPropagation();
444
- var children = $('.extras-container li');
445
-
446
- var id = 1;
447
- children.each(function (i, el) {
448
- var elId = parseInt($(el).data('extra-id'));
449
- id = (elId >= id) ? elId + 1 : id;
450
- });
451
- var template = $('.ab--templates.extras').html();
452
- var $container = $(this).parents('.bookly-js-collapse').find('.extras-container');
453
- id++;
454
- $container.append(
455
- template.replace(/%id%/g, id)
456
- );
457
- $('#title_' + id).focus();
458
- });
459
-
460
- $(document).on('click', '.bookly-js-collapse .extra-attachment', function (e) {
461
- e.preventDefault();
462
- e.stopPropagation();
463
- var extra = $(this).parents('.extra');
464
- var frame = wp.media({
465
- library: {type: 'image'},
466
- multiple: false
467
- });
468
- frame.on('select', function () {
469
- var selection = frame.state().get('selection').toJSON(),
470
- img_src
471
- ;
472
- if (selection.length) {
473
- if (selection[0].sizes['thumbnail'] !== undefined) {
474
- img_src = selection[0].sizes['thumbnail'].url;
475
- } else {
476
- img_src = selection[0].url;
477
- }
478
- extra.find("[name='extras[" + extra.data('extra-id') + "][attachment_id]']").val(selection[0].id);
479
- extra.find('.extra-attachment-image').css({'background-image': 'url(' + img_src + ')', 'background-size': 'cover'});
480
- extra.find('.bookly-js-remove-attachment').show();
481
- $(this).hide();
482
- }
483
- });
484
-
485
- frame.open();
486
- });
487
-
488
- $(document).on('click', '.bookly-js-collapse .bookly-js-remove-attachment', function (e) {
489
- e.preventDefault();
490
- e.stopPropagation();
491
- $(this).hide();
492
- var extra = $(this).parents('.extra');
493
- extra.find("[name='extras[" + extra.data('extra-id') + "][attachment_id]']").attr('value', '');
494
- extra.find('.extra-attachment-image').attr('style', '');
495
- extra.find('.extra-attachment').show();
496
- });
497
-
498
- $(document).on('click', '.bookly-js-collapse .extra-delete', function (e) {
499
- e.preventDefault();
500
- e.stopPropagation();
501
- if (confirm(BooklyL10n.are_you_sure)) {
502
- var extra = $(this).parents('.extra');
503
- if (!extra.hasClass('new')) {
504
- $.post(ajaxurl, {action: 'bookly_service_extras_delete_service_extra', id: extra.data('extra-id')}, function () {
505
- });
506
- }
507
- extra.remove();
508
- }
509
- });
510
- /*</Extras>*/
511
- });
1
+ jQuery(function($) {
2
+ var $no_result = $('#bookly-services-wrapper .no-result');
3
+ // Remember user choice in the modal dialog.
4
+ var update_staff_choice = null,
5
+ $new_category_popover = $('#bookly-new-category'),
6
+ $new_category_form = $('#new-category-form'),
7
+ $new_category_name = $('#bookly-category-name');
8
+
9
+ $new_category_popover.popover({
10
+ html: true,
11
+ placement: 'bottom',
12
+ template: '<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>',
13
+ content: $new_category_form.show().detach(),
14
+ trigger: 'manual'
15
+ }).on('click', function () {
16
+ $(this).popover('toggle');
17
+ }).on('shown.bs.popover', function () {
18
+ // focus input
19
+ $new_category_name.focus();
20
+ }).on('hidden.bs.popover', function (e) {
21
+ //clear input
22
+ $new_category_name.val('');
23
+ });
24
+
25
+ // Save new category.
26
+ $new_category_form.on('submit', function() {
27
+ var data = $(this).serialize();
28
+
29
+ $.post(ajaxurl, data, function(response) {
30
+ $('#bookly-category-item-list').append(response);
31
+ var $new_category = $('.bookly-category-item:last');
32
+ // add created category to services
33
+ $('select[name="category_id"]').append('<option value="' + $new_category.data('category-id') + '">' + $new_category.find('input').val() + '</option>');
34
+ });
35
+ $new_category_popover.popover('hide');
36
+ return false;
37
+ });
38
+
39
+ // Cancel button.
40
+ $new_category_form.on('click', 'button[type="button"]', function (e) {
41
+ $new_category_popover.popover('hide');
42
+ });
43
+
44
+ // Categories list delegated events.
45
+ $('#bookly-categories-list')
46
+
47
+ // On category item click.
48
+ .on('click', '.bookly-category-item', function(e) {
49
+ if ($(e.target).is('.bookly-js-handle')) return;
50
+ $('#ab-services-list').html('<div class="bookly-loading"></div>');
51
+ var $clicked = $(this);
52
+
53
+ $.get(ajaxurl, {action:'bookly_get_category_services', category_id: $clicked.data('category-id')}, function(response) {
54
+ if ( response.success ) {
55
+ $('.bookly-category-item').not($clicked).removeClass('active');
56
+ $clicked.addClass('active');
57
+ $('.bookly-category-title').text($clicked.text());
58
+ refreshList(response.data, 0);
59
+ }
60
+ });
61
+ })
62
+
63
+ // On edit category click.
64
+ .on('click', '.bookly-js-edit', function(e) {
65
+ // Keep category item click from being executed.
66
+ e.stopPropagation();
67
+ // Prevent navigating to '#'.
68
+ e.preventDefault();
69
+ var $this = $(this).closest('.bookly-category-item');
70
+ $this.find('.displayed-value').hide();
71
+ $this.find('input').show().focus();
72
+ })
73
+
74
+ // On blur save changes.
75
+ .on('blur', 'input', function() {
76
+ var $this = $(this),
77
+ $item = $this.closest('.bookly-category-item'),
78
+ field = $this.attr('name'),
79
+ value = $this.val(),
80
+ id = $item.data('category-id'),
81
+ data = { action: 'bookly_update_category', id: id };
82
+ data[field] = value;
83
+ $.post(ajaxurl, data, function(response) {
84
+ // Hide input field.
85
+ $item.find('input').hide();
86
+ $item.find('.displayed-value').show();
87
+ // Show modified category name.
88
+ $item.find('.displayed-value').text(value);
89
+ // update edited category's name for services
90
+ $('select[name="category_id"] option[value="' + id + '"]').text(value);
91
+ });
92
+ })
93
+
94
+ // On press Enter save changes.
95
+ .on('keypress', 'input', function (e) {
96
+ var code = e.keyCode || e.which;
97
+ if (code == 13) {
98
+ $(this).blur();
99
+ }
100
+ })
101
+
102
+ // On delete category click.
103
+ .on('click', '.bookly-js-delete', function(e) {
104
+ // Keep category item click from being executed.
105
+ e.stopPropagation();
106
+ // Prevent navigating to '#'.
107
+ e.preventDefault();
108
+ // Ask user if he is sure.
109
+ if (confirm(BooklyL10n.are_you_sure)) {
110
+ var $item = $(this).closest('.bookly-category-item');
111
+ var data = { action: 'bookly_delete_category', id: $item.data('category-id') };
112
+ $.post(ajaxurl, data, function(response) {
113
+ // Remove category item from Services
114
+ $('select[name="category_id"] option[value="' + $item.data('category-id') + '"]').remove();
115
+ // Remove category item from DOM.
116
+ $item.remove();
117
+ if ($item.is('.active')) {
118
+ $('.bookly-js-all-services').click();
119
+ }
120
+ });
121
+ }
122
+ })
123
+
124
+ .on('click', 'input', function(e) {
125
+ e.stopPropagation();
126
+ });
127
+
128
+ // Services list delegated events.
129
+ $('#bookly-services-wrapper')
130
+ // On click on 'Add Service' button.
131
+ .on('click', '.add-service', function(e) {
132
+ e.preventDefault();
133
+ var ladda = rangeTools.ladda(this);
134
+ var selected_category_id = $('#bookly-categories-list .active').data('category-id'),
135
+ data = { action: 'bookly_add_service' };
136
+ if (selected_category_id) {
137
+ data['category_id'] = selected_category_id;
138
+ }
139
+ $.post(ajaxurl, data, function(response) {
140
+ refreshList(response.data.html, response.data.service_id);
141
+ ladda.stop();
142
+ });
143
+ })
144
+ // On click on 'Delete' button.
145
+ .on('click', '#bookly-delete', function(e) {
146
+ if (confirm(BooklyL10n.are_you_sure)) {
147
+ var ladda = rangeTools.ladda(this);
148
+
149
+ var $for_delete = $('.service-checker:checked'),
150
+ data = { action: 'bookly_remove_services' },
151
+ services = [],
152
+ $panels = [];
153
+
154
+ $for_delete.each(function(){
155
+ var panel = $(this).parents('.bookly-js-collapse');
156
+ $panels.push(panel);
157
+ services.push(this.value);
158
+ });
159
+ data['service_ids[]'] = services;
160
+ $.post(ajaxurl, data, function() {
161
+ ladda.stop();
162
+ $.each($panels.reverse(), function (index) {
163
+ $(this).delay(500 * index).fadeOut(200, function () {
164
+ $(this).remove();
165
+ });
166
+ });
167
+ });
168
+ }
169
+ })
170
+
171
+ .on('change', 'input.bookly-check-all-entities, input.bookly-js-check-entity', function () {
172
+ var $panel = $(this).parents('.bookly-js-collapse');
173
+ if ($(this).hasClass('bookly-check-all-entities')) {
174
+ $panel.find('.bookly-js-check-entity').prop('checked', $(this).prop('checked'));
175
+ } else {
176
+ $panel.find('.bookly-check-all-entities').prop('checked', $panel.find('.bookly-js-check-entity:not(:checked)').length == 0);
177
+ }
178
+ updateStaffButton($panel);
179
+ });
180
+
181
+ // Modal window events.
182
+ var $modal = $('#ab-staff-update');
183
+ $modal
184
+ .on('click', '.ab-yes', function() {
185
+ $modal.modal('hide');
186
+ if ( $('#ab-remember-my-choice').prop('checked') ) {
187
+ update_staff_choice = true;
188
+ }
189
+ submitServiceFrom($modal.data('input'),true);
190
+ })
191
+ .on('click', '.ab-no', function() {
192
+ if ( $('#ab-remember-my-choice').prop('checked') ) {
193
+ update_staff_choice = false;
194
+ }
195
+ submitServiceFrom($modal.data('input'),false);
196
+ });
197
+
198
+ function refreshList(response,service_id) {
199
+ var $list = $('#ab-services-list');
200
+ $list.html(response);
201
+ if (response.indexOf('panel') >= 0) {
202
+ $no_result.hide();
203
+ makeServicesSortable();
204
+ onCollapseInitChildren();
205
+ $list.booklyHelp();
206
+ } else {
207
+ $no_result.show();
208
+ }
209
+ $('#service_' + service_id).collapse('show');
210
+ $('#service_' + service_id).find('input[name=title]').focus();
211
+ }
212
+
213
+ function initColorPicker($jquery_collection) {
214
+ $jquery_collection.each(function(){
215
+ $(this).data('last-color', $(this).val());
216
+ });
217
+ $jquery_collection.wpColorPicker({
218
+ width: 200
219
+ });
220
+ }
221
+
222
+ $('#ab-services-list').on('change', '[name=capacity]', function(){
223
+ if ($(this).val() > 1) {
224
+ booklyAlert({error: [BooklyL10n.limitations]});
225
+ $(this).val('1').prop('readonly',true);
226
+ }
227
+ }).on('change', '[name=padding_left],[name=padding_right]', function(){
228
+ if ($(this).val() > 0) {
229
+ booklyAlert({error: [BooklyL10n.limitations]});
230
+ $(this).val('0').prop('readonly', true);
231
+ $(this).find('option:gt(0)').prop('disabled', true);
232
+ }
233
+ });
234
+
235
+ function submitServiceFrom($form, update_staff) {
236
+ $form.find('input[name=update_staff]').val(update_staff ? 1 : 0);
237
+ var ladda = rangeTools.ladda($form.find('button.ajax-service-send[type=submit]').get(0)),
238
+ data = $form.serializeArray();
239
+ if ($form.find('input[name=type]:checked').val() == 'compound') {
240
+ $form.find('li[data-sub-service-id]').each(function () {
241
+ data.push({name: 'sub_services[]', value: $(this).data('sub-service-id')});
242
+ });
243
+ } else {
244
+ data.push({name: 'type', value: 'simple'});
245
+ data.push({name: 'sub_services[]', value: false});
246
+ }
247
+ $.post(ajaxurl, data, function (response) {
248
+ if (response.success) {
249
+ var $panel = $form.parents('.bookly-js-collapse'),
250
+ $price = $form.find('[name=price]'),
251
+ $capacity = $form.find('[name=capacity]');
252
+ $panel.find('.bookly-js-service-color').css('background-color', response.data.color);
253
+ $panel.find('.bookly-js-service-title').html(response.data.title);
254
+ $panel.find('.bookly-js-service-duration').html(response.data.nice_duration);
255
+ $panel.find('.bookly-js-service-price').html(response.data.price);
256
+ $price.data('last_value', $price.val());
257
+ $capacity.data('last_value', $capacity.val());
258
+ booklyAlert({success : [BooklyL10n.saved]});
259
+ $.each(response.data.new_extras, function (front_id, real_id) {
260
+ var $li = $('li.extra.new[data-extra-id="' + front_id + '"]', $form);
261
+ $('[name^="extras"]', $li).each(function () {
262
+ var name = $(this).attr('name');
263
+ name = name.replace('[' + front_id + ']', '[' + real_id + ']');
264
+ $(this).attr('name', name);
265
+ });
266
+ $li.data('extra-id', real_id).removeClass('new');
267
+ $li.append('<input type="hidden" value="' + real_id + '" name="extras[' + real_id + '][id]">');
268
+ });
269
+ } else {
270
+ booklyAlert({error: [response.data.message]});
271
+ }
272
+ }, 'json').always(function() {
273
+ ladda.stop();
274
+ });
275
+ }
276
+
277
+ function updateStaffButton($panel) {
278
+ var staff_checked = $panel.find('.bookly-js-check-entity:checked').length;
279
+ if (staff_checked == 0) {
280
+ $panel.find('.bookly-entity-counter').text(BooklyL10n.selector.nothing_selected);
281
+ } else if (staff_checked == 1) {
282
+ $panel.find('.bookly-entity-counter').text($panel.find('.bookly-js-check-entity:checked').data('staff_name'));
283
+ } else {
284
+ $panel.find('.bookly-entity-counter').text(staff_checked + '/' + $panel.find('.bookly-js-check-entity').length);
285
+ }
286
+ }
287
+
288
+ var $category = $('#bookly-category-item-list');
289
+ $category.sortable({
290
+ axis : 'y',
291
+ handle : '.bookly-js-handle',
292
+ update : function( event, ui ) {
293
+ var data = [];
294
+ $category.children('li').each(function() {
295
+ var $this = $(this);
296
+ var position = $this.data('category-id');
297
+ data.push(position);
298
+ });
299
+ $.ajax({
300
+ type : 'POST',
301
+ url : ajaxurl,
302
+ data : { action: 'bookly_update_category_position', position: data }
303
+ });
304
+ }
305
+ });
306
+
307
+ function makeServicesSortable() {
308
+ if ($('.bookly-js-all-services').hasClass('active')) {
309
+ var $services = $('#services_list'),
310
+ fixHelper = function(e, ui) {
311
+ ui.children().each(function() {
312
+ $(this).width($(this).width());
313
+ });
314
+ return ui;
315
+ };
316
+ $services.sortable({
317
+ helper : fixHelper,
318
+ axis : 'y',
319
+ handle : '.bookly-js-handle',
320
+ update : function( event, ui ) {
321
+ var data = [];
322
+ $services.children('div').each(function() {
323
+ data.push($(this).data('service-id'));
324
+ });
325
+ $.ajax({
326
+ type : 'POST',
327
+ url : ajaxurl,
328
+ data : { action: 'bookly_update_services_position', position: data }
329
+ });
330
+ }
331
+ });
332
+ } else {
333
+ $('#services_list .bookly-js-handle').hide();
334
+ }
335
+ }
336
+
337
+ function onCollapseInitChildren() {
338
+ $('.panel-collapse').on('show.bs.collapse.bookly', function () {
339
+ var $panel = $(this);
340
+ var $sub_services = $('.ab--service-list', $panel);
341
+ initColorPicker($panel.find('.bookly-js-color-picker'));
342
+ $('input[name=type]', $panel).on( 'click', function(){
343
+ if ($(this).val() == 'simple') {
344
+ $('.ab--for-simple', $panel).show();
345
+ $('.ab--for-compound', $panel).hide();
346
+ } else {
347
+ $('.ab--for-simple', $panel).hide();
348
+ $('.ab--for-compound', $panel).show();
349
+ }
350
+ });
351
+ $('input[name=type]:checked', $panel).trigger('click');
352
+
353
+ $('[data-toggle="popover"]').popover({
354
+ html: true,
355
+ placement: 'top',
356
+ trigger: 'hover',
357
+ template: '<div class="popover bookly-font-xs" style="width: 220px" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
358
+ });
359
+
360
+ var initSubServicesLi = function ($li) {
361
+ $("option[value=" + $li.data('sub-service-id') + "]", $sub_services).prop('disabled', true);
362
+ $('.ab--sub-service-remove', $li).click(function () {
363
+ $('li.list-group-item[data-sub-service-id="' + $li.data('sub-service-id') + '"]', $panel).remove();
364
+ $("option[value=" + $li.data('sub-service-id') + "]", $sub_services).prop('disabled', false);
365
+ });
366
+ };
367
+
368
+ $('.ab--sub-services li.list-group-item[data-sub-service-id]', $panel).each(function () {
369
+ initSubServicesLi($(this));
370
+ });
371
+
372
+ $sub_services.on('change', function () {
373
+ if ($(this).val()) {
374
+ var $li = $('.ab--templates.services .template_' + $sub_services.val() + ' li').clone();
375
+ $li.insertBefore($(this).parents('li'));
376
+ initSubServicesLi($li);
377
+ $(this).val(0);
378
+ }
379
+ });
380
+
381
+ $('.ab--sub-services', $panel).sortable({axis: 'y', items: "[data-sub-service-id]"});
382
+
383
+ updateStaffButton($(this).parents('.bookly-js-collapse'));
384
+
385
+ $panel.find('.ajax-service-send').on('click', function (e) {
386
+ e.preventDefault();
387
+ var $form = $(this).parents('form'),
388
+ show_modal = false;
389
+ if(update_staff_choice === null) {
390
+ $('.ab-question', $form).each(function () {
391
+ if ($(this).data('last_value') != $(this).val()) {
392
+ show_modal = true;
393
+ }
394
+ });
395
+ }
396
+ if (show_modal) {
397
+ $modal.data('input', $form).modal('show');
398
+ } else {
399
+ submitServiceFrom($form, update_staff_choice);
400
+ }
401
+ });
402
+
403
+ $panel.find('.js-reset').on('click', function () {
404
+ $(this).parents('form').trigger('reset');
405
+ var $color = $(this).parents('form').find('.wp-color-picker'),
406
+ $panel = $(this).parents('.bookly-js-collapse');
407
+ $color.val($color.data('last-color')).trigger('change');
408
+ $panel.find('.parent-range-start').trigger('change');
409
+ updateStaffButton($panel);
410
+ });
411
+ $panel.find('.ab-question').each(function () {
412
+ $(this).data('last_value', $(this).val());
413
+ });
414
+ $panel.unbind('show.bs.collapse.bookly');
415
+ $(document.body).trigger( 'service_list.service_expand', [ $panel, $panel.closest('.panel').data('service-id') ] );
416
+ });
417
+ }
418
+ makeServicesSortable();
419
+ onCollapseInitChildren();
420
+
421
+ /*<Extras>*/
422
+ $('.extras-container').sortable({
423
+ axis : 'y',
424
+ handle : '.bookly-js-handle',
425
+ update : function( event, ui ) {
426
+ var data = [];
427
+ $(this).find('.extra').each(function() {
428
+ data.push($(this).data('extra-id'));
429
+ });
430
+ $.ajax({
431
+ type : 'POST',
432
+ url : ajaxurl,
433
+ data : { action: 'bookly_update_extra_position', position: data }
434
+ });
435
+ }
436
+ });
437
+
438
+ $(document).on('click', '.bookly-js-collapse .extra-new', function (e) {
439
+ e.preventDefault();
440
+ e.stopPropagation();
441
+ var children = $('.extras-container li');
442
+
443
+ var id = 1;
444
+ children.each(function (i, el) {
445
+ var elId = parseInt($(el).data('extra-id'));
446
+ id = (elId >= id) ? elId + 1 : id;
447
+ });
448
+ var template = $('.ab--templates.extras').html();
449
+ var $container = $(this).parents('.bookly-js-collapse').find('.extras-container');
450
+ id++;
451
+ $container.append(
452
+ template.replace(/%id%/g, id)
453
+ );
454
+ $('#title_' + id).focus();
455
+ });
456
+
457
+ $(document).on('click', '.bookly-js-collapse .extra-attachment', function (e) {
458
+ e.preventDefault();
459
+ e.stopPropagation();
460
+ var extra = $(this).parents('.extra');
461
+ var frame = wp.media({
462
+ library: {type: 'image'},
463
+ multiple: false
464
+ });
465
+ frame.on('select', function () {
466
+ var selection = frame.state().get('selection').toJSON(),
467
+ img_src
468
+ ;
469
+ if (selection.length) {
470
+ if (selection[0].sizes['thumbnail'] !== undefined) {
471
+ img_src = selection[0].sizes['thumbnail'].url;
472
+ } else {
473
+ img_src = selection[0].url;
474
+ }
475
+ extra.find("[name='extras[" + extra.data('extra-id') + "][attachment_id]']").val(selection[0].id);
476
+ extra.find('.extra-attachment-image').css({'background-image': 'url(' + img_src + ')', 'background-size': 'cover'});
477
+ extra.find('.bookly-js-remove-attachment').show();
478
+ $(this).hide();
479
+ }
480
+ });
481
+
482
+ frame.open();
483
+ });
484
+
485
+ $(document).on('click', '.bookly-js-collapse .bookly-js-remove-attachment', function (e) {
486
+ e.preventDefault();
487
+ e.stopPropagation();
488
+ $(this).hide();
489
+ var extra = $(this).parents('.extra');
490
+ extra.find("[name='extras[" + extra.data('extra-id') + "][attachment_id]']").attr('value', '');
491
+ extra.find('.extra-attachment-image').attr('style', '');
492
+ extra.find('.extra-attachment').show();
493
+ }).on('change', '.popover-range-start, .popover-range-end', function () {
494
+ var $popover_content = $(this).closest('.popover-content');
495
+ rangeTools.hideInaccessibleBreaks($popover_content.find('.popover-range-start'), $popover_content.find('.popover-range-end'));
496
+ });
497
+
498
+ $(document).on('click', '.bookly-js-collapse .extra-delete', function (e) {
499
+ e.preventDefault();
500
+ e.stopPropagation();
501
+ if (confirm(BooklyL10n.are_you_sure)) {
502
+ var extra = $(this).parents('.extra');
503
+ if (!extra.hasClass('new')) {
504
+ $.post(ajaxurl, {action: 'bookly_service_extras_delete_service_extra', id: extra.data('extra-id')}, function () {
505
+ });
506
+ }
507
+ extra.remove();
508
+ }
509
+ });
510
+ /*</Extras>*/
511
+ });
backend/modules/services/templates/_list.php CHANGED
@@ -1,311 +1,322 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- $time_interval = get_option( 'ab_settings_time_slot_length' );
3
- ?>
4
- <?php if ( ! empty( $service_collection ) ) : ?>
5
- <div class="panel-group" id="services_list" role="tablist" aria-multiselectable="true">
6
- <?php foreach ( $service_collection as $service ) : ?>
7
- <?php $service_id = $service['id'];
8
- $assigned_staff_ids = $service['staff_ids'] ? explode( ',', $service['staff_ids'] ) : array();
9
- $all_staff_selected = count( $assigned_staff_ids ) == count( $staff_collection );
10
- ?>
11
- <div class="panel panel-default bookly-js-collapse" data-service-id="<?php echo $service_id ?>">
12
- <div class="panel-heading" role="tab" id="s_<?php echo $service_id ?>">
13
- <div class="row">
14
- <div class="col-sm-8 col-xs-10">
15
- <div class="bookly-flexbox">
16
- <div class="bookly-flex-cell bookly-vertical-middle" style="width: 1%">
17
- <i class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move"
18
- title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
19
- </div>
20
- <div class="bookly-flex-cell bookly-vertical-middle" style="width: 1%">
21
- <span class="bookly-service-color bookly-margin-right-sm bookly-js-service-color"
22
- style="background-color: <?php echo esc_attr( $service['color'] ) ?>">&nbsp;</span>
23
- </div>
24
- <div class="bookly-flex-cell bookly-vertical-middle">
25
- <a role="button" class="panel-title collapsed bookly-js-service-title" data-toggle="collapse"
26
- data-parent="#services_list" href="#service_<?php echo $service_id ?>"
27
- aria-expanded="false" aria-controls="service_<?php echo $service_id ?>">
28
- <?php echo esc_html( $service['title'] ) ?>
29
- </a>
30
- </div>
31
- </div>
32
- </div>
33
- <div class="col-sm-4 col-xs-2">
34
- <div class="bookly-flexbox">
35
- <div class="bookly-flex-cell bookly-vertical-middle hidden-xs" style="width: 60%">
36
- <span class="bookly-js-service-duration">
37
- <?php echo( $service['type'] == \BooklyLite\Lib\Entities\Service::TYPE_SIMPLE ? \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $service['duration'] ) : sprintf( _n( '%d service', '%d services', count( json_decode( $service['sub_services'], true ) ), 'bookly' ), count( json_decode( $service['sub_services'], true ) ) ) ) ?>
38
- </span>
39
- </div>
40
- <div class="bookly-flex-cell bookly-vertical-middle hidden-xs" style="width: 30%">
41
- <span class="bookly-js-service-price">
42
- <?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $service['price'] ) ?>
43
- </span>
44
- </div>
45
- <div class="bookly-flex-cell bookly-vertical-middle text-right" style="width: 10%">
46
- <div class="checkbox bookly-margin-remove">
47
- <label><input type="checkbox" class="service-checker" value="<?php echo $service_id ?>"/></label>
48
- </div>
49
- </div>
50
- </div>
51
- </div>
52
- </div>
53
- </div>
54
-
55
- <div id="service_<?php echo $service_id ?>" class="panel-collapse collapse" role="tabpanel" style="height: 0">
56
- <div class="panel-body">
57
- <form method="post">
58
- <?php do_action( 'bookly_render_service_form_contents', $service ) ?>
59
- <div class="row">
60
- <div class="col-md-9 col-sm-6">
61
- <div class="form-group">
62
- <label for="title_<?php echo $service_id ?>"><?php _e( 'Title', 'bookly' ) ?></label>
63
- <input name="title" value="<?php echo esc_attr( $service['title'] ) ?>" id="title_<?php echo $service_id ?>" class="form-control" type="text">
64
- </div>
65
- </div>
66
- <div class="col-md-3 col-sm-6">
67
- <div class="form-group">
68
- <label><?php _e( 'Color', 'bookly' ) ?></label>
69
- <div class="bookly-color-picker-wrapper">
70
- <input name="color" value="<?php echo esc_attr( $service['color'] ) ?>" class="bookly-js-color-picker" data-last-color="<?php echo esc_attr( $service['color'] ) ?>" type="hidden">
71
- </div>
72
- </div>
73
- </div>
74
- </div>
75
- <div class="row">
76
- <div class="col-sm-4">
77
- <div class="form-group">
78
- <label for="visibility_<?php echo $service_id ?>"><?php _e( 'Visibility', 'bookly' ) ?></label>
79
- <p class="help-block"><?php _e( 'To make service invisible to your customers set the visibility to "Private".', 'bookly' ) ?></p>
80
- <select name="visibility" class="form-control" id="visibility_<?php echo $service_id ?>">
81
- <option value="public" <?php selected( $service['visibility'], 'public' ) ?>><?php _e( 'Public', 'bookly' ) ?></option>
82
- <option value="private" <?php selected( $service['visibility'], 'private' ) ?>><?php _e( 'Private', 'bookly' ) ?></option>
83
- </select>
84
- </div>
85
- </div>
86
- <div class="col-sm-4">
87
- <div class="form-group">
88
- <label for="price_<?php echo $service_id ?>"><?php _e( 'Price', 'bookly' ) ?></label>
89
- <input id="price_<?php echo $service_id ?>" class="form-control ab-question" type="number" min="0.00" step="any" name="price" value="<?php echo esc_attr( $service['price'] ) ?>">
90
- </div>
91
- </div>
92
- <div class="col-sm-4 ab--for-simple">
93
- <div class="form-group">
94
- <label for="capacity_<?php echo $service_id ?>"><?php _e( 'Capacity', 'bookly' ) ?></label>
95
- <p class="help-block"><?php _e( 'The maximum number of customers allowed to book the service for the certain time period.', 'bookly' ) ?></p>
96
- <input id="capacity_<?php echo $service_id ?>" class="form-control ab-question" type="number" min="1" step="1" name="capacity" value="<?php echo esc_attr( $service['capacity'] ) ?>">
97
- </div>
98
- </div>
99
- </div>
100
-
101
- <div class="ab--for-simple">
102
- <div class="row">
103
- <div class="col-sm-4">
104
- <div class="form-group">
105
- <label for="duration_<?php echo $service_id ?>">
106
- <?php _e( 'Duration', 'bookly' ) ?>
107
- </label>
108
- <select id="duration_<?php echo $service_id ?>" class="form-control" name="duration">
109
- <?php for ( $j = $time_interval; $j <= 720; $j += $time_interval ) : ?><?php if ( $service['duration'] / 60 > $j - $time_interval && $service['duration'] / 60 < $j ) : ?><option value="<?php echo esc_attr( $service['duration'] ) ?>" selected><?php echo \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $service['duration'] ) ?></option><?php endif ?><option value="<?php echo $j * 60 ?>" <?php selected( $service['duration'], $j * 60 ) ?>><?php echo \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $j * 60 ) ?></option><?php endfor ?>
110
- <option value="86400" <?php selected( $service['duration'], DAY_IN_SECONDS ) ?>><?php _e( 'All day', 'bookly' ) ?></option>
111
- </select>
112
- </div>
113
- </div>
114
- <div class="col-sm-8">
115
- <div class="form-group">
116
- <label for="padding_left_<?php echo $service_id ?>">
117
- <?php _e( 'Padding time (before and after)', 'bookly' ) ?>
118
- </label>
119
- <p class="help-block"><?php _e( 'Set padding time before and/or after an appointment. For example, if you require 15 minutes to prepare for the next appointment then you should set "padding before" to 15 min. If there is an appointment from 8:00 to 9:00 then the next available time slot will be 9:15 rather than 9:00.', 'bookly' ) ?></p>
120
- <div class="row">
121
- <div class="col-xs-6">
122
- <select id="padding_left_<?php echo $service_id ?>" class="form-control" name="padding_left">
123
- <option value="0"><?php _e( 'OFF', 'bookly' ) ?></option>
124
- <?php for ( $j = $time_interval; $j <= 720; $j += $time_interval ) : ?><?php if ( $service['padding_left'] > 0 && $service['padding_left'] / 60 > $j - $time_interval && $service['padding_left'] / 60 < $j ) : ?><option value="<?php echo esc_attr( $service['padding_left'] ) ?>" selected><?php echo \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $service['padding_left'] ) ?></option><?php endif ?><option value="<?php echo $j * 60 ?>" <?php selected( $service['padding_left'], $j * 60 ) ?>><?php echo \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $j * 60 ) ?></option><?php endfor ?>
125
- </select>
126
- </div>
127
- <div class="col-xs-6">
128
- <select id="padding_right_<?php echo $service_id ?>" class="form-control" name="padding_right">
129
- <option value="0"><?php _e( 'OFF', 'bookly' ) ?></option>
130
- <?php for ( $j = $time_interval; $j <= 720; $j += $time_interval ) : ?><?php if ( $service['padding_right'] > 0 && $service['padding_right'] / 60 > $j - $time_interval && $service['padding_right'] / 60 < $j ) : ?><option value="<?php echo esc_attr( $service['padding_right'] ) ?>" selected><?php echo \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $service['padding_right'] ) ?></option><?php endif ?><option value="<?php echo $j * 60 ?>" <?php selected( $service['padding_right'], $j * 60 ) ?>><?php echo \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $j * 60 ) ?></option><?php endfor ?>
131
- </select>
132
- </div>
133
- </div>
134
- </div>
135
- </div>
136
- </div>
137
- </div>
138
-
139
- <div class="row">
140
- <div class="col-sm-6">
141
- <div class="form-group">
142
- <label for="category_<?php echo $service_id ?>"><?php _e( 'Category', 'bookly' ) ?></label>
143
- <select id="category_<?php echo $service_id ?>" class="form-control" name="category_id"><option value="0"></option>
144
- <?php foreach ( $category_collection as $category ) : ?>
145
- <option value="<?php echo $category['id'] ?>" <?php selected( $category['id'], $service['category_id'] ) ?>><?php echo esc_html( $category['name'] ) ?></option>
146
- <?php endforeach ?>
147
- </select>
148
- </div>
149
- </div>
150
- <div class="col-sm-6 ab--for-simple">
151
- <div class="form-group">
152
- <label><?php _e( 'Providers', 'bookly' ) ?></label><br>
153
- <div class="btn-group">
154
- <button class="btn btn-default btn-block dropdown-toggle bookly-flexbox" data-toggle="dropdown">
155
- <div class="bookly-flex-cell">
156
- <i class="dashicons dashicons-admin-users bookly-margin-right-md"></i>
157
- </div>
158
- <div class="bookly-flex-cell text-left" style="width: 100%">
159
- <span class=bookly-entity-counter><?php echo $service['total_staff'] ?></span>
160
- </div>
161
- <div class="bookly-flex-cell"><div class="bookly-margin-left-md"><span class="caret"></span></div></div>
162
- </button>
163
- <ul class="dropdown-menu bookly-entity-selector">
164
- <li>
165
- <a class="checkbox" href="javascript:void(0)">
166
- <label>
167
- <input type="checkbox" id="service_<?php echo $service_id ?>_all_bookly-js-check-entity" class="bookly-check-all-entities" <?php checked( $all_staff_selected ) ?>">
168
- <?php _e( 'All staff', 'bookly' ) ?>
169
- </label>
170
- </a>
171
- </li>
172
- <?php foreach ( $staff_collection as $i => $staff ) : ?>
173
- <li>
174
- <a class="checkbox" href="javascript:void(0)">
175
- <label>
176
- <input type="checkbox" name="staff_ids[]" class="bookly-js-check-entity" value="<?php echo $staff['id'] ?>" <?php checked( in_array( $staff['id'], $assigned_staff_ids ) ) ?> data-staff_name="<?php echo esc_attr( $staff['full_name'] ) ?>">
177
- <?php echo esc_html( $staff['full_name'] ) ?>
178
- </label>
179
- </a>
180
- </li>
181
- <?php endforeach ?>
182
- </ul>
183
- </div>
184
- </div>
185
- </div>
186
- </div>
187
-
188
- <div class="form-group">
189
- <label for="info_<?php echo $service_id ?>">
190
- <?php _e( 'Info', 'bookly' ) ?>
191
- </label>
192
- <p class="help-block">
193
- <?php printf( __( 'This text can be inserted into notifications with %s code.', 'bookly' ), '{service_info}' ) ?>
194
- </p>
195
- <textarea class="form-control" id="info_<?php echo $service_id ?>" name="info" rows="3" type="text"><?php echo esc_textarea( $service['info'] ) ?></textarea>
196
- </div>
197
-
198
- <?php do_action( 'bookly_compound_render_sub_services', $service, $service_collection, $service['sub_services'] ) ?>
199
- <?php if ( has_filter( 'bookly_extras_find_by_service_id' ) ) : ?>
200
- <div class="ab--for-simple">
201
- <a class="h3" href="#ab--container_<?php echo $service_id ?>" data-toggle="collapse" role="button">
202
- <?php echo get_option( 'ab_appearance_text_step_extras' ) ?>
203
- </a>
204
- <div id="ab--container_<?php echo $service_id ?>" class="bookly-margin-top-lg collapse in">
205
- <ul class="list-group extras-container" data-service="<?php echo $service_id ?>">
206
- <div class="form-group text-right">
207
- <button class="btn btn-lg btn-success-outline extra-new">
208
- <i class="dashicons dashicons-plus-alt"></i>
209
- <?php _e( 'New Item', 'bookly' ) ?>
210
- </button>
211
- </div>
212
- <?php foreach ( apply_filters( 'bookly_extras_find_by_service_id', array(), $service_id ) as $extra ) : ?>
213
- <li class="list-group-item extra" data-extra-id="<?php echo $extra->get( 'id' ) ?>">
214
- <div class="row">
215
- <div class="col-lg-3">
216
- <div class="bookly-flexbox">
217
- <div class="bookly-flex-cell bookly-vertical-top">
218
- <i class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
219
- </div>
220
- <div class="bookly-flex-cell" style="width: 100%">
221
- <div class="form-group">
222
- <input name="extras[<?php echo $extra->get( 'id' ) ?>][id]"
223
- value="<?php echo $extra->get( 'id' ) ?>" type="hidden">
224
- <input name="extras[<?php echo $extra->get( 'id' ) ?>][attachment_id]"
225
- value="<?php echo $extra->get( 'attachment_id' ) ?>" type="hidden">
226
-
227
- <?php $img = wp_get_attachment_image_src( $extra->get( 'attachment_id' ), 'thumbnail' ) ?>
228
-
229
- <div class="extra-attachment-image bookly-thumb bookly-thumb-lg bookly-margin-right-lg"
230
- <?php echo $img ? 'style="background-image: url(' . $img[0] . '); background-size: cover;"' : '' ?>
231
- >
232
- <a class="bookly-js-remove-attachment dashicons dashicons-trash text-danger bookly-thumb-delete" href="javascript:void(0)" title="<?php _e( 'Delete', 'bookly' ) ?>"
233
- <?php if ( !$img ) : ?>style="display: none;"<?php endif ?>>
234
- </a>
235
- <div class="bookly-thumb-edit extra-attachment" <?php if ( $img ) : ?>style="display: none;"<?php endif ?> >
236
- <div class="bookly-pretty">
237
- <label class="bookly-pretty-indicator bookly-thumb-edit-btn"><?php _e( 'Image', 'bookly' ) ?></label>
238
- </div>
239
- </div>
240
- </div>
241
- </div>
242
- </div>
243
- </div>
244
- </div>
245
-
246
- <div class="col-lg-9">
247
- <div class="form-group">
248
- <label for="title_<?php echo $extra->get( 'id' ) ?>">
249
- <?php _e( 'Title', 'bookly' ) ?>
250
- </label>
251
- <input name="extras[<?php echo $extra->get( 'id' ) ?>][title]" class="form-control" type="text" id="title_<?php echo $extra->get( 'id' ) ?>" value="<?php echo $extra->get( 'title' ) ?>">
252
- </div>
253
-
254
- <div class="row">
255
- <div class="col-sm-4">
256
- <div class="form-group">
257
- <label for="price_<?php echo $extra->get( 'id' ) ?>">
258
- <?php _e( 'Price', 'bookly' ) ?>
259
- </label>
260
- <input name="extras[<?php echo $extra->get( 'id' ) ?>][price]" class="form-control" type="number" step="0.01" id="price_<?php echo $extra->get( 'id' ) ?>" min="0.00" value="<?php echo $extra->get( 'price' ) ?>">
261
- </div>
262
- </div>
263
-
264
- <div class="col-sm-4">
265
- <div class="form-group">
266
- <label for="duration_<?php echo $extra->get( 'id' ) ?>">
267
- <?php _e( 'Duration', 'bookly' ) ?>
268
- </label>
269
- <select name="extras[<?php echo $extra->get( 'id' ) ?>][duration]" id="duration_<?php echo $extra->get( 'id' ) ?>" class="form-control">
270
- <option value="0"><?php _e( 'OFF', 'bookly' ) ?></option>
271
- <?php for ( $j = $time_interval; $j <= 720; $j += $time_interval ) : ?><?php if ( $extra->get( 'duration' ) > 0 && $extra->get( 'duration' ) / 60 > $j - $time_interval && $extra->get( 'duration' ) / 60 < $j ) : ?><option value="<?php echo esc_attr( $extra->get( 'duration' ) ) ?>" selected><?php echo BooklyLite\Lib\Utils\DateTime::secondsToInterval( $extra->get( 'duration' ) ) ?></option><?php endif ?><option value="<?php echo $j * 60 ?>" <?php selected( $extra->get( 'duration' ), $j * 60 ) ?>><?php echo BooklyLite\Lib\Utils\DateTime::secondsToInterval( $j * 60 ) ?></option><?php endfor ?>
272
- </select>
273
- </div>
274
- </div>
275
-
276
- <div class="col-sm-4">
277
- <div class="form-group">
278
- <label for="max_quantity_<?php echo $extra->get( 'id' ) ?>">
279
- <?php _e( 'Max quantity', 'bookly' ) ?>
280
- </label>
281
- <input name="extras[<?php echo $extra->get( 'id' ) ?>][max_quantity]" class="form-control" type="number" step="1" id="max_quantity_<?php echo $extra->get( 'id' ) ?>" min="1" value="<?php echo $extra->get( 'max_quantity' ) ?>">
282
- </div>
283
- </div>
284
- </div>
285
-
286
- <div class="form-group text-right">
287
- <?php \BooklyLite\Lib\Utils\Common::deleteButton( null, 'extra-delete' ) ?>
288
- </div>
289
- </div>
290
- </div>
291
- </li>
292
- <?php endforeach ?>
293
- </ul>
294
- </div>
295
- </div>
296
- <?php endif ?>
297
-
298
- <div class="panel-footer">
299
- <input type="hidden" name="action" value="ab_update_service">
300
- <input type="hidden" name="id" value="<?php echo esc_html( $service_id ) ?>">
301
- <input type="hidden" name="update_staff" value="0">
302
- <?php \BooklyLite\Lib\Utils\Common::submitButton( null, 'ajax-service-send' ) ?>
303
- <?php \BooklyLite\Lib\Utils\Common::resetButton( null, 'js-reset' ) ?>
304
- </div>
305
- </form>
306
- </div>
307
- </div>
308
- </div>
309
- <?php endforeach ?>
310
- </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ $time_interval = get_option( 'bookly_gen_time_slot_length' );
3
+ ?>
4
+ <?php if ( ! empty( $service_collection ) ) : ?>
5
+ <div class="panel-group" id="services_list" role="tablist" aria-multiselectable="true">
6
+ <?php foreach ( $service_collection as $service ) : ?>
7
+ <?php $service_id = $service['id'];
8
+ $assigned_staff_ids = $service['staff_ids'] ? explode( ',', $service['staff_ids'] ) : array();
9
+ $all_staff_selected = count( $assigned_staff_ids ) == count( $staff_collection );
10
+ ?>
11
+ <div class="panel panel-default bookly-js-collapse" data-service-id="<?php echo $service_id ?>">
12
+ <div class="panel-heading" role="tab" id="s_<?php echo $service_id ?>">
13
+ <div class="row">
14
+ <div class="col-sm-8 col-xs-10">
15
+ <div class="bookly-flexbox">
16
+ <div class="bookly-flex-cell bookly-vertical-middle" style="width: 1%">
17
+ <i class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move"
18
+ title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
19
+ </div>
20
+ <div class="bookly-flex-cell bookly-vertical-middle" style="width: 1%">
21
+ <span class="bookly-service-color bookly-margin-right-sm bookly-js-service-color"
22
+ style="background-color: <?php echo esc_attr( $service['color'] ) ?>">&nbsp;</span>
23
+ </div>
24
+ <div class="bookly-flex-cell bookly-vertical-middle">
25
+ <a role="button" class="panel-title collapsed bookly-js-service-title" data-toggle="collapse"
26
+ data-parent="#services_list" href="#service_<?php echo $service_id ?>"
27
+ aria-expanded="false" aria-controls="service_<?php echo $service_id ?>">
28
+ <?php echo esc_html( $service['title'] ) ?>
29
+ </a>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ <div class="col-sm-4 col-xs-2">
34
+ <div class="bookly-flexbox">
35
+ <div class="bookly-flex-cell bookly-vertical-middle hidden-xs" style="width: 60%">
36
+ <span class="bookly-js-service-duration">
37
+ <?php echo( $service['type'] == \BooklyLite\Lib\Entities\Service::TYPE_SIMPLE ? \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $service['duration'] ) : sprintf( _n( '%d service', '%d services', count( json_decode( $service['sub_services'], true ) ), 'bookly' ), count( json_decode( $service['sub_services'], true ) ) ) ) ?>
38
+ </span>
39
+ </div>
40
+ <div class="bookly-flex-cell bookly-vertical-middle hidden-xs" style="width: 30%">
41
+ <span class="bookly-js-service-price">
42
+ <?php echo \BooklyLite\Lib\Utils\Common::formatPrice( $service['price'] ) ?>
43
+ </span>
44
+ </div>
45
+ <div class="bookly-flex-cell bookly-vertical-middle text-right" style="width: 10%">
46
+ <div class="checkbox bookly-margin-remove">
47
+ <label><input type="checkbox" class="service-checker" value="<?php echo $service_id ?>"/></label>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+
55
+ <div id="service_<?php echo $service_id ?>" class="panel-collapse collapse" role="tabpanel" style="height: 0">
56
+ <div class="panel-body">
57
+ <form method="post">
58
+ <?php do_action( 'bookly_render_service_form_contents', $service ) ?>
59
+ <div class="row">
60
+ <div class="col-md-9 col-sm-6">
61
+ <div class="form-group">
62
+ <label for="title_<?php echo $service_id ?>"><?php _e( 'Title', 'bookly' ) ?></label>
63
+ <input name="title" value="<?php echo esc_attr( $service['title'] ) ?>" id="title_<?php echo $service_id ?>" class="form-control" type="text">
64
+ </div>
65
+ </div>
66
+ <div class="col-md-3 col-sm-6">
67
+ <div class="form-group">
68
+ <label><?php _e( 'Color', 'bookly' ) ?></label>
69
+ <div class="bookly-color-picker-wrapper">
70
+ <input name="color" value="<?php echo esc_attr( $service['color'] ) ?>" class="bookly-js-color-picker" data-last-color="<?php echo esc_attr( $service['color'] ) ?>" type="hidden">
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ <div class="row">
76
+ <div class="col-sm-4">
77
+ <div class="form-group">
78
+ <label for="visibility_<?php echo $service_id ?>"><?php _e( 'Visibility', 'bookly' ) ?></label>
79
+ <p class="help-block"><?php _e( 'To make service invisible to your customers set the visibility to "Private".', 'bookly' ) ?></p>
80
+ <select name="visibility" class="form-control" id="visibility_<?php echo $service_id ?>">
81
+ <option value="public" <?php selected( $service['visibility'], 'public' ) ?>><?php _e( 'Public', 'bookly' ) ?></option>
82
+ <option value="private" <?php selected( $service['visibility'], 'private' ) ?>><?php _e( 'Private', 'bookly' ) ?></option>
83
+ </select>
84
+ </div>
85
+ </div>
86
+ <div class="col-sm-4">
87
+ <div class="form-group">
88
+ <label for="price_<?php echo $service_id ?>"><?php _e( 'Price', 'bookly' ) ?></label>
89
+ <input id="price_<?php echo $service_id ?>" class="form-control ab-question" type="number" min="0" step="1" name="price" value="<?php echo esc_attr( $service['price'] ) ?>">
90
+ </div>
91
+ </div>
92
+ <div class="col-sm-4 ab--for-simple">
93
+ <div class="form-group">
94
+ <label for="capacity_<?php echo $service_id ?>"><?php _e( 'Capacity', 'bookly' ) ?></label>
95
+ <p class="help-block"><?php _e( 'The maximum number of customers allowed to book the service for the certain time period.', 'bookly' ) ?></p>
96
+ <input id="capacity_<?php echo $service_id ?>" class="form-control ab-question" type="number" min="1" step="1" name="capacity" value="<?php echo esc_attr( $service['capacity'] ) ?>">
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="ab--for-simple">
102
+ <div class="row">
103
+ <div class="col-sm-4">
104
+ <div class="form-group">
105
+ <label for="duration_<?php echo $service_id ?>">
106
+ <?php _e( 'Duration', 'bookly' ) ?>
107
+ </label>
108
+ <select id="duration_<?php echo $service_id ?>" class="form-control" name="duration">
109
+ <?php for ( $j = $time_interval; $j <= 720; $j += $time_interval ) : ?><?php if ( $service['duration'] / 60 > $j - $time_interval && $service['duration'] / 60 < $j ) : ?><option value="<?php echo esc_attr( $service['duration'] ) ?>" selected><?php echo \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $service['duration'] ) ?></option><?php endif ?><option value="<?php echo $j * 60 ?>" <?php selected( $service['duration'], $j * 60 ) ?>><?php echo \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $j * 60 ) ?></option><?php endfor ?>
110
+ <option value="86400" <?php selected( $service['duration'], DAY_IN_SECONDS ) ?>><?php _e( 'All day', 'bookly' ) ?></option>
111
+ </select>
112
+ </div>
113
+ </div>
114
+ <div class="col-sm-8">
115
+ <div class="form-group">
116
+ <label for="padding_left_<?php echo $service_id ?>">
117
+ <?php _e( 'Padding time (before and after)', 'bookly' ) ?>
118
+ </label>
119
+ <p class="help-block"><?php _e( 'Set padding time before and/or after an appointment. For example, if you require 15 minutes to prepare for the next appointment then you should set "padding before" to 15 min. If there is an appointment from 8:00 to 9:00 then the next available time slot will be 9:15 rather than 9:00.', 'bookly' ) ?></p>
120
+ <div class="row">
121
+ <div class="col-xs-6">
122
+ <select id="padding_left_<?php echo $service_id ?>" class="form-control" name="padding_left">
123
+ <option value="0"><?php _e( 'OFF', 'bookly' ) ?></option>
124
+ <?php for ( $j = $time_interval; $j <= 720; $j += $time_interval ) : ?><?php if ( $service['padding_left'] > 0 && $service['padding_left'] / 60 > $j - $time_interval && $service['padding_left'] / 60 < $j ) : ?><option value="<?php echo esc_attr( $service['padding_left'] ) ?>" selected><?php echo \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $service['padding_left'] ) ?></option><?php endif ?><option value="<?php echo $j * 60 ?>" <?php selected( $service['padding_left'], $j * 60 ) ?>><?php echo \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $j * 60 ) ?></option><?php endfor ?>
125
+ </select>
126
+ </div>
127
+ <div class="col-xs-6">
128
+ <select id="padding_right_<?php echo $service_id ?>" class="form-control" name="padding_right">
129
+ <option value="0"><?php _e( 'OFF', 'bookly' ) ?></option>
130
+ <?php for ( $j = $time_interval; $j <= 720; $j += $time_interval ) : ?><?php if ( $service['padding_right'] > 0 && $service['padding_right'] / 60 > $j - $time_interval && $service['padding_right'] / 60 < $j ) : ?><option value="<?php echo esc_attr( $service['padding_right'] ) ?>" selected><?php echo \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $service['padding_right'] ) ?></option><?php endif ?><option value="<?php echo $j * 60 ?>" <?php selected( $service['padding_right'], $j * 60 ) ?>><?php echo \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $j * 60 ) ?></option><?php endfor ?>
131
+ </select>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+ <div class="row">
140
+ <div class="col-sm-6">
141
+ <div class="form-group">
142
+ <label for="category_<?php echo $service_id ?>"><?php _e( 'Category', 'bookly' ) ?></label>
143
+ <select id="category_<?php echo $service_id ?>" class="form-control" name="category_id"><option value="0"><?php _e( 'Uncategorized', 'bookly' ) ?></option>
144
+ <?php foreach ( $category_collection as $category ) : ?>
145
+ <option value="<?php echo $category['id'] ?>" <?php selected( $category['id'], $service['category_id'] ) ?>><?php echo esc_html( $category['name'] ) ?></option>
146
+ <?php endforeach ?>
147
+ </select>
148
+ </div>
149
+ </div>
150
+ <div class="col-sm-6 ab--for-simple">
151
+ <div class="form-group">
152
+ <label><?php _e( 'Providers', 'bookly' ) ?></label><br>
153
+ <div class="btn-group">
154
+ <button class="btn btn-default btn-block dropdown-toggle bookly-flexbox" data-toggle="dropdown">
155
+ <div class="bookly-flex-cell">
156
+ <i class="dashicons dashicons-admin-users bookly-margin-right-md"></i>
157
+ </div>
158
+ <div class="bookly-flex-cell text-left" style="width: 100%">
159
+ <span class=bookly-entity-counter><?php echo $service['total_staff'] ?></span>
160
+ </div>
161
+ <div class="bookly-flex-cell"><div class="bookly-margin-left-md"><span class="caret"></span></div></div>
162
+ </button>
163
+ <ul class="dropdown-menu bookly-entity-selector">
164
+ <li>
165
+ <a class="checkbox" href="javascript:void(0)">
166
+ <label>
167
+ <input type="checkbox" id="service_<?php echo $service_id ?>_all_bookly-js-check-entity" class="bookly-check-all-entities" <?php checked( $all_staff_selected ) ?>">
168
+ <?php _e( 'All staff', 'bookly' ) ?>
169
+ </label>
170
+ </a>
171
+ </li>
172
+ <?php foreach ( $staff_collection as $i => $staff ) : ?>
173
+ <li>
174
+ <a class="checkbox" href="javascript:void(0)">
175
+ <label>
176
+ <input type="checkbox" name="staff_ids[]" class="bookly-js-check-entity" value="<?php echo $staff['id'] ?>" <?php checked( in_array( $staff['id'], $assigned_staff_ids ) ) ?> data-staff_name="<?php echo esc_attr( $staff['full_name'] ) ?>">
177
+ <?php echo esc_html( $staff['full_name'] ) ?>
178
+ </label>
179
+ </a>
180
+ </li>
181
+ <?php endforeach ?>
182
+ </ul>
183
+ </div>
184
+ </div>
185
+ </div>
186
+ </div>
187
+
188
+ <div class="form-group">
189
+ <label for="info_<?php echo $service_id ?>">
190
+ <?php _e( 'Info', 'bookly' ) ?>
191
+ </label>
192
+ <p class="help-block">
193
+ <?php printf( __( 'This text can be inserted into notifications with %s code.', 'bookly' ), '{service_info}' ) ?>
194
+ </p>
195
+ <textarea class="form-control" id="info_<?php echo $service_id ?>" name="info" rows="3" type="text"><?php echo esc_textarea( $service['info'] ) ?></textarea>
196
+ </div>
197
+
198
+ <?php do_action( 'bookly_compound_render_sub_services', $service, $service_collection, $service['sub_services'] ) ?>
199
+
200
+ <?php if ( has_action( 'bookly_service_schedule_render_service_settings' ) ) : ?>
201
+ <div class="ab--for-simple bookly-margin-bottom-xs">
202
+ <a class="h4" href="#bookly_service_schedule_container_<?php echo $service_id ?>" data-toggle="collapse" role="button">
203
+ <?php _e( 'Schedule', 'bookly' ) ?>
204
+ </a>
205
+ <div id="bookly_service_schedule_container_<?php echo $service_id ?>" class="bookly-margin-top-lg collapse in">
206
+ <?php do_action( 'bookly_service_schedule_render_service_settings', $service ) ?>
207
+ </div>
208
+ </div>
209
+ <?php endif ?>
210
+
211
+ <?php if ( has_filter( 'bookly_service_extras_find_by_service_id' ) ) : ?>
212
+ <div class="ab--for-simple bookly-margin-bottom-xs">
213
+ <a class="h4" href="#bookly_service_extras_container_<?php echo $service_id ?>" data-toggle="collapse" role="button">
214
+ <?php echo get_option( 'bookly_l10n_step_extras' ) ?>
215
+ </a>
216
+ <div id="bookly_service_extras_container_<?php echo $service_id ?>" class="bookly-margin-top-lg collapse in">
217
+ <ul class="list-group extras-container" data-service="<?php echo $service_id ?>">
218
+ <div class="form-group text-right">
219
+ <button type="button" class="btn btn-success extra-new" data-spinner-size="40" data-style="zoom-in">
220
+ <span class="ladda-label"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'New Item', 'bookly' ) ?></span>
221
+ </button>
222
+ </div>
223
+ <?php foreach ( apply_filters( 'bookly_service_extras_find_by_service_id', array(), $service_id ) as $extra ) : ?>
224
+ <li class="list-group-item extra" data-extra-id="<?php echo $extra->get( 'id' ) ?>">
225
+ <div class="row">
226
+ <div class="col-lg-3">
227
+ <div class="bookly-flexbox">
228
+ <div class="bookly-flex-cell bookly-vertical-top">
229
+ <i class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
230
+ </div>
231
+ <div class="bookly-flex-cell" style="width: 100%">
232
+ <div class="form-group">
233
+ <input name="extras[<?php echo $extra->get( 'id' ) ?>][id]"
234
+ value="<?php echo $extra->get( 'id' ) ?>" type="hidden">
235
+ <input name="extras[<?php echo $extra->get( 'id' ) ?>][attachment_id]"
236
+ value="<?php echo $extra->get( 'attachment_id' ) ?>" type="hidden">
237
+
238
+ <?php $img = wp_get_attachment_image_src( $extra->get( 'attachment_id' ), 'thumbnail' ) ?>
239
+
240
+ <div class="extra-attachment-image bookly-thumb bookly-thumb-lg bookly-margin-right-lg"
241
+ <?php echo $img ? 'style="background-image: url(' . $img[0] . '); background-size: cover;"' : '' ?>
242
+ >
243
+ <a class="bookly-js-remove-attachment dashicons dashicons-trash text-danger bookly-thumb-delete" href="javascript:void(0)" title="<?php _e( 'Delete', 'bookly' ) ?>"
244
+ <?php if ( !$img ) : ?>style="display: none;"<?php endif ?>>
245
+ </a>
246
+ <div class="bookly-thumb-edit extra-attachment" <?php if ( $img ) : ?>style="display: none;"<?php endif ?> >
247
+ <div class="bookly-pretty">
248
+ <label class="bookly-pretty-indicator bookly-thumb-edit-btn"><?php _e( 'Image', 'bookly' ) ?></label>
249
+ </div>
250
+ </div>
251
+ </div>
252
+ </div>
253
+ </div>
254
+ </div>
255
+ </div>
256
+
257
+ <div class="col-lg-9">
258
+