WordPress Online Booking and Scheduling Plugin – Bookly - Version 11.3

Version Description

  • Fixed issue with daylight saving time and Google Calendar
  • Fixed formatting for {next_day_agenda}
Download this release

Release Info

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

Code changes from version 7.6.1 to 11.3

Files changed (123) hide show
  1. autoload.php +12 -12
  2. backend/AB_Backend.php +0 -111
  3. backend/Backend.php +131 -0
  4. backend/modules/appearance/AB_AppearanceController.php +0 -152
  5. backend/modules/appearance/Controller.php +202 -0
  6. backend/modules/appearance/resources/css/appearance.css +0 -42
  7. backend/modules/appearance/resources/js/appearance.js +279 -123
  8. backend/modules/appearance/templates/_1_service.php +154 -96
  9. backend/modules/appearance/templates/_2_time.php +0 -192
  10. backend/modules/appearance/templates/_3_details.php +0 -42
  11. backend/modules/appearance/templates/_3_time.php +165 -0
  12. backend/modules/appearance/templates/_4_cart.php +132 -0
  13. backend/modules/appearance/templates/_4_payment.php +0 -60
  14. backend/modules/appearance/templates/_5_details.php +63 -0
  15. backend/modules/appearance/templates/_5_done.php +0 -8
  16. backend/modules/appearance/templates/_6_payment.php +67 -0
  17. backend/modules/appearance/templates/_7_done.php +7 -0
  18. backend/modules/appearance/templates/_card_payment.php +23 -19
  19. backend/modules/appearance/templates/_codes.php +25 -16
  20. backend/modules/appearance/templates/_progress_tracker.php +35 -25
  21. backend/modules/appearance/templates/index.php +113 -77
  22. backend/modules/appointments/AB_AppointmentsController.php +0 -276
  23. backend/modules/appointments/Controller.php +246 -0
  24. backend/modules/appointments/resources/js/appointments.js +271 -0
  25. backend/modules/appointments/resources/js/ng-app.js +0 -208
  26. backend/modules/appointments/templates/_export_dialog.php +40 -0
  27. backend/modules/appointments/templates/_print_dialog.php +1 -0
  28. backend/modules/appointments/templates/index.php +106 -67
  29. backend/modules/calendar/AB_CalendarController.php +0 -488
  30. backend/modules/calendar/Components.php +62 -0
  31. backend/modules/calendar/Controller.php +584 -0
  32. backend/modules/calendar/forms/AB_AppointmentForm.php +0 -22
  33. backend/modules/calendar/forms/AB_CustomerAppointmentForm.php +0 -17
  34. backend/modules/calendar/resources/css/calendar.css +0 -215
  35. backend/modules/calendar/resources/css/fullcalendar.min.css +1 -1
  36. backend/modules/calendar/resources/js/calendar.js +58 -70
  37. backend/modules/calendar/resources/js/fullcalendar.min.js +5 -4
  38. backend/{resources/js/ng-edit_appointment_dialog.js → modules/calendar/resources/js/ng-appointment_dialog.js} +288 -131
  39. backend/modules/calendar/templates/_appointment_dialog.php +149 -0
  40. backend/modules/calendar/templates/_appointment_form.php +0 -108
  41. backend/modules/calendar/templates/_custom_fields_form.php +0 -77
  42. backend/modules/calendar/templates/_customer_details_dialog.php +98 -0
  43. backend/modules/calendar/templates/calendar.php +79 -49
  44. backend/modules/coupons/AB_CouponsController.php +0 -43
  45. backend/modules/coupons/Controller.php +64 -0
  46. backend/modules/coupons/forms/Coupon.php +19 -0
  47. backend/modules/coupons/resources/css/coupons.css +0 -20
  48. backend/modules/coupons/resources/js/coupons.js +61 -2
  49. backend/modules/coupons/templates/_list.php +0 -4
  50. backend/modules/coupons/templates/_modal.php +1 -0
  51. backend/modules/coupons/templates/index.php +40 -30
  52. backend/modules/custom_fields/AB_CustomFieldsController.php +0 -63
  53. backend/modules/custom_fields/Controller.php +88 -0
  54. backend/modules/custom_fields/resources/css/custom_fields.css +0 -86
  55. backend/modules/custom_fields/resources/js/custom_fields.js +86 -19
  56. backend/modules/custom_fields/templates/_services.php +32 -0
  57. backend/modules/custom_fields/templates/index.php +303 -129
  58. backend/modules/customer/AB_CustomerController.php +0 -254
  59. backend/modules/customer/forms/AB_CustomerForm.php +0 -28
  60. backend/modules/customer/resources/css/customers.css +0 -10
  61. backend/modules/customer/resources/js/ng-app.js +0 -356
  62. backend/modules/customer/templates/_import.php +0 -37
  63. backend/modules/customer/templates/index.php +0 -136
  64. backend/modules/customer/templates/ng-new_customer_dialog.php +0 -110
  65. backend/modules/customers/Components.php +44 -0
  66. backend/modules/customers/Controller.php +235 -0
  67. backend/modules/customers/forms/Customer.php +25 -0
  68. backend/modules/customers/resources/js/customers.js +222 -0
  69. backend/modules/customers/resources/js/ng-customer_dialog.js +122 -0
  70. backend/modules/customers/templates/_customer_dialog.php +60 -0
  71. backend/modules/customers/templates/_export.php +1 -0
  72. backend/modules/customers/templates/_import.php +36 -0
  73. backend/modules/customers/templates/index.php +89 -0
  74. backend/modules/debug/Controller.php +181 -0
  75. backend/modules/debug/resources/css/style.css +12 -0
  76. backend/modules/debug/resources/js/debug.js +3 -0
  77. backend/modules/debug/templates/index.php +71 -0
  78. backend/modules/notifications/AB_NotificationsController.php +0 -70
  79. backend/modules/notifications/Controller.php +108 -0
  80. backend/modules/notifications/forms/AB_NotificationsForm.php +0 -166
  81. backend/modules/notifications/forms/Notifications.php +213 -0
  82. backend/modules/notifications/resources/css/notifications.css +0 -29
  83. backend/modules/notifications/resources/js/ng-app.js +96 -0
  84. backend/modules/notifications/resources/js/notification.js +16 -12
  85. backend/modules/notifications/templates/_codes.php +33 -24
  86. backend/modules/notifications/templates/_codes_cart.php +16 -0
  87. backend/modules/notifications/templates/_codes_client_new_wp_user.php +15 -12
  88. backend/modules/notifications/templates/_codes_staff_agenda.php +7 -4
  89. backend/modules/notifications/templates/_test_email_notifications_modal.php +105 -0
  90. backend/modules/notifications/templates/index.php +135 -121
  91. backend/modules/payment/AB_PaymentController.php +0 -84
  92. backend/modules/payment/templates/_alert.php +0 -4
  93. backend/modules/payment/templates/index.php +0 -148
  94. backend/modules/payments/Components.php +34 -0
  95. backend/modules/payments/Controller.php +263 -0
  96. backend/modules/payments/resources/js/ng-payment_details_dialog.js +69 -0
  97. backend/modules/payments/resources/js/payments.js +184 -0
  98. backend/modules/payments/templates/_payment_details_dialog.php +21 -0
  99. backend/modules/payments/templates/details.php +132 -0
  100. backend/modules/payments/templates/index.php +89 -0
  101. backend/modules/service/AB_ServiceController.php +0 -282
  102. backend/modules/service/forms/AB_CategoryForm.php +0 -25
  103. backend/modules/service/forms/AB_ServiceForm.php +0 -47
  104. backend/modules/service/resources/css/service.css +0 -144
  105. backend/modules/service/resources/js/service.js +0 -344
  106. backend/modules/service/templates/_list.php +0 -177
  107. backend/modules/service/templates/index.php +0 -105
  108. backend/modules/services/Controller.php +288 -0
  109. backend/modules/services/forms/Category.php +22 -0
  110. backend/modules/services/forms/Service.php +52 -0
  111. backend/modules/services/resources/js/service.js +511 -0
  112. backend/modules/services/templates/_category_item.php +18 -0
  113. backend/modules/services/templates/_list.php +311 -0
  114. backend/modules/services/templates/index.php +104 -0
  115. backend/modules/settings/AB_SettingsController.php +0 -197
  116. backend/modules/settings/Controller.php +241 -0
  117. backend/modules/settings/forms/AB_BusinessHoursForm.php +0 -66
  118. backend/modules/settings/forms/AB_CompanyForm.php +0 -64
  119. backend/modules/settings/forms/BusinessHours.php +81 -0
  120. backend/modules/settings/forms/Payments.php +27 -0
  121. backend/modules/settings/resources/js/settings.js +145 -87
  122. backend/modules/settings/templates/_cartForm.php +42 -0
  123. backend/modules/settings/templates/_companyForm.php +49 -48
autoload.php CHANGED
@@ -1,19 +1,19 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
 
3
  /**
4
- * @param $className
 
5
  */
6
- function bookly_autoload( $className ) {
7
-
8
- $paths = array(
9
- '/lib/'
10
- );
11
-
12
- foreach ( $paths as $path ) {
13
- if ( file_exists( AB_PATH . $path . $className . '.php' ) ) {
14
- require_once( AB_PATH . $path . $className . '.php' );
15
  }
16
  }
17
  }
18
-
19
- spl_autoload_register( 'bookly_autoload' );
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 );
 
backend/AB_Backend.php DELETED
@@ -1,111 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- class AB_Backend {
4
-
5
- public function __construct() {
6
- add_action( 'admin_menu', array( $this, 'addAdminMenu' ) );
7
-
8
- // Backend controllers.
9
- $this->apearanceController = new AB_AppearanceController();
10
- $this->calendarController = new AB_CalendarController();
11
- $this->customerController = new AB_CustomerController();
12
- $this->notificationsController = new AB_NotificationsController();
13
- $this->paymentController = new AB_PaymentController();
14
- $this->serviceController = new AB_ServiceController();
15
- $this->smsController = new AB_SmsController();
16
- $this->settingsController = new AB_SettingsController();
17
- $this->staffController = new AB_StaffController();
18
- $this->couponsController = new AB_CouponsController();
19
- $this->customFieldsController = new AB_CustomFieldsController();
20
- $this->appointmentsController = new AB_AppointmentsController();
21
-
22
- // Frontend controllers that work via admin-ajax.php.
23
- $this->bookingController = new AB_BookingController();
24
-
25
- add_action( 'wp_loaded', array( $this, 'init' ) );
26
- add_action( 'admin_init', array( $this, 'addTinyMCEPlugin' ) );
27
- }
28
-
29
- public function addTinyMCEPlugin() {
30
- /** @var WP_User $current_user */
31
- global $current_user;
32
- new AB_TinyMCE_Plugin();
33
- }
34
-
35
- public function init() {
36
- if ( ! session_id() ) {
37
- @session_start();
38
- }
39
-
40
- if ( isset( $_POST[ 'action' ] ) ) {
41
- switch ( $_POST[ 'action' ] ) {
42
- case 'ab_update_staff':
43
- $this->staffController->updateStaff();
44
- break;
45
- }
46
- }
47
- }
48
-
49
- public function addAdminMenu() {
50
- /** @var WP_User $current_user */
51
- global $current_user;
52
-
53
- // Translated submenu pages.
54
- $calendar = __( 'Calendar', 'bookly' );
55
- $appointments = __( 'Appointments', 'bookly' );
56
- $staff_members = __( 'Staff Members', 'bookly' );
57
- $services = __( 'Services', 'bookly' );
58
- $sms = __( 'SMS Notifications', 'bookly' );
59
- $notifications = __( 'Email Notifications', 'bookly' );
60
- $customers = __( 'Customers', 'bookly' );
61
- $payments = __( 'Payments', 'bookly' );
62
- $appearance = __( 'Appearance', 'bookly' );
63
- $settings = __( 'Settings', 'bookly' );
64
- $coupons = __( 'Coupons', 'bookly' );
65
- $custom_fields = __( 'Custom Fields', 'bookly' );
66
-
67
- if ( $current_user->has_cap( 'administrator' ) || AB_Staff::query()->where( 'wp_user_id', $current_user->ID )->count() ) {
68
- if ( function_exists( 'add_options_page' ) ) {
69
- $dynamic_position = '80.0000001' . mt_rand( 1, 1000 ); // position always is under `Settings`
70
- add_menu_page( 'Bookly', 'Bookly', 'read', 'ab-system', '',
71
- plugins_url('resources/images/menu.png', __FILE__), $dynamic_position );
72
- add_submenu_page( 'ab-system', $calendar, $calendar, 'read', 'ab-calendar',
73
- array( $this->calendarController, 'index' ) );
74
- add_submenu_page( 'ab-system', $appointments, $appointments, 'manage_options', 'ab-appointments',
75
- array( $this->appointmentsController, 'index' ) );
76
- if ( $current_user->has_cap( 'administrator' ) ) {
77
- add_submenu_page( 'ab-system', $staff_members, $staff_members, 'manage_options', AB_StaffController::page_slug,
78
- array( $this->staffController, 'index' ) );
79
- } else {
80
- if ( 1 == get_option( 'ab_settings_allow_staff_members_edit_profile' ) ) {
81
- add_submenu_page( 'ab-system', __( 'Profile', 'bookly' ), __( 'Profile', 'bookly' ), 'read', AB_StaffController::page_slug,
82
- array( $this->staffController, 'index' ) );
83
- }
84
- }
85
- add_submenu_page( 'ab-system', $services, $services, 'manage_options', AB_ServiceController::page_slug,
86
- array( $this->serviceController, 'index' ) );
87
- add_submenu_page( 'ab-system', $customers, $customers, 'manage_options', AB_CustomerController::page_slug,
88
- array( $this->customerController, 'index' ) );
89
- add_submenu_page( 'ab-system', $notifications, $notifications, 'manage_options', 'ab-notifications',
90
- array( $this->notificationsController, 'index' ) );
91
- add_submenu_page( 'ab-system', $sms, $sms, 'manage_options', AB_SmsController::page_slug,
92
- array( $this->smsController, 'index' ) );
93
- add_submenu_page( 'ab-system', $payments, $payments, 'manage_options', 'ab-payments',
94
- array( $this->paymentController, 'index' ) );
95
- add_submenu_page( 'ab-system', $appearance, $appearance, 'manage_options', 'ab-appearance',
96
- array( $this->apearanceController, 'index' ) );
97
- add_submenu_page( 'ab-system', $custom_fields, $custom_fields, 'manage_options', 'ab-custom-fields',
98
- array( $this->customFieldsController, 'index' ) );
99
- add_submenu_page( 'ab-system', $coupons, $coupons, 'manage_options', 'ab-coupons',
100
- array( $this->couponsController, 'index' ) );
101
- add_submenu_page( 'ab-system', $settings, $settings, 'manage_options', AB_SettingsController::page_slug,
102
- array( $this->settingsController, 'index' ) );
103
-
104
- global $submenu;
105
- do_action( 'bookly_addons_menu', 'ab-system' );
106
- unset( $submenu[ 'ab-system' ][ 0 ] );
107
- }
108
- }
109
- }
110
-
111
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/Backend.php ADDED
@@ -0,0 +1,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->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
+ }
backend/modules/appearance/AB_AppearanceController.php DELETED
@@ -1,152 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_AppearanceController
5
- */
6
- class AB_AppearanceController extends AB_Controller {
7
-
8
- /**
9
- * Default Action
10
- */
11
- public function index()
12
- {
13
- /** @var WP_Locale $wp_locale */
14
- global $wp_locale;
15
-
16
- $this->enqueueStyles( array(
17
- 'frontend' => array(
18
- 'css/intlTelInput.css',
19
- 'css/ladda.min.css',
20
- 'css/bookly-main.css',
21
- 'css/ab-columnizer.css',
22
- 'css/picker.classic.css',
23
- 'css/picker.classic.date.css',
24
- 'css/ab-picker.css',
25
- ),
26
- 'backend' => array(
27
- 'css/bookly.main-backend.css',
28
- 'bootstrap/css/bootstrap.min.css',
29
- 'css/bootstrap-editable.css',
30
- ),
31
- 'wp' => array(
32
- 'wp-color-picker',
33
- ),
34
- 'module' => array(
35
- 'css/appearance.css',
36
- )
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
- ),
44
- 'frontend' => array(
45
- 'js/picker.js' => array( 'jquery' ),
46
- 'js/picker.date.js' => array( 'jquery' ),
47
- 'js/spin.min.js' => array( 'jquery' ),
48
- 'js/ladda.min.js' => array( 'jquery' ),
49
- 'js/intlTelInput.min.js' => array( 'ab-picker.date.js' )
50
- ),
51
- 'wp' => array(
52
- 'wp-color-picker',
53
- ),
54
- 'module' => array(
55
- 'js/appearance.js' => array( 'jquery' ),
56
- )
57
- ) );
58
-
59
- wp_localize_script( 'ab-picker.date.js', 'BooklyL10n', array(
60
- 'today' => __( 'Today', 'bookly' ),
61
- 'months' => array_values( $wp_locale->month ),
62
- 'days' => array_values( $wp_locale->weekday_abbrev ),
63
- 'nextMonth' => __( 'Next month', 'bookly' ),
64
- 'prevMonth' => __( 'Previous month', 'bookly' ),
65
- 'date_format' => AB_DateTimeUtils::convertFormat( 'date', AB_DateTimeUtils::FORMAT_PICKADATE ),
66
- 'start_of_week' => (int) get_option( 'start_of_week' ),
67
- 'intlTelInput_utils' => plugins_url( 'intlTelInput.utils.js', AB_PATH . '/frontend/resources/js/intlTelInput.utils.js' ),
68
- ) );
69
-
70
- // Initialize steps (tabs).
71
- $this->steps = array(
72
- 1 => get_option( 'ab_appearance_text_step_service' ),
73
- 2 => get_option( 'ab_appearance_text_step_time' ),
74
- 3 => get_option( 'ab_appearance_text_step_details' ),
75
- 4 => get_option( 'ab_appearance_text_step_payment' ),
76
- 5 => get_option( 'ab_appearance_text_step_done' )
77
- );
78
-
79
- // Render general layout.
80
- $this->render( 'index' );
81
- } // index
82
-
83
- /**
84
- * Update options
85
- */
86
- public function executeUpdateAppearanceOptions()
87
- {
88
- if ( $this->hasParameter( 'options' ) ) {
89
- $get_option = $this->getParameter( 'options' );
90
- $options = array(
91
- // Info text.
92
- 'ab_appearance_text_info_first_step' => $get_option['text_info_first_step'],
93
- 'ab_appearance_text_info_second_step' => $get_option['text_info_second_step'],
94
- 'ab_appearance_text_info_third_step' => $get_option['text_info_third_step'],
95
- 'ab_appearance_text_info_third_step_guest' => $get_option['text_info_third_step_guest'],
96
- 'ab_appearance_text_info_fourth_step' => $get_option['text_info_fourth_step'],
97
- 'ab_appearance_text_info_fifth_step' => $get_option['text_info_fifth_step'],
98
- 'ab_appearance_text_info_coupon' => $get_option['text_info_coupon'],
99
- // Color.
100
- 'ab_appearance_color' => $get_option['color'],
101
- // Step, label and option texts.
102
- 'ab_appearance_text_step_service' => $get_option['text_step_service'],
103
- 'ab_appearance_text_step_time' => $get_option['text_step_time'],
104
- 'ab_appearance_text_step_details' => $get_option['text_step_details'],
105
- 'ab_appearance_text_step_payment' => $get_option['text_step_payment'],
106
- 'ab_appearance_text_step_done' => $get_option['text_step_done'],
107
- 'ab_appearance_text_label_category' => $get_option['text_label_category'],
108
- 'ab_appearance_text_label_service' => $get_option['text_label_service'],
109
- 'ab_appearance_text_label_number_of_persons' => $get_option['text_label_number_of_persons'],
110
- 'ab_appearance_text_label_employee' => $get_option['text_label_employee'],
111
- 'ab_appearance_text_label_select_date' => $get_option['text_label_select_date'],
112
- 'ab_appearance_text_label_start_from' => $get_option['text_label_start_from'],
113
- 'ab_appearance_text_label_finish_by' => $get_option['text_label_finish_by'],
114
- 'ab_appearance_text_label_name' => $get_option['text_label_name'],
115
- 'ab_appearance_text_label_phone' => $get_option['text_label_phone'],
116
- 'ab_appearance_text_label_email' => $get_option['text_label_email'],
117
- 'ab_appearance_text_option_service' => $get_option['text_option_service'],
118
- 'ab_appearance_text_option_category' => $get_option['text_option_category'],
119
- 'ab_appearance_text_option_employee' => $get_option['text_option_employee'],
120
- 'ab_appearance_text_label_coupon' => $get_option['text_label_coupon'],
121
- 'ab_appearance_text_label_pay_locally' => $get_option['text_label_pay_locally'],
122
- // Checkboxes.
123
- 'ab_appearance_show_progress_tracker' => $get_option['progress_tracker'],
124
- 'ab_appearance_show_blocked_timeslots' => $get_option['blocked_timeslots'],
125
- 'ab_appearance_show_day_one_column' => $get_option['day_one_column'],
126
- 'ab_appearance_show_calendar' => $get_option['show_calendar'],
127
- );
128
-
129
- // Save options.
130
- foreach ( $options as $option_name => $option_value ) {
131
- update_option( $option_name, $option_value );
132
- // Register string for translate in WPML.
133
- if ( strpos( $option_name, 'ab_appearance_text_' ) === 0 ) {
134
- do_action( 'wpml_register_single_string', 'bookly', $option_name, $option_value );
135
- }
136
- }
137
- }
138
- exit;
139
- }
140
-
141
- /**
142
- * Override parent method to add 'wp_ajax_ab_' prefix
143
- * so current 'execute*' methods look nicer.
144
- *
145
- * @param string $prefix
146
- */
147
- protected function registerWpActions( $prefix = '' )
148
- {
149
- parent::registerWpActions( 'wp_ajax_ab_' );
150
- }
151
-
152
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appearance/Controller.php ADDED
@@ -0,0 +1,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
+ /**
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
+ }
backend/modules/appearance/resources/css/appearance.css DELETED
@@ -1,42 +0,0 @@
1
- /* Backend-Appearance */
2
- #ab-appearance { max-width: 960px; }
3
- div.controls {
4
- float: right;
5
- margin-right: 15px;
6
- margin-top: 10px;
7
- }
8
-
9
- span.spinner {
10
- float: left;
11
- margin-right: 20px;
12
- }
13
-
14
- .editable-click {
15
- cursor: pointer;
16
- }
17
-
18
- .ab-first-step .select-list {
19
- background: transparent;
20
- }
21
-
22
- input.appearance-color-picker{
23
- height: 24px !important;
24
- background:#f3f3f3;
25
- background-image:-webkit-gradient(linear,left top,left bottom,from(#fefefe),to(#f4f4f4));
26
- background-image:-webkit-linear-gradient(top,#fefefe,#f4f4f4);
27
- background-image:-moz-linear-gradient(top,#fefefe,#f4f4f4);
28
- background-image:-o-linear-gradient(top,#fefefe,#f4f4f4);
29
- background-image:linear-gradient(to bottom,#fefefe,#f4f4f4);
30
- }
31
-
32
- .editable-container.popover { z-index: 10001; }
33
- .editable-container.editable-popup .arrow { margin-left: 0 !important; }
34
- .editable-input > textarea { width: 475px!important; }
35
-
36
- .editableform-loading { background: url('../../../../resources/images/loading.gif') center center no-repeat !important; }
37
- .editable-clear-x { background: url('../../../../resources/images/clear.png') center center no-repeat !important; }
38
-
39
- /* media query */
40
- @media screen and (max-width: 782px) {
41
- .ab-columnizer-wrap { height: 662px!important; }
42
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appearance/resources/js/appearance.js CHANGED
@@ -1,23 +1,28 @@
1
  jQuery(function($) {
2
  var // Progress Tracker.
3
  $progress_tracker_option = $('input#ab-progress-tracker-checkbox'),
 
4
  // Time slots setting.
5
  $blocked_timeslots_option = $('input#ab-blocked-timeslots-checkbox'),
6
  $day_one_column_option = $('input#ab-day-one-column-checkbox'),
7
  $show_calendar_option = $('input#ab-show-calendar-checkbox'),
8
- // Tabs.
9
- $tabs = $('div.tabbable').find('.nav-tabs'),
10
- $tab_content = $('div.tab-content'),
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_time = $('#ab-text-step-time'),
 
17
  $text_step_details = $('#ab-text-step-details'),
18
  $text_step_payment = $('#ab-text-step-payment'),
19
  $text_step_done = $('#ab-text-step-done'),
 
 
20
  $text_label_category = $('#ab-text-label-category'),
 
21
  $text_option_category = $('#ab-text-option-category'),
22
  $text_option_service = $('#ab-text-option-service'),
23
  $text_option_employee = $('#ab-text-option-employee'),
@@ -26,54 +31,85 @@ jQuery(function($) {
26
  $text_label_employee = $('#ab-text-label-employee'),
27
  $text_label_select_date = $('#ab-text-label-select_date'),
28
  $text_label_start_from = $('#ab-text-label-start_from'),
 
 
 
 
29
  $text_label_finish_by = $('#ab-text-label-finish_by'),
30
  $text_label_name = $('#ab-text-label-name'),
31
  $text_label_phone = $('#ab-text-label-phone'),
32
  $text_label_email = $('#ab-text-label-email'),
33
  $text_label_coupon = $('#ab-text-label-coupon'),
34
- $text_info_service = $('#ab-text-info-first'),
35
- $text_info_time = $('#ab-text-info-second'),
36
- $text_info_details = $('#ab-text-info-third'),
37
- $text_info_details_guest = $('#ab-text-info-third-guest'),
38
- $text_info_payment = $('#ab-text-info-fourth'),
39
- $text_info_done = $('#ab-text-info-fifth'),
40
  $text_info_coupon = $('#ab-text-info-coupon'),
41
- $color_picker = $('.wp-color-picker'),
 
 
 
 
 
 
 
42
  $ab_editable = $('.ab_editable'),
43
  $text_label_pay_locally = $('#ab-text-label-pay-locally'),
 
44
  // Calendars.
45
  $second_step_calendar = $('.ab-selected-date'),
46
- $second_step_calendar_wrap = $('.ab-slot-calendar')
 
 
47
  ;
48
 
49
- $('.ab-user-phone').intlTelInput({
50
- defaultCountry: 'auto',
51
- geoIpLookup: function(callback) {
52
- $.get(ajaxurl, {action: 'ab_ip_info'}, function() {}, 'json').always(function(resp) {
53
- var countryCode = (resp && resp.country) ? resp.country : '';
54
- callback(countryCode);
55
- });
56
- },
57
- utilsScript: BooklyL10n.intlTelInput_utils
58
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
  // menu fix for WP 3.8.1
61
  $('#toplevel_page_ab-system > ul').css('margin-left', '0px');
62
 
63
  // Tabs.
64
- $tabs.find('.ab-step-tabs').on('click', function() {
65
- var $step_id = $(this).data('step-id');
66
- // Hide all other tab content and show only current.
67
- $tab_content.children('div[data-step-id!="' + $step_id + '"]').removeClass('active').hide();
68
- $tab_content.children('div[data-step-id="' + $step_id + '"]').addClass('active').show();
69
- }).filter('li:first').trigger('click');
 
70
 
 
 
 
71
  // Apply color from color picker.
72
  var applyColor = function() {
73
  var color_important = $color_picker.wpColorPicker('color') + '!important';
74
- $('div.ab-progress-tracker').find('li.ab-step-tabs').filter('.active').find('a').css('color', $color_picker.wpColorPicker('color'));
75
- $('div.ab-progress-tracker').find('li.ab-step-tabs').filter('.active').find('div.step').css('background', $color_picker.wpColorPicker('color'));
76
  $('.ab-mobile-step_1 label').css('color', $color_picker.wpColorPicker('color'));
 
77
  $('.ab-next-step, .ab-mobile-next-step').css('background', $color_picker.wpColorPicker('color'));
78
  $('.ab-week-days label').css('background-color', $color_picker.wpColorPicker('color'));
79
  $('.picker__frame').attr('style', 'background: ' + color_important);
@@ -84,12 +120,12 @@ jQuery(function($) {
84
  $('.picker__day--selected').attr('style', 'color: ' + color_important);
85
  $('.picker__button--clear').attr('style', 'color: ' + color_important);
86
  $('.picker__button--today').attr('style', 'color: ' + color_important);
87
- $('.ab-columnizer .ab-available-day').css({
 
88
  'background': $color_picker.wpColorPicker('color'),
89
  'border-color': $color_picker.wpColorPicker('color')
90
  });
91
- $('.ab-nav-tabs .ladda-button, .ab-nav-steps .ladda-button, .ab-btn.ladda-button').css('background-color', $color_picker.wpColorPicker('color'));
92
- $('.ab-columnizer .ab-available-hour').off().hover(
93
  function() { // mouse-on
94
  $(this).css({
95
  'color': $color_picker.wpColorPicker('color'),
@@ -117,18 +153,15 @@ jQuery(function($) {
117
  });
118
  }
119
  );
120
- $('div.ab-formGroup > label.ab-formLabel').css('color', $color_picker.wpColorPicker('color'));
121
- $('.ab-to-second-step, .ab-to-fourth-step, .ab-to-third-step, .ab-final-step')
122
- .css('background', $color_picker.wpColorPicker('color'));
 
 
 
123
  };
124
  $color_picker.wpColorPicker({
125
- change : function() {
126
- applyColor();
127
- var style_arrow = '' +
128
- '.picker__nav--next:before { border-left: 6px solid ' + $color_picker.wpColorPicker('color') + '!important; } ' +
129
- '.picker__nav--prev:before { border-right: 6px solid ' + $color_picker.wpColorPicker('color') + '!important; }';
130
- $('#ab_update_arrow').html(style_arrow);
131
- }
132
  });
133
  // Init calendars.
134
  $('.ab-date-from').pickadate({
@@ -166,8 +199,6 @@ jQuery(function($) {
166
  onClose : function() {
167
  this.open(false);
168
  }
169
-
170
-
171
  });
172
  $second_step_calendar_wrap.find('.picker__holder').css({ top : '0px', left : '0px' });
173
  $second_step_calendar_wrap.toggle($show_calendar_option.prop('checked'));
@@ -181,39 +212,66 @@ jQuery(function($) {
181
  // Color.
182
  'color' : $color_picker.wpColorPicker('color'),
183
  // Info text.
184
- 'text_info_first_step' : $.trim($text_info_service.text() == 'Empty' ? '' : $text_info_service.text()),
185
- 'text_info_second_step' : $.trim($text_info_time.text() == 'Empty' ? '' : $text_info_time.text()),
186
- 'text_info_third_step' : $.trim($text_info_details.text() == 'Empty' ? '' : $text_info_details.text()),
187
- 'text_info_third_step_guest' : $.trim($text_info_details_guest.text() == 'Empty' ? '' : $text_info_details_guest.text()),
188
- 'text_info_fourth_step' : $.trim($text_info_payment.text() == 'Empty' ? '' : $text_info_payment.text()),
189
- 'text_info_fifth_step' : $.trim($text_info_done.text() == 'Empty' ? '' : $text_info_done.text()),
190
- 'text_info_coupon' : $.trim($text_info_coupon.text() == 'Empty' ? '' : $text_info_coupon.text()),
 
 
191
  // Step and label texts.
192
- 'text_step_service' : $.trim($text_step_service.text() == 'Empty' ? '' : $text_step_service.text()),
193
- 'text_step_time' : $.trim($text_step_time.text() == 'Empty' ? '' : $text_step_time.text()),
194
- 'text_step_details' : $.trim($text_step_details.text() == 'Empty' ? '' : $text_step_details.text()),
195
- 'text_step_payment' : $.trim($text_step_payment.text() == 'Empty' ? '' : $text_step_payment.text()),
196
- 'text_step_done' : $.trim($text_step_done.text() == 'Empty' ? '' : $text_step_done.text()),
197
- 'text_label_category' : $.trim($text_label_category.text() == 'Empty' ? '' : $text_label_category.text()),
198
- 'text_label_service' : $.trim($text_label_service.text() == 'Empty' ? '' : $text_label_service.text()),
199
- 'text_label_number_of_persons' : $.trim($text_label_number_of_persons.text() == 'Empty' ? '' : $text_label_number_of_persons.text()),
200
- 'text_label_employee' : $.trim($text_label_employee.text() == 'Empty' ? '' : $text_label_employee.text()),
201
- 'text_label_select_date' : $.trim($text_label_select_date.text() == 'Empty' ? '' : $text_label_select_date.text()),
202
- 'text_label_start_from' : $.trim($text_label_start_from.text() == 'Empty' ? '' : $text_label_start_from.text()),
203
- 'text_label_finish_by' : $.trim($text_label_finish_by.text() == 'Empty' ? '' : $text_label_finish_by.text()),
204
- 'text_label_name' : $.trim($text_label_name.text() == 'Empty' ? '' : $text_label_name.text()),
205
- 'text_label_phone' : $.trim($text_label_phone.text() == 'Empty' ? '' : $text_label_phone.text()),
206
- 'text_label_email' : $.trim($text_label_email.text() == 'Empty' ? '' : $text_label_email.text()),
207
- 'text_label_coupon' : $.trim($text_label_coupon.text() == 'Empty' ? '' : $text_label_coupon.text()),
208
- 'text_option_category' : $.trim($text_option_category.text() == 'Empty' ? '' : $text_option_category.text()),
209
- 'text_option_service' : $.trim($text_option_service.text() == 'Empty' ? '' : $text_option_service.text()),
210
- 'text_option_employee' : $.trim($text_option_employee.text() == 'Empty' ? '' : $text_option_employee.text()),
211
- 'text_label_pay_locally' : $.trim($text_label_pay_locally.text() == 'Empty' ? '' : $text_label_pay_locally.text()),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  // Checkboxes.
213
  'progress_tracker' : Number($progress_tracker_option.prop('checked')),
 
214
  'blocked_timeslots' : Number($blocked_timeslots_option.prop('checked')),
215
  'day_one_column' : Number($day_one_column_option.prop('checked')),
216
- 'show_calendar' : Number($show_calendar_option.prop('checked'))
 
 
217
  } // options
218
  }; // data
219
 
@@ -222,7 +280,7 @@ jQuery(function($) {
222
  ladda.start();
223
  $.post(ajaxurl, data, function (response) {
224
  ladda.stop();
225
- $('.notice-success').show();
226
  });
227
  });
228
 
@@ -233,69 +291,105 @@ jQuery(function($) {
233
 
234
  // Reset texts.
235
  jQuery.each($('.editable'), function() {
236
- var $default_value = $(this).data('default'),
237
- $steps = $(this).data('link-class');
 
238
 
239
- $(this).text($default_value); //default value for texts
240
- $('.' + $steps).text($default_value); //default value for steps
241
- $(this).editable('setValue', $default_value); // default value for editable inputs
242
  });
243
 
244
  // default value for multiple inputs
 
 
 
 
 
 
 
 
 
 
245
  $text_label_category.editable('setValue', {
246
  label: $text_label_category.text(),
247
  option: $text_option_category.text(),
248
- id_option: $text_label_category.data('link-class')
249
  });
250
 
251
  $text_label_service.editable('setValue', {
252
  label: $text_label_service.text(),
253
  option: $text_option_service.text(),
254
- id_option: $text_label_service.data('link-class')
 
 
 
255
  });
256
 
257
  $text_label_employee.editable('setValue', {
258
  label: $text_label_employee.text(),
259
  option: $text_option_employee.text(),
260
- id_option: $text_label_employee.data('link-class')
 
 
 
261
  });
262
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  });
264
 
265
  $progress_tracker_option.change(function(){
266
- $(this).is(':checked') ? $('div.ab-progress-tracker').show() : $('div.ab-progress-tracker').hide();
267
  }).trigger('change');
268
 
269
- var clickTwoStep = function() {
270
- $tabs.children('li').removeClass('active');
271
- $tabs.children('li[data-step-id="2"]').trigger('click').addClass('active');
272
- };
273
-
274
  var day_one_column = $('.ab-day-one-column'),
275
  day_columns = $('.ab-day-columns');
276
 
 
 
 
 
 
 
 
277
  // Change show calendar
278
  $show_calendar_option.change(function() {
279
  if (this.checked) {
280
  $second_step_calendar_wrap.show();
281
- day_columns.find('.col5,.col6,.col7').hide();
282
- day_one_column.find('.col5,.col6,.col7').hide();
 
283
  } else {
284
  $second_step_calendar_wrap.hide();
285
- day_one_column.find('.col5,.col6,.col7').css('display','inline-block');
286
- day_columns.find('.col5,.col6,.col7').css('display','inline-block');
 
287
  }
288
- clickTwoStep();
289
  });
290
 
291
  // Change blocked time slots.
292
  $blocked_timeslots_option.change(function(){
293
  if (this.checked) {
294
- $('.ab-available-hour.no-booked').removeClass('no-booked').addClass('booked');
295
  } else {
296
- $('.ab-available-hour.booked').removeClass('booked').addClass('no-booked');
297
  }
298
- clickTwoStep();
299
  });
300
 
301
  // Change day one column.
@@ -307,7 +401,6 @@ jQuery(function($) {
307
  day_one_column.hide();
308
  day_columns.show();
309
  }
310
- clickTwoStep();
311
  });
312
 
313
  // Clickable week-days.
@@ -330,6 +423,7 @@ jQuery(function($) {
330
  $.extend(multiple.prototype, {
331
  render: function() {
332
  this.$input = this.$tpl.find('input');
 
333
  },
334
 
335
  value2html: function(value, element) {
@@ -339,6 +433,7 @@ jQuery(function($) {
339
  }
340
  $(element).text(value.label);
341
  $('#' + value.id_option).text(value.option);
 
342
  },
343
 
344
  activate: function () {
@@ -349,29 +444,47 @@ jQuery(function($) {
349
  if(!value) {
350
  return;
351
  }
 
 
 
352
  this.$input.filter('[name="label"]').val(value.label);
353
  this.$input.filter('[name="option"]').val(value.option);
354
  this.$input.filter('[name="id_option"]').val(value.id_option);
 
 
355
  },
356
 
357
  input2value: function() {
358
  return {
359
- label: this.$input.filter('[name="label"]').val(),
360
- option: this.$input.filter('[name="option"]').val(),
361
- id_option: this.$input.filter('[name="id_option"]').val()
 
 
 
362
  };
363
  }
364
  });
365
 
366
  multiple.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
367
- tpl: '<div class="editable-multiple"><label><input type="text" name="label" class="input-medium form-control"></label></div>'+
368
- '<div style="margin-top:5px;" class="editable-multiple"><label><input type="text" name="option" class="input-medium form-control"><input type="hidden" name="id_option"></label></div>',
 
369
 
370
  inputclass: ''
371
  });
372
 
373
  $.fn.editabletypes.multiple = multiple;
374
-
 
 
 
 
 
 
 
 
 
375
  $text_label_category.editable({
376
  value: {
377
  label: $text_label_category.text(),
@@ -383,45 +496,88 @@ jQuery(function($) {
383
  value: {
384
  label: $text_label_service.text(),
385
  option: $text_option_service.text(),
386
- id_option: $text_label_service.data('option-id')
 
 
 
387
  }
388
  });
389
  $text_label_employee.editable({
390
  value: {
391
  label: $text_label_employee.text(),
392
  option: $text_option_employee.text(),
393
- id_option: $text_label_employee.data('option-id')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
  }
395
  });
396
 
397
- $text_info_service.add('#ab-text-info-second').add('#ab-text-info-third').add('#ab-text-info-fourth').add('#ab-text-info-fifth').add('#ab-text-info-coupon').editable({placement: 'right'});
 
 
 
 
 
 
 
 
398
  $ab_editable.editable();
399
 
400
- $.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 style="margin-top: 10px;" class="editable-notes"></div><div class="editable-error-block"></div></div> </form>';
 
401
 
402
  $ab_editable.on('shown', function(e, editable) {
 
403
  $('.editable-notes').html($(e.target).data('notes'));
404
  });
405
-
406
- $("span[data-link-class^='text_step_']").on('save', function(e, params) {
407
- $("span[data-link-class='" + $(e.target).data('link-class') + "']").editable('setValue', params.newValue);
408
- $("span." + $(e.target).data('link-class')).text(params.newValue);
409
  });
410
 
411
- if(jQuery('.ab-authorizenet-payment').is(':checked')) {
412
- jQuery('form.ab-authorizenet').show();
413
- }
414
-
415
- if(jQuery('.ab-stripe-payment').is(':checked')) {
416
- jQuery('form.ab-stripe').show();
417
- }
 
 
 
 
 
 
 
418
 
419
- jQuery('input[type=radio]').change( function() {
420
- jQuery('form.ab-authorizenet').add('form.ab-stripe').hide();
421
- if(jQuery('.ab-authorizenet-payment').is(':checked')) {
422
- jQuery('form.ab-authorizenet').show();
423
- } else if(jQuery('.ab-stripe-payment').is(':checked')) {
424
- jQuery('form.ab-stripe').show();
425
  }
426
  });
 
 
 
 
 
 
 
427
  }); // 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 = $('#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'),
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);
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'),
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({
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'));
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
 
280
  ladda.start();
281
  $.post(ajaxurl, data, function (response) {
282
  ladda.stop();
283
+ booklyAlert({success : [BooklyL10n.saved]});
284
  });
285
  });
286
 
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.
401
  day_one_column.hide();
402
  day_columns.show();
403
  }
 
404
  });
405
 
406
  // Clickable week-days.
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) {
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 () {
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(),
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
backend/modules/appearance/templates/_1_service.php CHANGED
@@ -2,21 +2,30 @@
2
  /** @var WP_Locale $wp_locale */
3
  global $wp_locale;
4
  ?>
5
- <div class="ab-booking-form">
 
6
 
7
- <!-- Progress Tracker-->
8
- <?php $step = 1; include '_progress_tracker.php'; ?>
9
-
10
- <div class="ab-first-step">
11
- <div class="ab-row-fluid">
12
- <span data-inputclass="input-xxlarge" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_first_step' ) ) ?>" class="ab-bold ab_editable" id="ab-text-info-first" data-rows="7" data-type="textarea"><?php echo esc_html( get_option( 'ab_appearance_text_info_first_step' ) ) ?></span>
13
  </div>
14
- <div class=ab-service-form>
15
- <div class="ab-mobile-step_1 ab-four-cols ab-row-fluid">
16
- <div class="ab-formGroup ab-left">
17
- <label data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_category' ) ) ?>" class="ab-formLabel text_category_label" id="ab-text-label-category" data-type="multiple" data-option-id="ab-text-option-category"><?php echo esc_html( get_option( 'ab_appearance_text_label_category' ) ) ?></label>
18
- <div class="ab-formField">
19
- <select class="ab-formElement ab-select-mobile ab-select-category" style="width: 100%">
 
 
 
 
 
 
 
 
 
20
  <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>
21
  <option value="1">Cosmetic Dentistry</option>
22
  <option value="2">Invisalign</option>
@@ -25,113 +34,162 @@
25
  </select>
26
  </div>
27
  </div>
28
- <div class="ab-formGroup ab-left">
29
- <label data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_service' ) ) ?>" class="ab-formLabel text_service_label" id="ab-text-label-service" data-type="multiple" data-option-id="ab-text-option-service"><?php echo esc_html( get_option( 'ab_appearance_text_label_service' ) ) ?></label>
30
- <div class="ab-formField">
31
- <select class="ab-formElement ab-select-mobile ab-select-service" style="width: 100%">
32
- <option value="" 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>
33
- <option value="1">Crown and Bridge</option>
34
- <option value="2">Teeth Whitening</option>
35
- <option value="3">Veneers</option>
36
- <option value="4">Invisalign (invisable braces)</option>
37
- <option value="5">Orthodontics (braces)</option>
38
- <option value="6">Wisdom tooth Removal</option>
39
- <option value="7">Root Canal Treatment</option>
40
- <option value="8">Dentures</option>
 
 
 
 
 
 
 
 
 
 
41
  </select>
42
  </div>
43
  </div>
44
- <div class="ab-formGroup ab-left">
45
- <label data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_employee' ) ) ?>" class="ab-formLabel text_employee_label" id="ab-text-label-employee" data-type="multiple" data-option-id="ab-text-option-employee"><?php echo esc_html( get_option( 'ab_appearance_text_label_employee' ) ) ?></label>
46
- <div class="ab-formField">
47
- <select class="ab-formElement ab-select-mobile ab-select-employee" style="width: 100%">
48
- <option value="" 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>
49
- <option value="1">Nick Knight</option>
50
- <option value="2">Jane Howard</option>
51
- <option value="3">Ashley Stamp</option>
52
- <option value="4">Bradley Tannen</option>
53
- <option value="5">Wayne Turner</option>
54
- <option value="6">Emily Taylor</option>
55
- <option value="7">Hugh Canberg</option>
56
- <option value="8">Jim Gonzalez</option>
57
- <option value="9">Nancy Stinson</option>
58
- <option value="10">Marry Murphy</option>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  </select>
60
  </div>
61
  </div>
62
- <div class="ab-formGroup ab-left">
63
- <label data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_number_of_persons' ) ) ?>" class="ab-formLabel 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>
64
- <div class="ab-formField">
65
- <select class="ab-formElement ab-select-mobile ab-select-number-of-persons">
66
- <option value="1">1</option>
67
- <option value="2">2</option>
68
- <option value="3">3</option>
69
  </select>
70
  </div>
71
  </div>
72
-
73
- <button class="ab-right ab-mobile-next-step ab-btn ab-none ladda-button" onclick="return false">
74
- <span><?php _e( 'Next', 'bookly' ) ?></span>
75
- </button>
76
- </div>
77
- <div class="ab-mobile-step_2">
78
- <div class="ab-row-fluid">
79
- <div class="ab-available-date ab-formGroup ab-lastGroup ab-left">
80
- <label data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_select_date' ) ) ?>" class="ab_editable ab-nowrap" id="ab-text-label-select_date" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_label_select_date' ) ) ?></label>
81
- <div class="ab-input-wrap ab-formField">
82
- <span class="ab-date-wrap">
83
- <input style="background: white" class="ab-date-from ab-formElement picker__input--active" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
84
- </span>
85
- </div>
86
  </div>
87
- <div class="ab-available-days ab-left">
88
- <ul class="ab-week-days">
89
- <?php foreach ( $wp_locale->weekday_abbrev as $weekday_abbrev ): ?>
90
- <li>
91
- <div class="ab-bold"><?php echo $weekday_abbrev ?></div>
92
- <label class="active">
93
- <input class="ab-week-day" value="1" checked="checked" type="checkbox">
94
- </label>
95
- </li>
96
- <?php endforeach ?>
97
- </ul>
98
  </div>
99
- <div class="ab-time-range ab-left">
100
- <div class="ab-time-from ab-left">
101
- <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>
 
 
 
 
 
 
 
 
 
 
102
  <div>
103
- <select class="select-list ab-select-time-from">
104
- <?php for ( $i = 28800; $i <= 64800; $i += 3600 ): ?>
105
- <option><?php echo AB_DateTimeUtils::formatTime( $i ) ?></option>
106
- <?php endfor ?>
107
- </select>
108
  </div>
109
  </div>
110
- <div class="ab-time-to ab-left">
111
- <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>
 
112
  <div>
113
- <select class="select-list ab-select-time-to">
114
- <?php for ( $i = 28800; $i <= 64800; $i += 3600 ): ?>
115
- <option<?php selected( $i == 64800 ) ?>><?php echo AB_DateTimeUtils::formatTime( $i ) ?></option>
116
- <?php endfor ?>
117
- </select>
118
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
119
  </div>
120
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  </div>
122
- <div class="ab-row-fluid ab-nav-steps last-row ab-clear">
123
- <button class="ab-right ab-mobile-prev-step ab-btn ab-none ladda-button">
124
- <span><?php _e( 'Back', 'bookly' ) ?></span>
125
- </button>
126
- <button class="ab-right ab-next-step ab-btn ladda-button">
127
- <span><?php _e( 'Next', 'bookly' ) ?></span>
128
- </button>
129
  </div>
 
130
  </div>
131
  </div>
132
  </div>
133
  </div>
134
- <style id="ab_update_arrow">
 
 
 
 
 
135
  .picker__nav--next:before { border-left: 6px solid <?php echo get_option( 'ab_appearance_color' ) ?>!important; }
136
  .picker__nav--prev:before { border-right: 6px solid <?php echo get_option( 'ab_appearance_color' ) ?>!important; }
137
  </style>
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>
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>
backend/modules/appearance/templates/_2_time.php DELETED
@@ -1,192 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div id="ab-booking-form" class="ab-booking-form">
3
- <!-- Progress Tracker-->
4
- <?php $step = 2; include '_progress_tracker.php'; ?>
5
-
6
- <div class="ab-row-fluid">
7
- <span data-inputclass="input-xxlarge" data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 2 ), false ) ) ?>" data-placement="bottom" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_second_step' ) ) ?>" class="ab-text-info-second-preview ab-row-fluid ab_editable" id="ab-text-info-second" data-type="textarea"><?php echo esc_html( get_option( 'ab_appearance_text_info_second_step' ) ) ?></span>
8
- </div>
9
- <!-- timeslots -->
10
- <div class="ab-columnizer-wrap" style="height: 400px;">
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 style="margin-right: 40px;" 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-available-day ab-first-child"><?php echo date_i18n( 'D, M d' ) ?></button>
20
- <?php for ( $i = 50400; $i <= 57600; $i += 900 ): ?>
21
- <button class="ab-available-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 AB_DateTimeUtils::formatTime( $i ) ?>
25
- </span>
26
- </button>
27
- <?php endfor ?>
28
- </div>
29
- <div class="ab-column col2">
30
- <?php for ( $i = 58500; $i <= 63900; $i += 900 ): ?>
31
- <button class="ab-available-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
32
- <span class="ladda-label">
33
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( $i ) ?>
34
- </span>
35
- </button>
36
- <?php endfor ?>
37
- <button class="ab-available-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+1 day' ) ) ?></button>
38
- <button class="ab-available-hour ladda-button ab-last-child">
39
- <span class="ladda-label">
40
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( 28800 ) ?>
41
- </span>
42
- </button>
43
- <button class="ab-available-hour ladda-button ab-last-child">
44
- <span class="ladda-label">
45
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( 29700 ) ?>
46
- </span>
47
- </button>
48
- </div>
49
- <div class="ab-column col3">
50
- <?php for ( $i = 30600; $i <= 38700; $i += 900 ): ?>
51
- <button class="ab-available-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
52
- <span class="ladda-label">
53
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( $i ) ?>
54
- </span>
55
- </button>
56
- <?php endfor ?>
57
- </div>
58
- <div class="ab-column col4">
59
- <?php for ( $i = 39600; $i <= 47700; $i += 900 ): ?>
60
- <button class="ab-available-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
61
- <span class="ladda-label">
62
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( $i ) ?>
63
- </span>
64
- </button>
65
- <?php endfor ?>
66
- </div>
67
- <div class="ab-column col5" style="display:<?php echo get_option( 'ab_appearance_show_calendar' ) == 1 ? ' none' : ' inline-block' ?>">
68
- <?php for ( $i = 48600; $i <= 56700; $i += 900 ): ?>
69
- <button class="ab-available-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
70
- <span class="ladda-label">
71
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( $i ) ?>
72
- </span>
73
- </button>
74
- <?php endfor ?>
75
- </div>
76
- <div class="ab-column col6" style="display: <?php echo get_option( 'ab_appearance_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
77
- <?php for ( $i = 57600; $i <= 63900; $i += 900 ): ?>
78
- <button class="ab-available-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
79
- <span class="ladda-label">
80
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( $i ) ?>
81
- </span>
82
- </button>
83
- <?php endfor ?>
84
- <button class="ab-available-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+2 days' ) ) ?></button>
85
- <button class="ab-available-hour ladda-button ab-last-child">
86
- <span class="ladda-label">
87
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( 28800 ) ?>
88
- </span>
89
- </button>
90
- </div>
91
- <div class="ab-column col7" style="display:<?php echo get_option( 'ab_appearance_show_calendar' ) == 1 ? ' none' : ' inline-block' ?>">
92
- <?php for ( $i = 29700; $i <= 37800; $i += 900 ): ?>
93
- <button class="ab-available-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
94
- <span class="ladda-label">
95
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( $i ) ?>
96
- </span>
97
- </button>
98
- <?php endfor ?>
99
- </div>
100
- </div>
101
-
102
- <div class="ab-time-screen ab-day-one-column" style="display: <?php echo get_option( 'ab_appearance_show_day_one_column' ) == 1 ? ' block' : 'none' ?>">
103
- <div style="margin-right: 40px;" class="ab-input-wrap ab-slot-calendar">
104
- <span class="ab-date-wrap">
105
- <input style="display: none" class="ab-selected-date ab-formElement" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
106
- </span>
107
- </div>
108
- <div class="ab-column col1">
109
- <button class="ab-available-day ab-first-child"><?php echo date_i18n( 'D, M d' ) ?></button>
110
- <?php for ( $i = 28800; $i <= 36000; $i += 900 ): ?>
111
- <button class="ab-available-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
112
- <span class="ladda-label">
113
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( $i ) ?>
114
- </span>
115
- </button>
116
- <?php endfor ?>
117
- </div>
118
- <div class="ab-column col2">
119
- <button class="ab-available-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+1 day' ) ) ?></button>
120
- <?php for ( $i = 28800; $i <= 36000; $i += 900 ): ?>
121
- <button class="ab-available-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
122
- <span class="ladda-label">
123
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( $i ) ?>
124
- </span>
125
- </button>
126
- <?php endfor ?>
127
- </div>
128
- <div class="ab-column col3">
129
- <button class="ab-available-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+2 days' ) ) ?></button>
130
- <?php for ( $i = 28800; $i <= 36000; $i += 900 ): ?>
131
- <button class="ab-available-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
132
- <span class="ladda-label">
133
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( $i ) ?>
134
- </span>
135
- </button>
136
- <?php endfor ?>
137
- </div>
138
- <div class="ab-column col4">
139
- <button class="ab-available-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+3 days' ) ) ?></button>
140
- <?php for ( $i = 28800; $i <= 36000; $i += 900 ): ?>
141
- <button class="ab-available-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 AB_DateTimeUtils::formatTime( $i ) ?>
144
- </span>
145
- </button>
146
- <?php endfor ?>
147
- </div>
148
- <div class="ab-column col5" style="display: <?php echo get_option( 'ab_appearance_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
149
- <button class="ab-available-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+4 days' ) ) ?></button>
150
- <?php for ( $i = 28800; $i <= 36000; $i += 900 ): ?>
151
- <button class="ab-available-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
152
- <span class="ladda-label">
153
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( $i ) ?>
154
- </span>
155
- </button>
156
- <?php endfor ?>
157
- </div>
158
- <div class="ab-column col6" style="display: <?php echo get_option( 'ab_appearance_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
159
- <button class="ab-available-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+5 days' ) ) ?></button>
160
- <?php for ( $i = 28800; $i <= 36000; $i += 900 ): ?>
161
- <button class="ab-available-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
162
- <span class="ladda-label">
163
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( $i ) ?>
164
- </span>
165
- </button>
166
- <?php endfor ?>
167
- </div>
168
- <div class="ab-column col7" style="display: <?php echo get_option( 'ab_appearance_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
169
- <button class="ab-available-day ab-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+6 days' ) ) ?></button>
170
- <?php for ( $i = 28800; $i <= 36000; $i += 900 ): ?>
171
- <button class="ab-available-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'ab_appearance_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
172
- <span class="ladda-label">
173
- <i class="ab-hour-icon"><span></span></i><?php echo AB_DateTimeUtils::formatTime( $i ) ?>
174
- </span>
175
- </button>
176
- <?php endfor ?>
177
- </div>
178
- </div>
179
- </div>
180
- </div>
181
- <div class="ab-row-fluid ab-nav-steps last-row ab-clear">
182
- <button class="ab-time-next ab-btn ab-right ladda-button">
183
- <span class="ab_label">&gt;</span>
184
- </button>
185
- <button class="ab-time-prev ab-btn ab-right ladda-button">
186
- <span class="ab_label">&lt;</span>
187
- </button>
188
- <button class="ab-left ab-to-first-step ab-btn ladda-button">
189
- <span><?php _e( 'Back', 'bookly' ) ?></span>
190
- </button>
191
- </div>
192
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appearance/templates/_3_details.php DELETED
@@ -1,42 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="ab-booking-form">
3
- <!-- Progress Tracker-->
4
- <?php $step = 3; include '_progress_tracker.php'; ?>
5
-
6
- <div class="ab-row-fluid">
7
- <span data-inputclass="input-xxlarge" data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 3, 'login' => false ), false ) ) ?>" data-placement="bottom" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_third_step' ) ) ?>" class="ab-text-info-third-preview ab-row-fluid ab_editable" id="ab-text-info-third" data-type="textarea"><?php echo esc_html( get_option( 'ab_appearance_text_info_third_step' ) ) ?></span>
8
- </div>
9
- <div class="ab-row-fluid">
10
- <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' => 3, 'login' => true ), false ) ) ?>" data-placement="bottom" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_third_step_guest' ) ) ?>" class="ab-text-info-third-preview ab-row-fluid ab_editable" id="ab-text-info-third-guest" data-type="textarea"><?php echo esc_html( get_option( 'ab_appearance_text_info_third_step_guest' ) ) ?></span>
11
- </div>
12
- <form class="ab-third-step">
13
- <div class="ab-row-fluid ab-col-phone" style="height: 55px; overflow: visible;">
14
- <div class="ab-formGroup ab-left">
15
- <label data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_name' ) ) ?>" class="ab-formLabel text_name_label ab_editable" id="ab-text-label-name" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_label_name' ) ) ?></label>
16
- <div class="ab-formField">
17
- <input class="ab-formElement" type="text" value="" maxlength="60" />
18
- </div>
19
- </div>
20
- <div class="ab-formGroup ab-left">
21
- <label data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_phone' ) ) ?>" class="ab-formLabel text_phone_label ab_editable" id="ab-text-label-phone" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_label_phone' ) ) ?></label>
22
- <div class="ab-formField">
23
- <input class="ab-formElement ab-user-phone" maxlength="30" type="tel" value="" />
24
- </div>
25
- </div>
26
- <div class="ab-formGroup ab-left">
27
- <label data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_label_email' ) ) ?>" class="ab-formLabel text_email_label ab_editable" id="ab-text-label-email" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_label_email' ) ) ?></label>
28
- <div class="ab-formField" style="margin-right: 0">
29
- <input class="ab-formElement" maxlength="40" type="text" value="" />
30
- </div>
31
- </div>
32
- </div>
33
- </form>
34
- <div class="ab-row-fluid last-row ab-nav-steps ab-clear">
35
- <button class="ab-left ab-to-second-step ab-btn ladda-button">
36
- <span><?php _e( 'Back', 'bookly' ) ?></span>
37
- </button>
38
- <button class="ab-right ab-to-fourth-step ab-btn ladda-button">
39
- <span><?php _e( 'Next', 'bookly' ) ?></span>
40
- </button>
41
- </div>
42
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appearance/templates/_3_time.php ADDED
@@ -0,0 +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>
backend/modules/appearance/templates/_4_cart.php ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/_4_payment.php DELETED
@@ -1,60 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="ab-booking-form">
3
- <!-- Progress Tracker-->
4
- <?php $step = 4; include '_progress_tracker.php'; ?>
5
- <!-- payment -->
6
- <div class="ab-payment">
7
- <!-- Coupons -->
8
- <div class="ab-row-fluid">
9
- <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' => 4 ), false ) ) ?>" class="ab-text-info-coupon-preview ab-row-fluid ab_editable" id="ab-text-info-coupon" data-type="textarea"><?php echo esc_html( get_option( 'ab_appearance_text_info_coupon' ) ) ?></span>
10
- </div>
11
-
12
- <div style="margin-bottom: 15px">
13
- <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>
14
- <div class="ab-inline-block">
15
- <input class="ab-user-coupon ab-inline-block" maxlength="40" type="text" value="" />
16
- <button class="ab-btn ladda-button orange ab-inline-block"><?php _e( 'Apply', 'bookly' ) ?></button>
17
- </div>
18
- </div>
19
- <div class="ab-clear"></div>
20
-
21
- <div class="ab-row-fluid">
22
- <span data-inputclass="input-xxlarge" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_fourth_step' ) ) ?>" data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 4 ), false ) ) ?>" class="ab-text-info-fourth-preview ab-row-fluid ab_editable" id="ab-text-info-fourth" data-type="textarea"><?php echo esc_html( get_option( 'ab_appearance_text_info_fourth_step' ) ) ?></span>
23
- </div>
24
-
25
- <!-- label -->
26
- <div class="ab-row-fluid">
27
- <label>
28
- <input type="radio" name="payment" class="ab-local-payment" checked="checked" value="local"/>
29
- <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>
30
- </label>
31
- </div>
32
- <div class="ab-row-fluid">
33
- <label>
34
- <input type="radio" name="payment" class="ab-paypal-payment" value="paypal" />
35
- <?php _e( 'I will pay now with PayPal', 'bookly' ) ?>
36
- <img src="<?php echo plugins_url( 'frontend/resources/images/paypal.png', AB_PATH . '/main.php' ) ?>" style="margin-left: 10px;" alt="paypal" />
37
- </label>
38
- </div>
39
- <div class="ab-row-fluid">
40
- <label>
41
- <input type="radio" name="payment" class="ab-stripe-payment" value="stripe"/>
42
- <?php _e( 'I will pay now with Credit Card', 'bookly' ) ?>
43
- <img style="margin-left: 10px;" src="<?php echo plugins_url( 'frontend/resources/images/cards.png', AB_PATH . '/main.php' ) ?>" alt="cards" />
44
- </label>
45
- <form class="ab-stripe ab-clearBottom" style="margin-top:15px;display: none;">
46
- <?php include "_card_payment.php" ?>
47
- </form>
48
- </div>
49
-
50
- <!-- buttons -->
51
- <div class="ab-local-pay-button ab-row-fluid ab-nav-steps last-row">
52
- <button class="ab-left ab-to-third-step ab-btn ladda-button">
53
- <span><?php _e( 'Back', 'bookly' ) ?></span>
54
- </button>
55
- <button class="ab-right ab-final-step ab-btn ladda-button">
56
- <span><?php _e( 'Next', 'bookly' ) ?></span>
57
- </button>
58
- </div>
59
- </div>
60
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appearance/templates/_5_details.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/_5_done.php DELETED
@@ -1,8 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="ab-booking-form">
3
- <!-- Progress Tracker-->
4
- <?php $step = 5; include '_progress_tracker.php'; ?>
5
- <div class="ab-row-fluid">
6
- <span data-inputclass="input-xxlarge" data-link-class="ab-text-info-fifth" class="ab-text-info-fifth-preview ab_editable" data-notes="<?php echo esc_attr( $this->render( '_codes', array( 'step' => 5 ), false ) ) ?>" data-placement="bottom" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_fifth_step' ) ) ?>" id="ab-text-info-fifth" data-type="textarea"><?php echo nl2br( esc_html( get_option( 'ab_appearance_text_info_fifth_step' ) ) ) ?></span>
7
- </div>
8
- </div>
 
 
 
 
 
 
 
 
backend/modules/appearance/templates/_6_payment.php ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 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="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/_card_payment.php CHANGED
@@ -1,34 +1,38 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="ab-row-fluid">
3
- <div class="ab-formGroup ab-left">
4
- <label class="ab-formLabel"><?php _e( 'Credit Card Number', 'bookly' ) ?></label>
5
- <div class="ab-formField">
6
- <input class="ab-formElement ab-full-name" type="text" name="ab_card_number">
 
 
7
  </div>
8
  </div>
9
- <div class="ab-formGroup ab-left" style="width: auto;">
10
- <label class="ab-formLabel"><?php _e( 'Expiration Date', 'bookly' ) ?></label>
11
- <div class="ab-formField">
12
- <select class="ab-formElement ab-full-name" style="width: 40px;float: left;" name="ab_card_month">
13
- <?php for ( $i = 1; $i <= 12; ++ $i ): ?>
 
 
14
  <option value="<?php echo $i ?>"><?php printf( '%02d', $i ) ?></option>
15
  <?php endfor ?>
16
  </select>
17
- <select class="ab-formElement ab-full-name" style="width: 55px;float: left; margin-left: 10px;" name="ab_card_year">
18
- <?php for ( $i = date( 'Y' ); $i <= date( 'Y' ) + 10; ++ $i ): ?>
19
  <option value="<?php echo $i ?>"><?php echo $i ?></option>
20
  <?php endfor ?>
21
  </select>
22
  </div>
23
  </div>
24
  </div>
25
- <div class="ab-row-fluid">
26
- <div class="ab-formGroup ab-left">
27
- <label class="ab-formLabel"><?php _e( 'Card Security Code', 'bookly' ) ?></label>
28
- <div class="ab-formField">
29
- <input class="ab-formElement ab-full-name" style="width: 50px;float: left;" type="text" name="ab_card_code" />
 
 
30
  </div>
31
  </div>
32
- <div class="ab-clear"></div>
33
- <div class="ab-error ab-bold ab-card-error"></div>
34
  </div>
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>
backend/modules/appearance/templates/_codes.php CHANGED
@@ -1,16 +1,25 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <b>[[CATEGORY_NAME]]</b> - <?php _e( 'name of category', 'bookly' ) ?><br />
3
- <?php if ( $step == 3 && $login ) : ?>
4
- <b>[[LOGIN_FORM]]</b> - <?php _e( 'login form', 'bookly' ) ?><br />
5
- <?php endif ?>
6
- <b>[[NUMBER_OF_PERSONS]]</b> - <?php _e( 'number of persons', 'bookly' ) ?><br />
7
- <?php if ( $step > 2 ) : ?>
8
- <b>[[SERVICE_DATE]]</b> - <?php _e( 'date of service', 'bookly' ) ?><br />
9
- <?php endif ?>
10
- <b>[[SERVICE_NAME]]</b> - <?php _e( 'name of service', 'bookly' ) ?><br />
11
- <b>[[SERVICE_PRICE]]</b> - <?php _e( 'price of service', 'bookly' ) ?><br />
12
- <?php if ( $step > 2 ) : ?>
13
- <b>[[SERVICE_TIME]]</b> - <?php _e( 'time of service', 'bookly' ) ?><br />
14
- <?php endif ?>
15
- <b>[[STAFF_NAME]]</b> - <?php _e( 'name of staff', 'bookly' ) ?><br />
16
- <b>[[TOTAL_PRICE]]</b> - <?php _e( 'total price of booking', 'bookly' ) ?>
 
 
 
 
 
 
 
 
 
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 );
backend/modules/appearance/templates/_progress_tracker.php CHANGED
@@ -1,25 +1,35 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="ab-progress-tracker">
3
- <ul class="ab-progress-bar nav-3">
4
- <li class="ab-step-tabs ab-first active">
5
- <a href="javascript:void(0)">1. <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_step_service' ) ) ?>" data-link-class="text_step_1" 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></a>
6
- <div class="step"></div>
7
- </li>
8
- <li class="ab-step-tabs<?php if ($step >= 2): ?> active<?php endif ?>">
9
- <a href="javascript:void(0)">2. <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_step_time' ) ) ?>" data-link-class="text_step_2" class="text_time text_step_2 ab_editable" id="ab-text-step-time" data-type="text"><?php echo esc_html(get_option( 'ab_appearance_text_step_time' )) ?></span></a>
10
- <div class="step"></div>
11
- </li>
12
- <li class="ab-step-tabs<?php if ($step >= 3): ?> active<?php endif ?>">
13
- <a href="javascript:void(0)">3. <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_step_details' ) ) ?>" data-link-class="text_step_3" class="text_details text_step_3 ab_editable" id="ab-text-step-details" data-type="text"><?php echo esc_html( get_option( 'ab_appearance_text_step_details' ) ) ?></span></a>
14
- <div class="step"></div>
15
- </li>
16
- <li class="ab-step-tabs<?php if ($step >= 4): ?> active<?php endif ?>">
17
- <a href="javascript:void(0)">4. <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_step_payment' ) ) ?>" data-link-class="text_step_4" 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></a>
18
- <div class="step"></div>
19
- </li>
20
- <li class="ab-step-tabs ab-last<?php if ($step >= 5): ?> active<?php endif ?>">
21
- <a href="javascript:void(0)">5. <span data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_step_done' ) ) ?>" data-link-class="text_step_5" 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></a>
22
- <div class="step"></div>
23
- </li>
24
- </ul>
25
- </div>
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ $i = 1;
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>
backend/modules/appearance/templates/index.php CHANGED
@@ -1,90 +1,126 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="panel panel-default">
3
- <div class="panel-heading">
4
- <h3 class="panel-title"><?php _e( 'Appearance', 'bookly' ) ?></h3>
5
- </div>
6
- <div class="panel-body">
7
- <?php AB_Utils::notice( __( 'Settings saved.', 'bookly' ), 'notice-success', false ) ?>
8
- <input type=text class="wp-color-picker appearance-color-picker" name=color
9
- value="<?php echo esc_attr( get_option( 'ab_appearance_color' ) ) ?>"
10
- data-selected="<?php echo esc_attr( get_option( 'ab_appearance_color' ) ) ?>" />
11
- <div id="ab-appearance">
12
- <form method=post id=common_settings>
13
- <div class="row">
14
- <div class="col-md-3">
15
- <div id=main_form class="checkbox">
16
- <label>
17
- <input id=ab-progress-tracker-checkbox name=ab-progress-tracker-checkbox <?php checked( get_option( 'ab_appearance_show_progress_tracker' ) ) ?> type=checkbox />
18
- <b><?php _e( 'Show form progress tracker', 'bookly' ) ?></b>
19
- </label>
20
  </div>
21
- </div>
22
- <div class="col-md-3">
23
- <div class="checkbox">
24
- <label>
25
- <input id="ab-show-calendar-checkbox" name="ab-show-calendar-checkbox" <?php checked ( get_option( 'ab_appearance_show_calendar' ) ) ?> type="checkbox" />
26
- <b><?php _e( 'Show calendar', 'bookly' ) ?></b>
27
- </label>
28
  </div>
29
  </div>
30
- <div class="col-md-3">
31
- <div class="checkbox">
32
- <label>
33
- <input id="ab-blocked-timeslots-checkbox" name="ab-blocked-timeslots-checkbox" <?php checked( get_option( 'ab_appearance_show_blocked_timeslots' ) ) ?> type="checkbox" />
34
- <b><?php _e( 'Show blocked timeslots', 'bookly' ) ?></b>
35
- </label>
 
 
 
 
 
 
 
 
 
 
36
  </div>
37
- </div>
38
- <div class="col-md-3">
39
- <div class="checkbox">
40
- <label>
41
- <input id="ab-day-one-column-checkbox" name="ab-day-one-column-checkbox" <?php checked( get_option( 'ab_appearance_show_day_one_column' ) ) ?> type="checkbox" />
42
- <b><?php _e( 'Show each day in one column', 'bookly' ) ?></b>
43
- </label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  </div>
45
  </div>
46
- </div>
47
- </form>
48
- <!-- Tabs -->
49
- <div class=tabbable style="margin-top: 20px;">
50
- <ul class="nav nav-tabs ab-nav-tabs">
51
- <?php foreach ( $steps as $step_id => $step_name ): ?>
52
- <li class="ab-step-tab-<?php echo $step_id ?> ab-step-tabs<?php if ( $step_id == 1 ): ?> active<?php endif ?>" data-step-id="<?php echo $step_id ?>">
53
- <a href="#" data-toggle=tab><?php echo $step_id ?>. <span class="text_step_<?php echo $step_id ?>" ><?php echo esc_html( $step_name ) ?></span></a>
54
- </li>
55
- <?php endforeach ?>
56
- </ul>
57
- <!-- Tabs-Content -->
58
- <div class=tab-content>
59
- <?php foreach ( $steps as $step_id => $step_name ) : ?>
60
- <div class="tab-pane-<?php echo $step_id ?><?php if ( $step_id == 1 ): ?> active<?php endif ?>" data-step-id="<?php echo $step_id ?>"<?php if ( $step_id != 1 ): ?> style="display: none"<?php endif ?>>
61
- <?php
62
- // Render unique data per step
63
- switch ( $step_id ) {
64
- case 1: // Service
65
- include '_1_service.php'; break;
66
- case 2: // Time
67
- include '_2_time.php'; break;
68
- case 3: // Details
69
- include '_3_details.php'; break;
70
- case 4: // Payment
71
- include '_4_payment.php'; break;
72
- case 5: // Done
73
- include '_5_done.php'; break;
74
- }
75
- ?>
76
  </div>
77
- <?php endforeach ?>
78
- </div>
79
- <div class="text-right">
80
- <?php _e( 'Click on the underlined text to edit.', 'bookly' ) ?>
81
  </div>
82
- <div class="clear"></div>
 
 
 
83
  </div>
84
  </div>
85
  </div>
86
- <div class="panel-footer">
87
- <?php AB_Utils::submitButton( 'ajax-send-appearance' ) ?>
88
- <?php AB_Utils::resetButton() ?>
89
- </div>
90
  </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
+ </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>
backend/modules/appointments/AB_AppointmentsController.php DELETED
@@ -1,276 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_AppointmentsController
5
- */
6
- class AB_AppointmentsController extends AB_Controller {
7
-
8
- public function index()
9
- {
10
- /** @var WP_Locale $wp_locale */
11
- global $wp_locale;
12
-
13
- $this->enqueueStyles( array(
14
- 'frontend' => array(
15
- 'css/intlTelInput.css',
16
- ),
17
- 'backend' => array(
18
- 'css/jquery-ui-theme/jquery-ui.min.css',
19
- 'css/bookly.main-backend.css',
20
- 'bootstrap/css/bootstrap.min.css',
21
- 'css/daterangepicker.css',
22
- 'css/chosen.min.css'
23
- )
24
- ) );
25
-
26
- $this->enqueueScripts( array(
27
- 'backend' => array(
28
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
29
- 'js/angular.min.js',
30
- 'js/angular-sanitize.min.js' => array( 'ab-angular.min.js' ),
31
- 'js/angular-ui-utils-0.2.1.min.js' => array( 'ab-angular.min.js' ),
32
- 'js/ng-new_customer_dialog.js' => array( 'ab-intlTelInput.min.js', 'ab-angular.min.js' ),
33
- 'js/angular-ui-date-0.0.8.js' => array( 'ab-angular.min.js' ),
34
- 'js/moment.min.js',
35
- 'js/daterangepicker.js' => array( 'jquery' ),
36
- 'js/chosen.jquery.min.js' => array( 'jquery' ),
37
- 'js/ng-edit_appointment_dialog.js' => array( 'ab-angular-ui-date-0.0.8.js', 'jquery-ui-datepicker' ),
38
- ),
39
- 'frontend' => array(
40
- 'js/intlTelInput.min.js',
41
- ),
42
- 'module' => array(
43
- 'js/ng-app.js' => array( 'jquery', 'ab-angular.min.js', 'ab-angular-ui-utils-0.2.1.min.js' ),
44
- ),
45
- ) );
46
-
47
- wp_localize_script( 'ab-ng-app.js', 'BooklyL10n', array(
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
- 'editAppointment' => __( 'Edit appointment', 'bookly' ),
60
- 'newAppointment' => __( 'New appointment', 'bookly' ),
61
- 'longMonths' => array_values( $wp_locale->month ),
62
- 'shortMonths' => array_values( $wp_locale->month_abbrev ),
63
- 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
64
- 'dpDateFormat' => AB_DateTimeUtils::convertFormat( 'date', AB_DateTimeUtils::FORMAT_JQUERY_DATEPICKER ),
65
- 'mjsDateFormat' => AB_DateTimeUtils::convertFormat( 'date', AB_DateTimeUtils::FORMAT_MOMENT_JS ),
66
- 'startOfWeek' => (int) get_option( 'start_of_week' ),
67
- 'country' => get_option( 'ab_settings_phone_default_country' ),
68
- 'intlTelInput_utils' => plugins_url( 'intlTelInput.utils.js', AB_PATH . '/frontend/resources/js/intlTelInput.utils.js' ),
69
- 'please_select_at_least_one_row' => __( 'Please select at least one appointment.', 'bookly' ),
70
- ));
71
-
72
- $this->render( 'index' );
73
- }
74
-
75
- /**
76
- * Get list of appointments.
77
- */
78
- public function executeGetAppointments()
79
- {
80
- $response = array(
81
- 'appointments' => array(),
82
- 'total' => 0,
83
- 'pages' => 0,
84
- 'active_page' => 0
85
- );
86
-
87
- $page = intval( $this->getParameter( 'page' ) );
88
- $sort = in_array( $this->getParameter( 'sort' ), array( 'staff_name', 'service_title', 'start_date', 'price' ) )
89
- ? $this->getParameter( 'sort' ) : 'start_date';
90
- $order = in_array( $this->getParameter( 'order' ), array( 'asc', 'desc' ) ) ? $this->getParameter( 'order' ) : 'asc';
91
-
92
- $start_date = new DateTime( $this->getParameter( 'date_start' ) );
93
- $start_date = $start_date->format( 'Y-m-d H:i:s' );
94
- $end_date = new DateTime( $this->getParameter( 'date_end' ) );
95
- $end_date = $end_date->modify( '+1 day' )->format( 'Y-m-d H:i:s' );
96
-
97
- $items_per_page = 20;
98
- $total = AB_Appointment::query()->whereBetween( 'start_date', $start_date, $end_date )->count();
99
- $pages = ceil( $total / $items_per_page );
100
- if ( $page < 1 || $page > $pages ) {
101
- $page = 1;
102
- }
103
-
104
- if ( $total ) {
105
- $query = AB_CustomerAppointment::query( 'ca' )
106
- ->select( 'ca.id,
107
- ca.number_of_persons,
108
- ca.coupon_discount,
109
- ca.coupon_deduction,
110
- ca.appointment_id,
111
- a.start_date,
112
- a.end_date,
113
- a.staff_id,
114
- st.full_name AS staff_name,
115
- s.title AS service_title,
116
- s.duration AS service_duration,
117
- c.name AS customer_name,
118
- ss.price' )
119
- ->leftJoin( 'AB_Appointment', 'a', 'a.id = ca.appointment_id' )
120
- ->leftJoin( 'AB_Service', 's', 's.id = a.service_id' )
121
- ->leftJoin( 'AB_Customer', 'c', 'c.id = ca.customer_id' )
122
- ->leftJoin( 'AB_Staff', 'st', 'st.id = a.staff_id' )
123
- ->leftJoin( 'AB_StaffService', 'ss', 'ss.staff_id = st.id AND ss.service_id = s.id')
124
- ->whereBetween( 'a.start_date', $start_date, $end_date )
125
- ->sortBy( $sort )
126
- ->order( $order );
127
-
128
- // LIMIT.
129
- $start = ( $page - 1 ) * $items_per_page;
130
- $query->offset( $start )->limit( $items_per_page );
131
-
132
- $rows = $query->fetchArray();
133
- foreach ( $rows as &$row ) {
134
- $row['price'] *= $row['number_of_persons'];
135
- $row['price'] = AB_Utils::formatPrice( $row['price'] );
136
- $row['start_date_f'] = AB_DateTimeUtils::formatDateTime( $row['start_date'] );
137
- $row['service_duration'] = AB_DateTimeUtils::secondsToInterval( $row['service_duration'] );
138
- }
139
-
140
- // Populate response.
141
- $response['appointments'] = $rows;
142
- $response['total'] = $total;
143
- $response['pages'] = $pages;
144
- $response['active_page'] = $page;
145
- }
146
-
147
- wp_send_json_success( $response );
148
- }
149
-
150
- /**
151
- * Delete customer appointment.
152
- */
153
- public function executeDeleteCustomerAppointment()
154
- {
155
- if ( $this->hasParameter( 'ids' ) ) {
156
- foreach ( $this->getParameter( 'ids' ) as $id ) {
157
- $customer_appointment = new AB_CustomerAppointment();
158
- $customer_appointment->load( $id );
159
-
160
- $appointment = new AB_Appointment();
161
- $appointment->load( $customer_appointment->get( 'appointment_id' ) );
162
-
163
- $customer_appointment->delete();
164
-
165
- // Delete appointment, if there aren't customers.
166
- $count = AB_CustomerAppointment::query()->where( 'appointment_id', $customer_appointment->get( 'appointment_id' ) )->count();
167
-
168
- if ( ! $count ) {
169
- $appointment->delete();
170
- } else {
171
- $appointment->handleGoogleCalendar();
172
- }
173
- }
174
- }
175
- wp_send_json_success();
176
- }
177
-
178
- /**
179
- * Export Appointment to CSV
180
- */
181
- public function executeExportToCSV()
182
- {
183
- $start_date = new DateTime( $this->getParameter( 'date_start' ) );
184
- $start_date = $start_date->format( 'Y-m-d H:i:s' );
185
- $end_date = new DateTime( $this->getParameter( 'date_end' ) );
186
- $end_date = $end_date->modify( '+1 day' )->format( 'Y-m-d H:i:s' );
187
- $delimiter = $this->getParameter( 'delimiter', ',' );
188
-
189
- header( 'Content-Type: text/csv; charset=utf-8' );
190
- header( 'Content-Disposition: attachment; filename=Appointments.csv' );
191
-
192
- $header = array(
193
- __( 'Booking Time', 'bookly' ),
194
- __( 'Staff Member', 'bookly' ),
195
- __( 'Service', 'bookly' ),
196
- __( 'Duration', 'bookly' ),
197
- __( 'Price', 'bookly' ),
198
- __( 'Customer', 'bookly' ),
199
- __( 'Phone', 'bookly' ),
200
- __( 'Email', 'bookly' ),
201
- );
202
-
203
- $custom_fields = array();
204
- $fields_data = json_decode( get_option( 'ab_custom_fields' ) );
205
- foreach ( $fields_data as $field_data ) {
206
- $custom_fields[$field_data->id] = '';
207
- $header[] = $field_data->label;
208
- }
209
-
210
- $output = fopen( 'php://output', 'w' );
211
- fwrite( $output, pack( 'CCC', 0xef, 0xbb, 0xbf ) );
212
- fputcsv( $output, $header, $delimiter );
213
- $rows = AB_CustomerAppointment::query()
214
- ->select( 'r.id,
215
- r.number_of_persons,
216
- r.coupon_discount,
217
- r.coupon_deduction,
218
- st.full_name AS staff_name,
219
- s.title AS service_title,
220
- s.duration AS service_duration,
221
- c.name AS customer_name,
222
- c.phone AS customer_phone,
223
- c.email AS customer_email,
224
- ss.price,
225
- a.start_date' )
226
- ->leftJoin( 'AB_Appointment', 'a', 'a.id = r.appointment_id' )
227
- ->leftJoin( 'AB_Service', 's', 's.id = a.service_id' )
228
- ->leftJoin( 'AB_Staff', 'st', 'st.id = a.staff_id' )
229
- ->leftJoin( 'AB_Customer', 'c', 'c.id = r.customer_id' )
230
- ->leftJoin( 'AB_StaffService', 'ss', 'ss.staff_id = st.id AND ss.service_id = s.id' )
231
- ->whereBetween( 'a.start_date', $start_date, $end_date )
232
- ->sortBy( 'a.start_date' )
233
- ->order( AB_Query::ORDER_DESCENDING )
234
- ->fetchArray();
235
-
236
- foreach( $rows as $row ) {
237
- $row['price'] *= $row['number_of_persons'];
238
-
239
- $row_data = array(
240
- $row['start_date'],
241
- $row['staff_name'],
242
- $row['service_title'],
243
- AB_DateTimeUtils::secondsToInterval( $row['service_duration'] ),
244
- AB_Utils::formatPrice( $row['price'] ),
245
- $row['customer_name'],
246
- $row['customer_phone'],
247
- $row['customer_email'],
248
- );
249
-
250
- $customer_appointment = new AB_CustomerAppointment();
251
- $customer_appointment->load( $row['id'] );
252
- foreach ( $customer_appointment->getCustomFields() as $custom_field ) {
253
- $custom_fields[$custom_field['id']] = $custom_field['value'];
254
- }
255
-
256
- fputcsv( $output, array_merge( $row_data, $custom_fields ), $delimiter );
257
-
258
- $custom_fields = array_map( function () { return ''; }, $custom_fields );
259
- }
260
- fclose( $output );
261
-
262
- exit();
263
- }
264
-
265
- /**
266
- * Override parent method to add 'wp_ajax_ab_' prefix
267
- * so current 'execute*' methods look nicer.
268
- *
269
- * @param string $prefix
270
- */
271
- protected function registerWpActions( $prefix = '' )
272
- {
273
- parent::registerWpActions( 'wp_ajax_ab_' );
274
- }
275
-
276
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appointments/Controller.php ADDED
@@ -0,0 +1,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
+ 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
+ }
backend/modules/appointments/resources/js/appointments.js ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(function($) {
2
+
3
+ var
4
+ $appointments_list = $('#bookly-appointments-list'),
5
+ $check_all_button = $('#bookly-check-all'),
6
+ $id_filter = $('#bookly-filter-id'),
7
+ $date_filter = $('#bookly-filter-date'),
8
+ $staff_filter = $('#bookly-filter-staff'),
9
+ $customer_filter = $('#bookly-filter-customer'),
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
+ */
23
+ var columns = [
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>';
38
+ });
39
+ extras += '</ul>';
40
+ return data + extras;
41
+ }
42
+ else {
43
+ return data;
44
+ }
45
+ }
46
+ },
47
+ { data: 'service.duration', responsivePriority: 2 },
48
+ { data: 'status', responsivePriority: 2 },
49
+ {
50
+ data: 'payment',
51
+ responsivePriority: 2,
52
+ render: function ( data, type, row, meta ) {
53
+ return '<a href="#bookly-payment-details-modal" data-toggle="modal" data-payment_id="' + row.payment_id + '">' + data + '</a>';
54
+ }
55
+ }
56
+ ];
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
+ });
63
+ });
64
+ var dt = $appointments_list.DataTable({
65
+ order: [[ 1, 'desc' ]],
66
+ info: false,
67
+ paging: false,
68
+ searching: false,
69
+ processing: true,
70
+ responsive: true,
71
+ serverSide: true,
72
+ ajax: {
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'),
80
+ staff : $staff_filter.val(),
81
+ customer: $customer_filter.val(),
82
+ service : $service_filter.val(),
83
+ status : $status_filter.val()
84
+ }
85
+ }, d);
86
+ }
87
+ },
88
+ columns: columns.concat([
89
+ {
90
+ responsivePriority: 1,
91
+ orderable: false,
92
+ render: function ( data, type, row, meta ) {
93
+ return '<button type="button" class="btn btn-default"><i class="glyphicon glyphicon-edit"></i> ' + BooklyL10n.edit + '</a>';
94
+ }
95
+ },
96
+ {
97
+ responsivePriority: 1,
98
+ orderable: false,
99
+ render: function ( data, type, row, meta ) {
100
+ return '<input type="checkbox" value="' + row.ca_id + '" />';
101
+ }
102
+ }
103
+ ]),
104
+ language: {
105
+ zeroRecords: BooklyL10n.zeroRecords,
106
+ processing: BooklyL10n.processing
107
+ }
108
+ });
109
+
110
+ /**
111
+ * Add appointment.
112
+ */
113
+ $add_button.on('click', function () {
114
+ showAppointmentDialog(
115
+ null,
116
+ null,
117
+ moment(),
118
+ function(event) {
119
+ dt.ajax.reload();
120
+ }
121
+ )
122
+ });
123
+
124
+ /**
125
+ * Edit appointment.
126
+ */
127
+ $appointments_list.on('click', 'button', function (e) {
128
+ e.preventDefault();
129
+ var data = dt.row($(this).closest('td')).data();
130
+ showAppointmentDialog(
131
+ data.id,
132
+ null,
133
+ null,
134
+ function(event) {
135
+ dt.ajax.reload();
136
+ }
137
+ )
138
+ });
139
+
140
+ /**
141
+ * Export.
142
+ */
143
+ $export_button.on('click', function () {
144
+ var columns = [];
145
+ $export_dialog.find('input:checked').each(function () {
146
+ columns.push(this.value);
147
+ });
148
+ var config = {
149
+ autoPrint: false,
150
+ fieldSeparator: $('#bookly-csv-delimiter').val(),
151
+ exportOptions: {
152
+ columns: columns
153
+ },
154
+ filename: 'Appointments'
155
+ };
156
+ $.fn.dataTable.ext.buttons.csvHtml5.action(null, dt, null, $.extend({}, $.fn.dataTable.ext.buttons.csvHtml5, config));
157
+ });
158
+
159
+ $('.bookly-limitation').on('click', function () {
160
+ booklyAlert({error: [BooklyL10n.limitations]});
161
+ });
162
+
163
+ /**
164
+ * Select all appointments.
165
+ */
166
+ $check_all_button.on('change', function () {
167
+ $appointments_list.find('tbody input:checkbox').prop('checked', this.checked);
168
+ });
169
+
170
+ /**
171
+ * On appointment select.
172
+ */
173
+ $appointments_list.on('change', 'tbody input:checkbox', function () {
174
+ $check_all_button.prop('checked', $appointments_list.find('tbody input:not(:checked)').length == 0);
175
+ });
176
+
177
+ /**
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
+ /**
212
+ * Init date range picker.
213
+ */
214
+ moment.locale('en', {
215
+ months : BooklyL10n.calendar.longMonths,
216
+ monthsShort : BooklyL10n.calendar.shortMonths,
217
+ weekdays : BooklyL10n.calendar.longDays,
218
+ weekdaysShort: BooklyL10n.calendar.shortDays,
219
+ weekdaysMin : BooklyL10n.calendar.shortDays
220
+ });
221
+
222
+ var picker_ranges = {};
223
+ picker_ranges[BooklyL10n.yesterday] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
224
+ picker_ranges[BooklyL10n.today] = [moment(), moment()];
225
+ picker_ranges[BooklyL10n.tomorrow] = [moment().add(1, 'days'), moment().add(1, 'days')];
226
+ picker_ranges[BooklyL10n.last_7] = [moment().subtract(7, 'days'), moment()];
227
+ picker_ranges[BooklyL10n.last_30] = [moment().subtract(30, 'days'), moment()];
228
+ picker_ranges[BooklyL10n.this_month] = [moment().startOf('month'), moment().endOf('month')];
229
+ picker_ranges[BooklyL10n.next_month] = [moment().add(1, 'month').startOf('month'), moment().add(1, 'month').endOf('month')];
230
+
231
+ $date_filter.daterangepicker(
232
+ {
233
+ parentEl: $date_filter.parent(),
234
+ startDate: moment().startOf('month'),
235
+ endDate: moment().endOf('month'),
236
+ ranges: picker_ranges,
237
+ locale: {
238
+ applyLabel : BooklyL10n.apply,
239
+ cancelLabel: BooklyL10n.cancel,
240
+ fromLabel : BooklyL10n.from,
241
+ toLabel : BooklyL10n.to,
242
+ customRangeLabel: BooklyL10n.custom_range,
243
+ daysOfWeek : BooklyL10n.calendar.shortDays,
244
+ monthNames : BooklyL10n.calendar.longMonths,
245
+ firstDay : parseInt(BooklyL10n.startOfWeek),
246
+ format : BooklyL10n.mjsDateFormat
247
+ }
248
+ },
249
+ function(start, end) {
250
+ var format = 'YYYY-MM-DD';
251
+ $date_filter
252
+ .data('date', start.format(format) + ' - ' + end.format(format))
253
+ .find('span')
254
+ .html(start.format(BooklyL10n.mjsDateFormat) + ' - ' + end.format(BooklyL10n.mjsDateFormat));
255
+ }
256
+ );
257
+
258
+ /**
259
+ * On filters change.
260
+ */
261
+ $('.bookly-js-chosen-select').chosen({
262
+ allow_single_deselect: true,
263
+ disable_search_threshold: 10
264
+ });
265
+ $id_filter.on('keyup', function () { dt.ajax.reload(); });
266
+ $date_filter.on('apply.daterangepicker', function () { dt.ajax.reload(); });
267
+ $staff_filter.on('change', function () { dt.ajax.reload(); });
268
+ $customer_filter.on('change', function () { dt.ajax.reload(); });
269
+ $service_filter.on('change', function () { dt.ajax.reload(); });
270
+ $status_filter.on('change', function () { dt.ajax.reload(); });
271
+ });
backend/modules/appointments/resources/js/ng-app.js DELETED
@@ -1,208 +0,0 @@
1
- ;(function() {
2
-
3
- var module = angular.module('appointments', ['ui.utils', 'ui.date', 'ngSanitize']);
4
-
5
- module.factory('dataSource', function($q, $rootScope) {
6
- var ds = {
7
- appointments : [],
8
- total : 0,
9
- pages : [],
10
- loadData : function(params) {
11
- var deferred = $q.defer();
12
- jQuery.ajax({
13
- url : ajaxurl,
14
- type : 'POST',
15
- data : jQuery.extend({ action : 'ab_get_appointments' }, params),
16
- dataType : 'json',
17
- success : function(response) {
18
- if (response.success) {
19
- ds.appointments = response.data.appointments;
20
- ds.total = response.data.total;
21
- ds.pages = [];
22
- for (var i = 0; i < response.data.pages; ++ i) {
23
- ds.pages.push({
24
- number : i + 1,
25
- active : response.data.active_page == i + 1
26
- });
27
- }
28
- }
29
- $rootScope.$apply(deferred.resolve);
30
- },
31
- error : function() {
32
- ds.appointments = [];
33
- ds.total = 0;
34
- $rootScope.$apply(deferred.resolve);
35
- }
36
- });
37
-
38
- return deferred.promise;
39
- }
40
- };
41
-
42
- return ds;
43
- });
44
-
45
- module.controller('appointmentsCtrl', function($scope, dataSource) {
46
- // Set up initial data.
47
- var params = {
48
- page : 1,
49
- sort : 'start_date',
50
- order : 'desc',
51
- date_start : '',
52
- date_end : ''
53
- };
54
- $scope.loading = true;
55
- $scope.css_class = {
56
- staff_name : '',
57
- customer_name : '',
58
- service_title : '',
59
- start_date : 'desc',
60
- service_duration: '',
61
- price : ''
62
- };
63
-
64
- var format = 'YYYY-MM-DD';
65
- $scope.date_start = moment().startOf('month').format(format);
66
- $scope.date_end = moment().endOf('month').format(format);
67
-
68
- // Set up data source (data will be loaded in reload function).
69
- $scope.dataSource = dataSource;
70
-
71
- $scope.reload = function( opt ) {
72
- $scope.loading = true;
73
- if (opt !== undefined) {
74
- if (opt.sort !== undefined) {
75
- if (params.sort === opt.sort) {
76
- // Toggle order when sorting by the same field.
77
- params.order = params.order === 'asc' ? 'desc' : 'asc';
78
- } else {
79
- params.order = 'asc';
80
- }
81
- $scope.css_class = {
82
- staff_name : '',
83
- customer_name : '',
84
- service_title : '',
85
- start_date : '',
86
- service_duration: '',
87
- price : ''
88
- };
89
- $scope.css_class[opt.sort] = params.order;
90
- }
91
- jQuery.extend(params, opt);
92
- }
93
- params.date_start = $scope.date_start;
94
- params.date_end = $scope.date_end;
95
- dataSource.loadData(params).then(function() {
96
- $scope.loading = false;
97
- });
98
- };
99
-
100
- $scope.reload();
101
-
102
- /**
103
- * New appointment.
104
- *
105
- * @param appointment
106
- */
107
- $scope.newAppointment = function() {
108
- showAppointmentDialog(
109
- null,
110
- null,
111
- moment(),
112
- null,
113
- function(event) {
114
- $scope.$apply(function($scope) {
115
- $scope.reload();
116
- });
117
- }
118
- )
119
- };
120
-
121
- /**
122
- * Edit appointment.
123
- *
124
- * @param appointment
125
- */
126
- $scope.editAppointment = function(appointment) {
127
- showAppointmentDialog(
128
- appointment.appointment_id,
129
- appointment.staff_id,
130
- moment(appointment.start_date),
131
- moment(appointment.end_date),
132
- function(event) {
133
- $scope.$apply(function($scope) {
134
- $scope.reload();
135
- });
136
- }
137
- )
138
- };
139
-
140
- /**
141
- * Delete customer appointments.
142
- */
143
- $scope.deleteAppointments = function() {
144
- var ids = [];
145
- jQuery('table input[type=checkbox]:checked').each(function() {
146
- ids.push(jQuery(this).data('appointment_id'));
147
- });
148
- if( ids.length ) {
149
- $scope.loading = true;
150
- jQuery.ajax({
151
- url: ajaxurl,
152
- type: 'POST',
153
- data: {
154
- action: 'ab_delete_customer_appointment',
155
- ids: ids
156
- },
157
- dataType: 'json',
158
- success: function (response) {
159
- $scope.$apply(function ($scope) {
160
- $scope.reload();
161
- });
162
- }
163
- });
164
- } else{
165
- alert(BooklyL10n.please_select_at_least_one_row);
166
- }
167
- };
168
-
169
- // Init date range picker.
170
- var picker_ranges = {};
171
- picker_ranges[BooklyL10n.today] = [moment(), moment()];
172
- picker_ranges[BooklyL10n.yesterday] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
173
- picker_ranges[BooklyL10n.last_7] = [moment().subtract(7, 'days'), moment()];
174
- picker_ranges[BooklyL10n.last_30] = [moment().subtract(30, 'days'), moment()];
175
- picker_ranges[BooklyL10n.this_month] = [moment().startOf('month'), moment().endOf('month')];
176
- picker_ranges[BooklyL10n.next_month] = [moment().add(1, 'month').startOf('month'), moment().add(1, 'month').endOf('month')];
177
-
178
- jQuery('#reportrange').daterangepicker(
179
- {
180
- startDate: moment().startOf('month'),
181
- endDate: moment().endOf('month'),
182
- ranges: picker_ranges,
183
- locale: {
184
- applyLabel : BooklyL10n.apply,
185
- cancelLabel: BooklyL10n.cancel,
186
- fromLabel : BooklyL10n.from,
187
- toLabel : BooklyL10n.to,
188
- customRangeLabel: BooklyL10n.custom_range,
189
- daysOfWeek : BooklyL10n.shortDays,
190
- monthNames : BooklyL10n.longMonths,
191
- firstDay : parseInt(BooklyL10n.startOfWeek),
192
- format : BooklyL10n.mjsDateFormat
193
- }
194
- },
195
- function(start, end) {
196
- jQuery('#reportrange span').html(start.format(BooklyL10n.mjsDateFormat) + ' - ' + end.format(BooklyL10n.mjsDateFormat));
197
- $scope.$apply(function($scope){
198
- $scope.date_start = start.format(format);
199
- $scope.date_end = end.format(format);
200
- $scope.reload();
201
- });
202
- }
203
- );
204
- });
205
-
206
- // Bootstrap 'appointmentForm' application.
207
- angular.bootstrap(document.getElementById('ab-appointment-form'), ['appointmentForm']);
208
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appointments/templates/_export_dialog.php ADDED
@@ -0,0 +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>
backend/modules/appointments/templates/_print_dialog.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
backend/modules/appointments/templates/index.php CHANGED
@@ -1,79 +1,118 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="panel panel-default">
3
- <div class="panel-heading">
4
- <h3 class="panel-title"><?php _e( 'Appointments', 'bookly' ) ?></h3>
5
- </div>
6
- <div class="panel-body">
7
- <div ng-app="appointments" ng-controller="appointmentsCtrl" class="form-horizontal ng-cloak">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- <form class="form-inline" action="<?php echo admin_url( 'admin-ajax.php' ) ?>?action=ab_export_to_csv" method="post" style="margin-bottom: 20px">
10
- <div id=reportrange class="pull-left ab-reportrange">
11
- <i class="glyphicon glyphicon-calendar"></i>
12
- <span data-date="<?php echo date( 'F j, Y', strtotime( 'first day of' ) ) ?> - <?php echo date( 'F j, Y', strtotime( 'last day of' ) ) ?>"><?php echo date_i18n( get_option( 'date_format' ), strtotime( 'first day of' ) ) ?> - <?php echo date_i18n( get_option( 'date_format' ), strtotime( 'last day of' ) ) ?></span> <b style="margin-top: 8px;" class=caret></b>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  </div>
14
- <input type="hidden" name="date_start" ng-value="date_start" />
15
- <input type="hidden" name="date_end" ng-value="date_end" />
16
- <span class="help-inline"><?php _e( 'Delimiter', 'bookly' ) ?></span>
17
- <select name="delimiter" style="width: 125px;height: 30px" class="form-control">
18
- <option value=","><?php _e( 'Comma (,)', 'bookly' ) ?></option>
19
- <option value=";"><?php _e( 'Semicolon (;)', 'bookly' ) ?></option>
20
- </select>
21
- <button type="submit" class="btn btn-info"><?php _e( 'Export to CSV', 'bookly' ) ?></button>
22
- <button type="button" class="btn btn-info pull-right" ng-click="newAppointment()"><?php _e( 'New appointment', 'bookly' ) ?></button>
23
- </form>
24
 
25
- <div class="table-responsive">
26
- <table id="ab_appointments_list" class="table table-striped" cellspacing=0 cellpadding=0 border=0 style="clear: both;">
27
  <thead>
28
- <tr>
29
- <th style="width: 14%;" ng-class="css_class.start_date"><a href="" ng-click="reload({sort:'start_date'})"><?php _e( 'Booking Time', 'bookly' ) ?></a></th>
30
- <th style="width: 14%;" ng-class="css_class.staff_name"><a href="" ng-click="reload({sort:'staff_name'})"><?php _e( 'Staff Member', 'bookly' ) ?></a></th>
31
- <th style="width: 14%;" ng-class="css_class.customer_name"><a href="" ng-click="reload({sort:'customer_name'})"><?php _e( 'Customer Name', 'bookly' ) ?></a></th>
32
- <th style="width: 14%;" ng-class="css_class.service_title"><a href="" ng-click="reload({sort:'service_title'})"><?php _e( 'Service', 'bookly' ) ?></a></th>
33
- <th style="width: 14%;" ng-class="css_class.service_duration"><a href="" ng-click="reload({sort:'service_duration'})"><?php _e( 'Duration', 'bookly' ) ?></a></th>
34
- <th style="width: 14%;" colspan="3" ng-class="css_class.price"><a href="" ng-click="reload({sort:'price'})"><?php _e( 'Price', 'bookly' ) ?></a></th>
35
- </tr>
 
 
 
 
 
 
 
 
 
36
  </thead>
37
- <tbody>
38
- <tr ng-repeat="appointment in dataSource.appointments">
39
- <td>{{appointment.start_date_f}}</td>
40
- <td>{{appointment.staff_name}}</td>
41
- <td>{{appointment.customer_name}}</td>
42
- <td>{{appointment.service_title}}</td>
43
- <td>{{appointment.service_duration}}</td>
44
- <td>{{appointment.price}}</td>
45
- <td>
46
- <button class="btn btn-info pull-right" ng-click="editAppointment(appointment)">
47
- <?php _e( 'Edit', 'bookly' ) ?>
48
- </button>
49
- </td>
50
- <td><input type="checkbox" data-appointment_id="{{appointment.id}}"></td>
51
- </tr>
52
- </tbody>
53
  </table>
54
- <div ng-hide="dataSource.appointments.length || loading" class="alert alert-info"><?php _e( 'No appointments for selected period.', 'bookly' ) ?></div>
55
- </div>
56
 
57
- <div class="btn-toolbar">
58
- <div class="col-xs-8">
59
- <div class="btn-group" ng-hide="dataSource.pages.length == 1">
60
- <button ng-click="reload({page:page.number})" class="btn btn-default" ng-repeat="page in dataSource.pages" ng-switch on="page.active">
61
- <span ng-switch-when="true">{{page.number}}</span>
62
- <a href="" ng-switch-default>{{page.number}}</a>
63
- </button>
64
- </div>
65
- </div>
66
- <div class="col-xs-4">
67
- <a class="btn btn-info pull-right" ng-click="deleteAppointments()"><?php _e( 'Delete', 'bookly' ) ?></a>
68
  </div>
69
  </div>
70
-
71
- <div ng-show="loading" class="loading-indicator">
72
- <span class="ab-loader"></span>
73
- </div>
74
- </div>
75
- <div id="ab-appointment-form">
76
- <?php include AB_PATH . '/backend/modules/calendar/templates/_appointment_form.php' ?>
77
  </div>
 
 
 
 
 
 
78
  </div>
79
  </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
+ </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>
backend/modules/calendar/AB_CalendarController.php DELETED
@@ -1,488 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_CalendarController
5
- */
6
- class AB_CalendarController extends AB_Controller {
7
-
8
- protected function getPermissions()
9
- {
10
- return array( '_this' => 'user' );
11
- }
12
-
13
- public function index()
14
- {
15
- /** @var WP_Locale $wp_locale */
16
- global $wp_locale;
17
-
18
- $this->enqueueStyles( array(
19
- 'frontend' => array(
20
- 'css/intlTelInput.css',
21
- ),
22
- 'module' => array(
23
- 'css/calendar.css',
24
- 'css/fullcalendar.min.css',
25
- ),
26
- 'backend' => array(
27
- 'css/chosen.min.css',
28
- 'css/jquery-ui-theme/jquery-ui.min.css',
29
- 'css/bookly.main-backend.css',
30
- 'bootstrap/css/bootstrap.min.css',
31
- ),
32
- ) );
33
-
34
- $this->enqueueScripts( array(
35
- 'backend' => array(
36
- 'js/angular.min.js' => array( 'jquery' ),
37
- 'js/angular-ui-date-0.0.8.js' => array( 'ab-angular.min.js' ),
38
- 'js/ng-new_customer_dialog.js' => array( 'ab-angular.min.js' ),
39
- 'js/moment.min.js' => array( 'jquery' ),
40
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
41
- 'js/chosen.jquery.min.js' => array( 'jquery' ),
42
- 'js/ng-edit_appointment_dialog.js' => array( 'ab-angular-ui-date-0.0.8.js', 'jquery-ui-datepicker' ),
43
- ),
44
- 'module' => array(
45
- 'js/fullcalendar.min.js' => array( 'ab-moment.min.js' ),
46
- 'js/fc-multistaff-view.js' => array( 'ab-fullcalendar.min.js' ),
47
- 'js/calendar.js' => array( 'ab-fc-multistaff-view.js', 'ab-intlTelInput.min.js' ),
48
- ),
49
- 'frontend' => array(
50
- 'js/intlTelInput.min.js' => array( 'jquery' ),
51
- )
52
- ) );
53
-
54
- $slot_length_minutes = get_option( 'ab_settings_time_slot_length', '15' );
55
- $slot = new DateInterval( 'PT' . $slot_length_minutes . 'M' );
56
-
57
- $this->staff_members = AB_Utils::isCurrentUserAdmin()
58
- ? AB_Staff::query()->sortBy( 'position' )->find()
59
- : AB_Staff::query()->where( 'wp_user_id', get_current_user_id() )->find();
60
-
61
-
62
- wp_localize_script( 'ab-calendar.js', 'BooklyL10n', array(
63
- 'slotDuration' => $slot->format( '%H:%I:%S' ),
64
- 'shortMonths' => array_values( $wp_locale->month_abbrev ),
65
- 'longMonths' => array_values( $wp_locale->month ),
66
- 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
67
- 'longDays' => array_values( $wp_locale->weekday ),
68
- 'AM' => $wp_locale->meridiem[ 'AM' ],
69
- 'PM' => $wp_locale->meridiem[ 'PM' ],
70
- 'dpDateFormat' => AB_DateTimeUtils::convertFormat( 'date', AB_DateTimeUtils::FORMAT_JQUERY_DATEPICKER ),
71
- 'mjsDateFormat' => AB_DateTimeUtils::convertFormat( 'date', AB_DateTimeUtils::FORMAT_MOMENT_JS ),
72
- 'mjsTimeFormat' => AB_DateTimeUtils::convertFormat( 'time', AB_DateTimeUtils::FORMAT_MOMENT_JS ),
73
- 'today' => __( 'Today', 'bookly' ),
74
- 'week' => __( 'Week', 'bookly' ),
75
- 'day' => __( 'Day', 'bookly' ),
76
- 'month' => __( 'Month', 'bookly' ),
77
- 'allDay' => __( 'All Day', 'bookly' ),
78
- 'noStaffSelected' => __( 'No staff selected', 'bookly' ),
79
- 'newAppointment' => __( 'New appointment', 'bookly' ),
80
- 'editAppointment' => __( 'Edit appointment', 'bookly' ),
81
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
82
- 'startOfWeek' => (int) get_option( 'start_of_week' ),
83
- 'country' => get_option( 'ab_settings_phone_default_country' ),
84
- 'intlTelInput_utils' => plugins_url( 'intlTelInput.utils.js', AB_PATH . '/frontend/resources/js/intlTelInput.utils.js' ),
85
- ) );
86
-
87
- $this->render( 'calendar' );
88
- }
89
-
90
- /**
91
- * Get data for FullCalendar.
92
- *
93
- * @return json
94
- */
95
- public function executeGetStaffAppointments()
96
- {
97
- $result = array();
98
- $staff_members = array();
99
- $one_day = new DateInterval( 'P1D' );
100
- $start_date = new DateTime( $this->getParameter( 'start' ) );
101
- $end_date = new DateTime( $this->getParameter( 'end' ) );
102
- // FullCalendar sends end date as 1 day further.
103
- $end_date->sub( $one_day );
104
-
105
- if ( AB_Utils::isCurrentUserAdmin() ) {
106
- $staff_members = AB_Staff::query()
107
- ->where( 'id', 1 )
108
- ->find();
109
- } else {
110
- $staff_members[] = AB_Staff::query()
111
- ->where( 'wp_user_id', get_current_user_id() )
112
- ->findOne();
113
- }
114
-
115
- foreach ( $staff_members as $staff ) {
116
- /** @var AB_Staff $staff */
117
- $result = array_merge( $result, $staff->getAppointmentsForFC( $start_date, $end_date ) );
118
-
119
- // Schedule.
120
- $items = $staff->getScheduleItems();
121
- $day = clone $start_date;
122
- // Find previous day end time.
123
- $last_end = clone $day;
124
- $last_end->sub( $one_day );
125
- $w = $day->format( 'w' );
126
- $end_time = $items[ $w > 0 ? $w : 7 ]->get( 'end_time' );
127
- if ( $end_time !== null ) {
128
- $end_time = explode( ':', $end_time );
129
- $last_end->setTime( $end_time[0], $end_time[1] );
130
- } else {
131
- $last_end->setTime( 24, 0 );
132
- }
133
- // Do the loop.
134
- while ( $day <= $end_date ) {
135
- do {
136
- /** @var AB_StaffScheduleItem $item */
137
- $item = $items[ $day->format( 'w' ) + 1 ];
138
- if ( $item->get( 'start_time' ) && ! $staff->isOnHoliday( $day ) ) {
139
- $start = $last_end->format( 'Y-m-d H:i:s' );
140
- $end = $day->format( 'Y-m-d '.$item->get( 'start_time' ) );
141
- if ( $start < $end ) {
142
- $result[] = array(
143
- 'start' => $start,
144
- 'end' => $end,
145
- 'rendering' => 'background',
146
- 'staffId' => $staff->get( 'id' ),
147
- );
148
- }
149
- $last_end = clone $day;
150
- $end_time = explode( ':', $item->get( 'end_time' ) );
151
- $last_end->setTime( $end_time[0], $end_time[1] );
152
-
153
- // Breaks.
154
- foreach ( $item->getBreaksList() as $break ) {
155
- $result[] = array(
156
- 'start' => $day->format( 'Y-m-d '.$break['start_time'] ),
157
- 'end' => $day->format( 'Y-m-d '.$break['end_time'] ),
158
- 'rendering' => 'background',
159
- 'staffId' => $staff->get( 'id' ),
160
- );
161
- }
162
-
163
- break;
164
- }
165
-
166
- $result[] = array(
167
- 'start' => $last_end->format( 'Y-m-d H:i:s' ),
168
- 'end' => $day->format( 'Y-m-d 24:00:00' ),
169
- 'rendering' => 'background',
170
- 'staffId' => $staff->get( 'id' ),
171
- );
172
- $last_end = clone $day;
173
- $last_end->setTime( 24, 0 );
174
-
175
- } while ( 0 );
176
-
177
- $day->add( $one_day );
178
- }
179
-
180
- if ( $last_end->format( 'H' ) != 24 ) {
181
- $result[] = array(
182
- 'start' => $last_end->format( 'Y-m-d H:i:s' ),
183
- 'end' => $last_end->format( 'Y-m-d 24:00:00' ),
184
- 'rendering' => 'background',
185
- 'staffId' => $staff->get( 'id' ),
186
- );
187
- }
188
- }
189
-
190
- wp_send_json( $result );
191
- }
192
-
193
- /**
194
- * Get data needed for appointment form initialisation.
195
- */
196
- public function executeGetDataForAppointmentForm()
197
- {
198
- $result = array(
199
- 'staff' => array(),
200
- 'customers' => array(),
201
- 'start_time' => array(),
202
- 'end_time' => array(),
203
- 'time_interval' => get_option( 'ab_settings_time_slot_length' ) * 60
204
- );
205
-
206
- // Staff list.
207
- $staff_members = AB_Staff::query()->where( 'id', 1 )->sortBy( 'position' )->find();
208
-
209
-
210
- /** @var AB_Staff $staff_member */
211
- foreach ( $staff_members as $staff_member ) {
212
- $services = array();
213
- foreach ( $staff_member->getStaffServices() as $staff_service ) {
214
- $services[] = array(
215
- 'id' => $staff_service->service->get( 'id' ),
216
- 'title' => sprintf(
217
- '%s (%s)',
218
- $staff_service->service->get( 'title' ),
219
- AB_DateTimeUtils::secondsToInterval( $staff_service->service->get( 'duration' ) )
220
- ),
221
- 'duration' => $staff_service->service->get( 'duration' ),
222
- 'capacity' => $staff_service->get( 'capacity' )
223
- );
224
- }
225
- $result['staff'][] = array(
226
- 'id' => 1,
227
- 'full_name' => $staff_member->get( 'full_name' ),
228
- 'services' => $services
229
- );
230
- }
231
-
232
- // Customers list.
233
- foreach ( AB_Customer::query()->sortBy( 'name' )->find() as $customer ) {
234
- $name = $customer->get( 'name' );
235
- if ( $customer->get( 'email' ) != '' || $customer->get( 'phone' ) != '' ) {
236
- $name .= ' (' . trim( $customer->get( 'email' ) . ', ' . $customer->get( 'phone' ) , ', ') . ')';
237
- }
238
-
239
- $result[ 'customers' ][] = array(
240
- 'id' => $customer->get( 'id' ),
241
- 'name' => $name,
242
- 'custom_fields' => array(),
243
- 'number_of_persons' => 1,
244
- );
245
- }
246
-
247
- // Time list.
248
- $ts_length = AB_Config::getTimeSlotLength();
249
- $time_start = 0;
250
- $time_end = DAY_IN_SECONDS * 2;
251
-
252
- // Run the loop.
253
- while ( $time_start <= $time_end ) {
254
- $slot = array(
255
- 'value' => AB_DateTimeUtils::buildTimeString( $time_start, false ),
256
- 'title' => AB_DateTimeUtils::formatTime( $time_start )
257
- );
258
- if ( $time_start < DAY_IN_SECONDS ) {
259
- $result['start_time'][] = $slot;
260
- }
261
- $result['end_time'][] = $slot;
262
- $time_start += $ts_length;
263
- }
264
-
265
- wp_send_json( $result );
266
- }
267
-
268
- /**
269
- * Get appointment data when editing an appointment.
270
- */
271
- public function executeGetDataForAppointment()
272
- {
273
- $response = array( 'success' => false, 'data' => array( 'customers' => array() ) );
274
-
275
- $appointment = new AB_Appointment();
276
- if ( $appointment->load( $this->getParameter( 'id' ) ) ) {
277
- $response['success'] = true;
278
-
279
- $info = AB_Appointment::query( 'a' )
280
- ->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' )
281
- ->leftJoin( 'AB_CustomerAppointment', 'ca', 'ca.appointment_id = a.id' )
282
- ->leftJoin( 'AB_StaffService', 'ss', 'ss.staff_id = a.staff_id AND ss.service_id = a.service_id' )
283
- ->where( 'a.id', $appointment->get( 'id' ) )
284
- ->fetchRow();
285
-
286
- $response['data']['total_number_of_persons'] = $info['total_number_of_persons'];
287
- $response['data']['max_capacity'] = $info['max_capacity'];
288
- $response['data']['start_date'] = $info['start_date'];
289
- $response['data']['end_date'] = $info['end_date'];
290
- $response['data']['staff_id'] = $info['staff_id'];
291
- $response['data']['service_id'] = $info['service_id'];
292
-
293
- $customer_appointments = AB_CustomerAppointment::query()->where( 'appointment_id', $appointment->get( 'id' ) )->fetchArray();
294
-
295
- foreach ( $customer_appointments as $customer_appointment ) {
296
- $response[ 'data' ][ 'customers' ][] = array(
297
- 'id' => $customer_appointment['customer_id'],
298
- 'custom_fields' => $customer_appointment['custom_fields'] ? json_decode( $customer_appointment['custom_fields'], true ) : array(),
299
- 'number_of_persons' => $customer_appointment['number_of_persons']
300
- );
301
- }
302
- }
303
- wp_send_json( $response );
304
- }
305
-
306
- /**
307
- * Save appointment form (for both create and edit).
308
- */
309
- public function executeSaveAppointmentForm()
310
- {
311
- $response = array( 'success' => false );
312
-
313
- $start_date = date( 'Y-m-d H:i:s', strtotime( $this->getParameter( 'start_date' ) ) );
314
- $end_date = date( 'Y-m-d H:i:s', strtotime( $this->getParameter( 'end_date' ) ) );
315
- $staff_id = $this->getParameter( 'staff_id' );
316
- $service_id = $this->getParameter( 'service_id' );
317
- $appointment_id = $this->getParameter( 'id', 0 );
318
- $customers = json_decode( $this->getParameter( 'customers', '[]' ), true );
319
-
320
- $staff_service = new AB_StaffService();
321
- $staff_service->loadBy( array(
322
- 'staff_id' => $staff_id,
323
- 'service_id' => $service_id
324
- ) );
325
-
326
- // Check for errors.
327
- if ( ! $service_id ) {
328
- $response['errors']['service_required'] = true;
329
- }
330
- if ( empty ( $customers ) ) {
331
- $response['errors']['customers_required'] = true;
332
- }
333
- if ( !$this->dateIntervalIsAvailableForAppointment( $start_date, $end_date, $staff_id, $appointment_id ) ) {
334
- $response['errors']['date_interval_not_available'] = true;
335
- }
336
- $number_of_persons = 0;
337
- foreach ( $customers as $customer ) {
338
- $number_of_persons += $customer['number_of_persons'];
339
- }
340
- if ( $number_of_persons > $staff_service->get( 'capacity' ) ) {
341
- $response['errors']['overflow_capacity'] = __( 'The number of customers should be not more than ', 'bookly' ) . $staff_service->get( 'capacity' );
342
- }
343
- if ( ! $this->getParameter( 'start_date' ) ) {
344
- $response['errors']['time_interval'] = __( 'Start time must not be empty', 'bookly' );
345
- } elseif ( ! $this->getParameter( 'end_date' ) ) {
346
- $response['errors']['time_interval'] = __( 'End time must not be empty', 'bookly' );
347
- } elseif ( $start_date == $end_date ) {
348
- $response['errors']['time_interval'] = __( 'End time must not be equal to start time', 'bookly' );
349
- }
350
-
351
- // If no errors then try to save the appointment.
352
- if ( !isset ( $response[ 'errors' ] ) ) {
353
- $appointment = new AB_Appointment();
354
- if ( $appointment_id ) {
355
- // Edit.
356
- $appointment->load( $appointment_id );
357
- }
358
- $appointment->set( 'start_date', $start_date );
359
- $appointment->set( 'end_date', $end_date );
360
- $appointment->set( 'staff_id', $staff_id );
361
- $appointment->set( 'service_id', $service_id );
362
-
363
- if ( $appointment->save() !== false ) {
364
- // Save customers.
365
- $appointment->setCustomers( $customers );
366
-
367
- // Google Calendar.
368
- $appointment->handleGoogleCalendar();
369
-
370
- $startDate = new DateTime( $appointment->get( 'start_date' ) );
371
- $endDate = new DateTime( $appointment->get( 'end_date' ) );
372
- $desc = array();
373
- if ( $staff_service->get( 'capacity' ) == 1 ) {
374
- $customer_appointments = $appointment->getCustomerAppointments();
375
- if ( !empty ( $customer_appointments ) ) {
376
- $ca = $customer_appointments[ 0 ]->customer;
377
- foreach ( array( 'name', 'phone', 'email' ) as $data_entry ) {
378
- $entry_value = $ca->get( $data_entry );
379
- if ( $entry_value ) {
380
- $desc[] = '<div class="fc-employee">' . esc_html( $entry_value ) . '</div>';
381
- }
382
- }
383
-
384
- foreach ( $customer_appointments[0]->getCustomFields() as $custom_field ) {
385
- $desc[] = '<div class="fc-notes">' . wp_strip_all_tags( $custom_field['label'] ) . ': ' . esc_html( $custom_field['value'] ) . '</div>';
386
- }
387
- }
388
- } else {
389
- $signed_up = 0;
390
- foreach ( $appointment->getCustomerAppointments() as $ca ) {
391
- $signed_up += $ca->get( 'number_of_persons' );
392
- }
393
-
394
- $desc[] = '<div class="fc-notes">' . __( 'Signed up', 'bookly' ) . ' ' . $signed_up . '</div>';
395
- $desc[] = '<div class="fc-notes">' . __( 'Capacity', 'bookly' ) . ' ' . $staff_service->get( 'capacity' ) . '</div>';
396
- }
397
-
398
- $service = new AB_Service();
399
- $service->load( $service_id );
400
-
401
- $response['success'] = true;
402
- $response['data'] = array(
403
- 'id' => (int)$appointment->get( 'id' ),
404
- 'start' => $startDate->format( 'Y-m-d H:i:s' ),
405
- 'end' => $endDate->format( 'Y-m-d H:i:s' ),
406
- 'desc' => implode( '', $desc ),
407
- 'title' => $service->get( 'title' ) ? $service->get( 'title' ) : __( 'Untitled', 'bookly' ),
408
- 'color' => $service->get( 'color' ),
409
- 'staffId' => $appointment->get( 'staff_id' ),
410
- );
411
- } else {
412
- $response[ 'errors' ] = array( 'db' => __( 'Could not save appointment in database.', 'bookly' ) );
413
- }
414
- }
415
-
416
- wp_send_json( $response );
417
- }
418
-
419
- public function executeCheckAppointmentDateSelection()
420
- {
421
- $start_date = $this->getParameter( 'start_date' );
422
- $end_date = $this->getParameter( 'end_date' );
423
- $staff_id = $this->getParameter( 'staff_id' );
424
- $service_id = $this->getParameter( 'service_id' );
425
- $appointment_id = $this->getParameter( 'appointment_id' );
426
- $timestamp_diff = strtotime( $end_date ) - strtotime( $start_date );
427
-
428
- $result = array(
429
- 'date_interval_not_available' => false,
430
- 'date_interval_warning' => false,
431
- );
432
-
433
- if ( !$this->dateIntervalIsAvailableForAppointment( $start_date, $end_date, $staff_id, $appointment_id ) ) {
434
- $result['date_interval_not_available'] = true;
435
- }
436
-
437
- if ( $service_id ) {
438
- $service = new AB_Service();
439
- $service->load( $service_id );
440
-
441
- $duration = $service->get( 'duration' );
442
-
443
- // Service duration interval is not equal to.
444
- $result['date_interval_warning'] = ( $timestamp_diff != $duration );
445
- }
446
-
447
- wp_send_json( $result );
448
- }
449
-
450
- public function executeDeleteAppointment()
451
- {
452
- $appointment = new AB_Appointment();
453
- $appointment->load( $this->getParameter( 'appointment_id' ) );
454
- $appointment->delete();
455
- exit;
456
- }
457
-
458
- /**
459
- * @param $start_date
460
- * @param $end_date
461
- * @param $staff_id
462
- * @param $appointment_id
463
- * @return bool
464
- */
465
- private function dateIntervalIsAvailableForAppointment( $start_date, $end_date, $staff_id, $appointment_id )
466
- {
467
- return AB_Appointment::query()
468
- ->whereNot( 'id', $appointment_id )
469
- ->where( 'staff_id', $staff_id )
470
- ->whereRaw(
471
- '(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) )',
472
- array( $start_date, $end_date, $start_date, $end_date, $start_date, $end_date, $start_date, $end_date )
473
- )
474
- ->count() == 0;
475
- }
476
-
477
- /**
478
- * Override parent method to add 'wp_ajax_ab_' prefix
479
- * so current 'execute*' methods look nicer.
480
- *
481
- * @param string $prefix
482
- */
483
- protected function registerWpActions( $prefix = '' )
484
- {
485
- parent::registerWpActions( 'wp_ajax_ab_' );
486
- }
487
-
488
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/Components.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Calendar;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Components
8
+ * @package BooklyLite\Backend\Modules\Calendar
9
+ */
10
+ class Components extends Lib\Base\Components
11
+ {
12
+ /**
13
+ * Render appointment dialog.
14
+ * @throws \Exception
15
+ */
16
+ public function renderAppointmentDialog()
17
+ {
18
+ global $wp_locale;
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 ),
41
+ 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
42
+ 'longDays' => array_values( $wp_locale->weekday ),
43
+ ),
44
+ 'dpDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_JQUERY_DATEPICKER ),
45
+ 'startOfWeek' => (int) get_option( 'start_of_week' ),
46
+ 'cf_per_service' => (int) Lib\Config::customFieldsPerService(),
47
+ 'title' => array(
48
+ 'edit_appointment' => __( 'Edit appointment', 'bookly' ),
49
+ 'new_appointment' => __( 'New appointment', 'bookly' ),
50
+ ),
51
+ ) );
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
+ }
backend/modules/calendar/Controller.php ADDED
@@ -0,0 +1,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
+ 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
+ ->whereIn( '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
+ }
backend/modules/calendar/forms/AB_AppointmentForm.php DELETED
@@ -1,22 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_AppointmentForm
5
- */
6
- class AB_AppointmentForm extends AB_Form {
7
-
8
- /**
9
- * Constructor.
10
- */
11
- public function __construct()
12
- {
13
- parent::$entity_class = 'AB_Appointment';
14
- parent::__construct();
15
- }
16
-
17
- public function configure()
18
- {
19
- //$this->setFields( array() );
20
- }
21
-
22
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/forms/AB_CustomerAppointmentForm.php DELETED
@@ -1,17 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_AppointmentForm
5
- */
6
- class AB_CustomerAppointmentForm extends AB_Form {
7
-
8
- /**
9
- * Constructor.
10
- */
11
- public function __construct()
12
- {
13
- parent::$entity_class = 'AB_CustomerAppointment';
14
- parent::__construct();
15
- }
16
-
17
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/resources/css/calendar.css DELETED
@@ -1,215 +0,0 @@
1
- /* calendar nav */
2
- .ab-calendar-day, .ab-calendar-week, .ab-calendar-today, .btn-list, .btn-dropdown {
3
- display: inline-block;
4
- padding: 2px 10px;
5
- margin-bottom: 0;
6
- font-size: 12px;
7
- line-height: 20px;
8
- color: #fff;
9
- text-align: center;
10
- text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
11
- vertical-align: middle;
12
- cursor: pointer;
13
- background-color: #21759b;
14
- background-image: -webkit-gradient(linear,left top,left bottom,from(#2a95c5),to(#21759b));
15
- background-image: -webkit-linear-gradient(top,#2a95c5,#21759b);
16
- background-image: -moz-linear-gradient(top,#2a95c5,#21759b);
17
- background-image: -ms-linear-gradient(top,#2a95c5,#21759b);
18
- background-image: -o-linear-gradient(top,#2a95c5,#21759b);
19
- background-image: linear-gradient(to bottom,#2a95c5,#21759b);
20
- background-repeat: repeat-x;
21
- border: 1px solid #21759b;
22
- border-color: #e6e6e6 #e6e6e6 #1e6a8d;
23
- border-bottom-color: #b3b3b3;
24
- -webkit-border-radius: 4px;
25
- -moz-border-radius: 4px;
26
- border-radius: 4px;
27
- -webkit-box-shadow: inset 0 1px 0 rgba(120,200,230,0.5);
28
- moz-box-shadow: inset 0 1px 0 rgba(120,200,230,0.5);
29
- box-shadow: inset 0 1px 0 rgba(120,200,230,0.5);
30
- }
31
- .ab-calendar-day { border-bottom-right-radius: 0px; border-top-right-radius: 0px; margin-top: 0; }
32
- .ab-calendar-week { border-bottom-left-radius: 0px; border-top-left-radius: 0px; margin-left: -3px; margin-top: 0; border-left: 0; }
33
- .ab-calendar-today { margin: 0 5px; border-radius: 3px; }
34
- .ab-nav-calendar { height: 30px; }
35
- .ab-nav-calendar .ab-button-active { background: #1f6a8c; }
36
- .ab-nav-calendar .ab-popup-wrapper { margin-left: 10px; }
37
- .ab-popup-table td { white-space: nowrap; padding: 5px; border: 0!important; }
38
- .ab-popup-table td > p { margin: 0; }
39
-
40
- .ab-btn-group .btn-list,
41
- .ab-btn-group .btn-dropdown {
42
- margin: 0;
43
- border: 0;
44
- float: left;
45
- position: relative;
46
- }
47
- .ab-btn-group .btn-list {
48
- border-bottom-right-radius: 0px;
49
- border-top-right-radius: 0px;
50
- padding-left: 25px;
51
- }
52
- .ab-btn-group .btn-dropdown {
53
- font-size: 9px;
54
- padding-left: 0;
55
- border-bottom-left-radius: 0px;
56
- border-top-left-radius: 0px;
57
- }
58
- .ab-icon-button {
59
- position: absolute;
60
- top: 3px;
61
- left: 3px;
62
- width: 15px;
63
- height: 15px;
64
- }
65
- .ab-icon-button.ab-services {
66
- background: url("../images/box-small.png") 2px 2px no-repeat;
67
- }
68
- td.fc-axis.fc-time.fc-widget-content { vertical-align: top; }
69
-
70
- /** week picker */
71
- .ab-week-picker-wrapper { width: 350px; position: relative; margin-top: 2px; }
72
- .ab-week-picker-wrapper i.glyphicon-calendar { position: absolute; left: 46px; top: 8px; z-index: 9; }
73
- .ab-week-picker-wrapper .ab-week-picker { clear: both; left: -1px; position: absolute; top: 30px; z-index: 9; display: none; }
74
- .ab-week-picker-data { border-radius: 3px; background: url("../images/calendar.png") 2px 4px no-repeat; padding: 4px 7px 4px 25px; height: 16px; border: 1px solid black; }
75
- #appendedPrependedInput { background-color: #FFFFFF; cursor: default; }
76
-
77
- /** appointment dialog */
78
- .ab-appointment-popup .ui-widget-header {
79
- background: none!important;
80
- border: none!important;
81
- border-bottom: 1px solid #eee!important;
82
- margin: -5px -5px 15px -5px!important;
83
- font-size: 24.5px!important;
84
- font-family: sans-serif!important;
85
- line-height: 26px!important;
86
- padding: 9px 15px!important;
87
- }
88
-
89
- .ab-appointment-popup .loading-indicator img { display: block; margin: 20px auto; }
90
- .ab-appointment-popup .dialog-button-wrapper { }
91
-
92
- .ab-appointment-popup .dialog-content .ab-appointment-field-value { width: 110px; vertical-align: top; }
93
- .ab-appointment-popup .dialog-content td { padding-top: 7px; text-align: left; white-space: nowrap; }
94
- .ab-appointment-popup .dialog-content select { width: auto; max-width: 100%; }
95
- .ab-appointment-popup .dialog-content { color: #333333!important; font-family: "Helvetica Neue",Helvetica,Arial,sans-serif!important; font-size: 14px!important; }
96
- .ab-appointment-popup .dialog-content .ab-reset-form { color: #21759b; }
97
- .ab-appointment-popup .dialog-content .ab-reset-form:hover { color: #d54e21; }
98
-
99
- /** calendars */
100
- #day_calendar_wrapper { margin-top: 55px; }
101
- .ab-calendar-inner .table-responsive { position: relative; }
102
- .ab-calendar-inner .ab-loading-inner {
103
- position: absolute;
104
- left: 0;
105
- top: 50px;
106
- right: 0;
107
- bottom: 0;
108
- background-color: rgba(255, 255, 255, 0.7);
109
- z-index: 9;
110
- }
111
- .ab-calendar-inner .ab-loading-inner .ab-loader { position: absolute; left: 50%; top: 50%; width: auto; }
112
-
113
- /** wp elements */
114
- #update-nag, .update-nag, #wpfooter { display: none; }
115
- #wpbody-content { padding-bottom: 0; }
116
-
117
- .ab-date-picker { border: 1px solid black!important; padding: 4px 4px 4px 7px; border-radius: 3px; width: 65px; }
118
- .ab-date-paginator { margin-left: 10px; }
119
- .ab-date-paginator > a {
120
- float: left;
121
- text-decoration: none;
122
- color: black;
123
- padding: 4px 2px;
124
- display: block;
125
- border: 1px solid black;
126
- margin-left: -1px;
127
- width: 18px;
128
- text-align: center;
129
- }
130
- .ab-date-paginator > a.first {
131
- border-bottom-left-radius: 10px;
132
- -webkit-border-bottom-left-radius: 10px;
133
- -moz-border-bottom-left-radius: 10px;
134
- -o-border-bottom-left-radius: 10px;
135
- border-top-left-radius: 10px;
136
- -webkit-border-top-left-radius: 10px;
137
- -moz-border-top-left-radius: 10px;
138
- -o-border-top-left-radius: 10px;
139
- }
140
- .ab-date-paginator > a.last {
141
- border-bottom-right-radius: 10px;
142
- -webkit-border-bottom-right-radius: 10px;
143
- -moz-border-bottom-right-radius: 10px;
144
- -o-border-bottom-right-radius: 10px;
145
- border-top-right-radius: 10px;
146
- -webkit-border-top-right-radius: 10px;
147
- -moz-border-top-right-radius: 10px;
148
- -o-border-top-right-radius: 10px;
149
- }
150
-
151
- .fc-cal-event {
152
- background-color: #DDDDDD;
153
- line-height: 16px!important;
154
- opacity: 1;
155
- }
156
- .fc-cal-event .fc-time {
157
- background-color : #DDDDDD;
158
- border-left-color : #DDDDDD;
159
- border-right-color : #DDDDDD;
160
- border-top-color : #DDDDDD;
161
- border-bottom-color : #ABABAB;
162
- padding : 3.5px 0;
163
- }
164
-
165
- .pagination { margin: 0!important; }
166
-
167
- .nav-tabs > .active > a { background: #1f6a8c!important; color: white!important; border-left: 0!important; }
168
- .ab-date-calendar { padding-left: 25px!important; background: url("../images/calendar.png") 5px 8px no-repeat; }
169
-
170
- /* Custom fields */
171
- .dialog-content .ab-formGroup { margin-bottom: 15px; }
172
- .ab-customer-list { margin: 6px 0 0 0; }
173
- .ab-create-customer > a,
174
- .ab-customer-list li a { cursor: pointer; border-bottom: 1px dashed #21759B; }
175
- .ab-customer-list li a + .icon-remove { cursor: pointer; }
176
- .ab-create-customer > a:hover,
177
- .ab-customer-list li a:hover { text-decoration: none; color: #21759b; border-color: #21759b; }
178
-
179
- /* Appointment dialog */
180
- /*#ab_appointment_dialog { overflow: visible; }*/
181
- #ab_appointment_dialog .chzn-container { font-family: Verdana,Arial,sans-serif; font-size: 14px; }
182
- #ab_custom_fields_dialog * { font-family: "Helvetica Neue",Helvetica,Arial,sans-serif!important; }
183
-
184
- .fc-content .icon-rt { float: right; position: absolute; top: 4px; right: 4px; cursor: pointer; }
185
- .fc-view-container { box-shadow: 0 0 3px silver; }
186
- .fc-widget-header table th { padding: 10px; }
187
- .ab-head-calendar { margin-bottom: 0!important; }
188
- .fc button { padding: 6px 12px!important; height: auto!important; }
189
- .fc-time-grid-event.fc-short .fc-time:before { font-size: 12px; }
190
- .fc-ltr .fc-time-grid .fc-event-container { margin: 0!important; }
191
- .fc-time-grid-event .fc-time,
192
- .fc-time-grid-event.fc-short .fc-title { font-size: 12px!important; padding: 0 5px!important; padding-top: 3px!important; }
193
- .fc-time-grid-event.fc-short .fc-title { padding-top: 5px!important; display: block!important; }
194
- .fc-day-grid-event,
195
- .fc-time-grid-event { cursor: pointer; }
196
- .fc-time-grid-event .fc-title { padding: 0 5px!important; padding-top: 5px!important; }
197
- #full_calendar_wrapper .table-responsive { overflow: visible; }
198
- .fc-toolbar h2 { position: relative; padding-right: 25px; }
199
- .fc-toolbar h2:before {
200
- content: "\e252";
201
- font-family: 'Glyphicons Halflings';
202
- position: absolute;
203
- right: 0;
204
- font-size: 19px;
205
- top: 7px;
206
- }
207
- .fc-toolbar h2:hover,
208
- .fc-toolbar h2:hover:before { color: #337ab7; cursor: pointer; }
209
-
210
- /* media query */
211
- @media screen and (max-width: 782px) {
212
- #full_calendar_wrapper .table-responsive { overflow-y: hidden; }
213
- }
214
-
215
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/resources/css/fullcalendar.min.css CHANGED
@@ -1,5 +1,5 @@
1
  /*!
2
- * FullCalendar v2.3.2 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,9 +1,9 @@
1
  jQuery(function ($) {
2
 
3
- var $fullCalendar = $('#full_calendar_wrapper .ab-calendar-element'),
4
- $tabs = $('li.ab-calendar-tab'),
5
- $staff = $('input.ab-staff-filter'),
6
- $showAll = $('input#ab-filter-all-staff'),
7
  firstHour = new Date().getHours(),
8
  $staffButton = $('#ab-staff-button'),
9
  staffMembers = [],
@@ -17,12 +17,12 @@ jQuery(function ($) {
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
  });
@@ -49,7 +49,7 @@ jQuery(function ($) {
49
  height: heightFC(),
50
  // Views.
51
  defaultView: lastView,
52
- scrollTime: firstHour+':00:00',
53
  views: {
54
  agendaWeek: {
55
  columnFormat: 'ddd, D'
@@ -67,16 +67,16 @@ jQuery(function ($) {
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.longMonths,
77
- monthNamesShort: BooklyL10n.shortMonths,
78
- dayNames: BooklyL10n.longDays,
79
- dayNamesShort: BooklyL10n.shortDays,
80
  // Event Dragging & Resizing.
81
  editable: false,
82
  // Event Data.
@@ -84,7 +84,7 @@ jQuery(function ($) {
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) {
@@ -98,8 +98,8 @@ jQuery(function ($) {
98
  }
99
  }],
100
  // Event Rendering.
101
- eventRender : function(calEvent, $event) {
102
- var body = '<div class="fc-service-name">' + calEvent.title + '<i class="delete-event icon-rt glyphicon glyphicon-trash"></i></div>';
103
 
104
  if (calEvent.desc) {
105
  body += calEvent.desc;
@@ -107,7 +107,10 @@ jQuery(function ($) {
107
 
108
  $event.find('.fc-title').html(body);
109
 
110
- $event.find('i.delete-event').on('click', function(e) {
 
 
 
111
  e.stopPropagation();
112
  if (confirm(BooklyL10n.are_you_sure)) {
113
  $.post(ajaxurl, {'action' : 'ab_delete_appointment', 'appointment_id' : calEvent.id }, function () {
@@ -116,39 +119,25 @@ jQuery(function ($) {
116
  }
117
  });
118
  },
119
- eventAfterRender : function(calEvent, $calEventList, calendar) {
120
  $calEventList.each(function () {
121
  var $calEvent = $(this);
122
- var titleHeight = $calEvent.find('.fc-title').height();
123
- var origHeight = $calEvent.height();
124
-
125
- if ( origHeight < titleHeight ) {
126
- var $info = $('<i class="icon-rt glyphicon glyphicon-info-sign"></i>');
127
- var $delete = $('.delete-event', $calEvent);
128
-
129
- $delete.hide();
130
- $('.fc-content', $calEvent).append($info);
131
- $('.fc-content', $calEvent).append($delete);
132
-
133
  var z_index = $calEvent.zIndex();
134
  // Mouse handlers.
135
- $info.on('mouseenter', function () {
136
- $calEvent.css({height: (titleHeight + 30), 'z-index': 64});
137
- $info.hide();
138
- $delete.show();
139
- $calEvent.removeClass('fc-short');
140
- });
141
- $calEvent.on('mouseleave', function () {
142
- $calEvent.css({height: origHeight, 'z-index': z_index});
143
- $delete.hide();
144
- $info.show();
145
- $calEvent.addClass('fc-short');
146
  });
147
  }
148
  });
149
  },
150
  // Clicking & Hovering.
151
- dayClick: function(date, jsEvent, view) {
152
  var staff_id, visible_staff_id;
153
  if (view.type == 'multiStaffDay') {
154
  var cell = view.coordMap.getCell(jsEvent.pageX, jsEvent.pageY);
@@ -163,11 +152,10 @@ jQuery(function ($) {
163
  null,
164
  staff_id,
165
  date,
166
- null,
167
- function(event) {
168
  if (visible_staff_id == event.staffId || visible_staff_id == 0) {
169
  // Create event in calendar.
170
- $fullCalendar.fullCalendar('renderEvent', event );
171
  } else {
172
  // Switch to the event owner tab.
173
  jQuery('li[data-staff_id=' + event.staffId + ']').click();
@@ -175,7 +163,7 @@ jQuery(function ($) {
175
  }
176
  );
177
  },
178
- eventClick: function(calEvent, jsEvent, view) {
179
  var visible_staff_id;
180
  if (view.type == 'multiStaffDay') {
181
  visible_staff_id = 0;
@@ -185,10 +173,9 @@ jQuery(function ($) {
185
 
186
  showAppointmentDialog(
187
  calEvent.id,
188
- calEvent.staffId,
189
- calEvent.start,
190
- calEvent.end,
191
- function(event) {
192
  if (visible_staff_id == event.staffId || visible_staff_id == 0) {
193
  // Update event in calendar.
194
  jQuery.extend(calEvent, event);
@@ -201,10 +188,10 @@ jQuery(function ($) {
201
  );
202
  },
203
  loading: function (bool) {
204
- bool ? $('.ab-loading-inner').show() : $('.ab-loading-inner').hide();
205
  },
206
- viewRender: function(view, element){
207
- setCookie('bookly_cal_view',view.type);
208
  }
209
  });
210
 
@@ -217,43 +204,44 @@ jQuery(function ($) {
217
 
218
  // Init date picker for fast navigation in FullCalendar.
219
  var $fcDatePicker = $('<input type=hidden />');
220
- $('.fc-toolbar .fc-center h2').before($fcDatePicker).on('click', function() {
221
  $fcDatePicker.datepicker('setDate', $fullCalendar.fullCalendar('getDate').toDate()).datepicker('show');
222
  });
223
  $fcDatePicker.datepicker({
224
- dayNamesMin : BooklyL10n.shortDays,
225
- monthNames : BooklyL10n.longMonths,
226
- monthNamesShort : BooklyL10n.shortMonths,
227
  firstDay : BooklyL10n.startOfWeek,
228
- beforeShow: function(input, inst){
229
- inst.dpDiv.queue(function() {
230
  inst.dpDiv.css({marginTop: '35px'});
231
  inst.dpDiv.dequeue();
232
  });
233
  },
234
- onSelect: function(dateText, inst) {
235
  var d = new Date(dateText);
236
  $fullCalendar.fullCalendar('gotoDate', d);
237
- if( $fullCalendar.fullCalendar('getView').type != 'agendaDay' &&
238
- $fullCalendar.fullCalendar('getView').type != 'multiStaffDay' ){
239
- $fullCalendar.find('.fc-day').removeClass('ab-fcday-active');
240
- $fullCalendar.find('.fc-day[data-date="' + moment(d).format('YYYY-MM-DD') + '"]').addClass('ab-fcday-active');
 
241
  }
242
  },
243
- onClose: function(dateText, inst) {
244
- inst.dpDiv.queue(function() {
245
  inst.dpDiv.css({marginTop: '0'});
246
  inst.dpDiv.dequeue();
247
  });
248
  }
249
  });
250
 
251
- $(window).on('resize', function() {
252
  $fullCalendar.fullCalendar('option', 'height', heightFC());
253
  });
254
 
255
  // Click on tabs.
256
- $tabs.on('click', function(e) {
257
  e.preventDefault();
258
  $tabs.removeClass('active');
259
  $(this).addClass('active');
@@ -298,7 +286,7 @@ jQuery(function ($) {
298
  updateStaffButton();
299
 
300
  $tabs.filter('[data-staff_id=' + this.value + ']').toggle(this.checked);
301
- if ($tabs.filter(':visible.active').length == 0 ) {
302
  $tabs.filter(':visible:first').triggerHandler('click');
303
  } else if ($tabs.filter('.active').data('staff_id') == 0) {
304
  var view = $fullCalendar.fullCalendar('getView');
@@ -315,7 +303,7 @@ jQuery(function ($) {
315
  // Update staffMembers array.
316
  var ids = [];
317
  staffMembers.length = 0;
318
- $staff.filter(':checked').each(function() {
319
  staffMembers.push({id: this.value, name: this.getAttribute('data-staff_name')});
320
  ids.push(this.value);
321
  });
@@ -340,7 +328,7 @@ jQuery(function ($) {
340
  */
341
  function setCookie(key, value) {
342
  var expires = new Date();
343
- expires.setTime(expires.getTime() + (1 * 24 * 60 * 60 * 1000));
344
  document.cookie = key + '=' + value + ';expires=' + expires.toUTCString();
345
  }
346
 
@@ -363,7 +351,7 @@ jQuery(function ($) {
363
  function heightFC() {
364
  var window_height = $(window).height(),
365
  wp_admin_bar_height = $('#wpadminbar').height(),
366
- ab_calendar_tabs_height = $('#full_calendar_wrapper .tabbable').outerHeight(true),
367
  height_to_reduce = wp_admin_bar_height + ab_calendar_tabs_height,
368
  $wrap = $('#wpbody-content .wrap');
369
 
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 = [],
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
  });
49
  height: heightFC(),
50
  // Views.
51
  defaultView: lastView,
52
+ scrollTime: firstHour + ':00:00',
53
  views: {
54
  agendaWeek: {
55
  columnFormat: 'ddd, D'
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.
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) {
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;
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 () {
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);
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();
163
  }
164
  );
165
  },
166
+ eventClick: function (calEvent, jsEvent, view) {
167
  var visible_staff_id;
168
  if (view.type == 'multiStaffDay') {
169
  visible_staff_id = 0;
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);
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
 
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');
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');
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
  });
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
 
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
 
backend/modules/calendar/resources/js/fullcalendar.min.js CHANGED
@@ -1,8 +1,9 @@
1
  /*!
2
- * FullCalendar v2.3.2
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 J(a,La)}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,La)?(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===Ma&&(Ma=t()),Ma}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(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 B(a,c){return b.duration({days:a.clone().stripTime().diff(c.clone().stripTime(),"days"),ms:a.time()-c.time()})}function C(a,c){return b.duration({days:a.clone().stripTime().diff(c.clone().stripTime(),"days")})}function D(a,c,d){return b.duration(Math.round(a.diff(c,d,!0)),d)}function E(a,b){var c,d,e;for(c=0;c<Ra.length&&(d=Ra[c],e=F(d,a,b),!(e>=1&&W(e)));c++);return d}function F(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 G(a){return Boolean(a.hours()||a.minutes()||a.seconds()||a.milliseconds())}function H(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function I(a){return/^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(a)}function J(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]=J(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 K(a){var b=function(){};return b.prototype=a,new b}function L(a,b){for(var c in a)N(a,c)&&(b[c]=a[c])}function M(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 N(a,b){return Sa.call(a,b)}function O(b){return/undefined|null|boolean|number|string/.test(a.type(b))}function P(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 Q(){for(var a=0;a<arguments.length;a++)if(void 0!==arguments[a])return arguments[a]}function R(a){return(a+"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/'/g,"&#039;").replace(/"/g,"&quot;").replace(/\n/g,"<br />")}function S(a){return a.replace(/&.*?;/g,"")}function T(b){var c=[];return a.each(b,function(a,b){null!=b&&c.push(a+":"+b)}),c.join(";")}function U(a){return a.charAt(0).toUpperCase()+a.slice(1)}function V(a,b){return a-b}function W(a){return a%1===0}function X(a,b){var c=a[b];return function(){return c.apply(a,arguments)}}function Y(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 Z(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),_(j,i)):H(j)||void 0===j?i=b.apply(null,c):(f=!1,g=!1,k?Ta.test(j)?(j+="-01",c=[j],f=!0,g=!0):(h=Ua.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 $(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=Ja.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 _(a,b){a._ambigTime?b._ambigTime=!0:b._ambigTime&&(b._ambigTime=!1),a._ambigZone?b._ambigZone=!0:b._ambigZone&&(b._ambigZone=!1)}function aa(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 ba(a,b){return Wa.format.call(a,b)}function ca(a,b){return da(a,ia(b))}function da(a,b){var c,d="";for(c=0;c<b.length;c++)d+=ea(a,b[c]);return d}function ea(a,b){var c,d;return"string"==typeof b?b:(c=b.token)?Xa[c]?Xa[c](a):ba(a,c):b.maybe&&(d=da(a,b.maybe),d.match(/[1-9]/))?d:""}function fa(a,b,c,d,e){var f;return a=Ja.moment.parseZone(a),b=Ja.moment.parseZone(b),f=(a.localeData||a.lang).call(a),c=f.longDateFormat(c)||c,d=d||" - ",ga(a,b,ia(c),d,e)}function ga(a,b,c,d,e){var f,g,h,i,j="",k="",l="",m="",n="";for(g=0;g<c.length&&(f=ha(a,b,c[g]),f!==!1);g++)j+=f;for(h=c.length-1;h>g&&(f=ha(a,b,c[h]),f!==!1);h--)k=f+k;for(i=g;h>=i;i++)l+=ea(a,c[i]),m+=ea(b,c[i]);return(l||m)&&(n=e?m+d+l:l+d+m),j+n+k}function ha(a,b,c){var d,e;return"string"==typeof c?c:(d=c.token)&&(e=Ya[d.charAt(0)],e&&a.isSame(b,e))?ba(a,d):!1}function ia(a){return a in Za?Za[a]:Za[a]=ja(a)}function ja(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:ja(b[2])}):b[3]?c.push({token:b[3]}):b[5]&&c.push(b[5]);return c}function ka(){}function la(a,b){return a||b?a&&b?a.grid===b.grid&&a.row===b.row&&a.col===b.col:!1:!0}function ma(a){var b=oa(a);return"background"===b||"inverse-background"===b}function na(a){return"inverse-background"===oa(a)}function oa(a){return Q((a.source||{}).rendering,a.rendering)}function pa(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 qa(a,b){return a.eventStartMS-b.eventStartMS}function ra(a,b){return a.eventStartMS-b.eventStartMS||b.eventDurationMS-a.eventDurationMS||b.event.allDay-a.event.allDay||(a.event.title||"").localeCompare(b.event.title)}function sa(c){var d,e,f,g,h=Ja.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 ta(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 ua(a,b){return a.leftCol-b.leftCol}function va(a){var b,c,d;if(a.sort(ra),b=wa(a),xa(b),c=b[0]){for(d=0;d<c.length;d++)ya(c[d]);for(d=0;d<c.length;d++)za(c[d],0,0)}}function wa(a){var b,c,d,e=[];for(b=0;b<a.length;b++){for(c=a[b],d=0;d<e.length&&Aa(c,e[d]).length;d++);c.level=d,(e[d]||(e[d]=[])).push(c)}return e}function xa(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++)Aa(e,a[f],e.forwardSegs)}function ya(a){var b,c,d=a.forwardSegs,e=0;if(void 0===a.forwardPressure){for(b=0;b<d.length;b++)c=d[b],ya(c),e=Math.max(e,1+c.forwardPressure);a.forwardPressure=e}}function za(a,b,c){var d,e=a.forwardSegs;if(void 0===a.forwardCoord)for(e.length?(e.sort(Ca),za(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++)za(e[d],0,a.forwardCoord)}function Aa(a,b,c){c=c||[];for(var d=0;d<b.length;d++)Ba(a,b[d])&&c.push(b[d]);return c}function Ba(a,b){return a.bottom>b.top&&a.top<b.bottom}function Ca(a,b){return b.forwardPressure-a.forwardPressure||(a.backwardCoord||0)-(b.backwardCoord||0)||ra(a,b)}function Da(c,d){function e(){U?h()&&(k(),i()):f()}function f(){V=P.theme?"ui":"fc",c.addClass("fc"),P.isRTL?c.addClass("fc-rtl"):c.addClass("fc-ltr"),P.theme?c.addClass("ui-widget"):c.addClass("fc-unthemed"),U=a("<div class='fc-view-container'/>").prependTo(c),S=O.header=new Ga(O,P),T=S.render(),T&&c.prepend(T),i(P.defaultView),P.handleWindowResize&&(Z=Y(m,P.windowResizeDelay),a(window).resize(Z))}function g(){W&&W.removeElement(),S.removeElement(),U.remove(),c.removeClass("fc fc-ltr fc-rtl fc-unthemed ui-widget"),Z&&a(window).unbind("resize",Z)}function h(){return c.is(":visible")}function i(b){da++,W&&b&&W.type!==b&&(S.deactivateButton(W.type),H(),W.removeElement(),W=O.view=null),!W&&b&&(W=O.view=ca[b]||(ca[b]=O.instantiateView(b)),W.setElement(a("<div class='fc-view fc-"+b+"-view' />").appendTo(U)),S.activateButton(b)),W&&($=W.massageCurrentDate($),W.displaying&&$.isWithin(W.intervalStart,W.intervalEnd)||h()&&(H(),W.display($),I(),u(),v(),q())),I(),da--}function j(a){return h()?(a&&l(),da++,W.updateSize(!0),da--,!0):void 0}function k(){h()&&l()}function l(){X="number"==typeof P.contentHeight?P.contentHeight:"number"==typeof P.height?P.height-(T?T.outerHeight(!0):0):Math.round(U.width()/Math.max(P.aspectRatio,.5))}function m(a){!da&&a.target===window&&W.start&&j(!0)&&W.trigger("windowResize",ba)}function n(){p(),r()}function o(){h()&&(H(),W.displayEvents(ea),I())}function p(){H(),W.clearEvents(),I()}function q(){!P.lazyFetching||_(W.start,W.end)?r():o()}function r(){aa(W.start,W.end)}function s(a){ea=a,o()}function t(){o()}function u(){S.updateTitle(W.title)}function v(){var a=O.getNow();a.isWithin(W.intervalStart,W.intervalEnd)?S.disableButton("today"):S.enableButton("today")}function w(a,b){W.select(O.buildSelectRange.apply(O,arguments))}function x(){W&&W.unselect()}function y(){$=W.computePrevDate($),i()}function z(){$=W.computeNextDate($),i()}function A(){$.add(-1,"years"),i()}function B(){$.add(1,"years"),i()}function C(){$=O.getNow(),i()}function D(a){$=O.moment(a),i()}function E(a){$.add(b.duration(a)),i()}function F(a,b){var c;b=b||"day",c=O.getViewSpec(b)||O.getUnitViewSpec(b),$=a,i(c?c.type:null)}function G(){return $.clone()}function H(){U.css({width:"100%",height:U.height(),overflow:"hidden"})}function I(){U.css({width:"",height:"",overflow:""})}function J(){return O}function L(){return W}function M(a,b){return void 0===b?P[a]:void(("height"==a||"contentHeight"==a||"aspectRatio"==a)&&(P[a]=b,j(!0)))}function N(a,b){return P[a]?P[a].apply(b||ba,Array.prototype.slice.call(arguments,2)):void 0}var O=this;O.initOptions(d||{});var P=this.options;O.render=e,O.destroy=g,O.refetchEvents=n,O.reportEvents=s,O.reportEventChange=t,O.rerenderEvents=o,O.changeView=i,O.select=w,O.unselect=x,O.prev=y,O.next=z,O.prevYear=A,O.nextYear=B,O.today=C,O.gotoDate=D,O.incrementDate=E,O.zoomTo=F,O.getDate=G,O.getCalendar=J,O.getView=L,O.option=M,O.trigger=N;var Q=K(Fa(P.lang));if(P.monthNames&&(Q._months=P.monthNames),P.monthNamesShort&&(Q._monthsShort=P.monthNamesShort),P.dayNames&&(Q._weekdays=P.dayNames),P.dayNamesShort&&(Q._weekdaysShort=P.dayNamesShort),null!=P.firstDay){var R=K(Q._week);R.dow=P.firstDay,Q._week=R}Q._fullCalendar_weekCalc=function(a){return"function"==typeof a?a:"local"===a?a:"iso"===a||"ISO"===a?"ISO":void 0}(P.weekNumberCalculation),O.defaultAllDayEventDuration=b.duration(P.defaultAllDayEventDuration),O.defaultTimedEventDuration=b.duration(P.defaultTimedEventDuration),O.moment=function(){var a;return"local"===P.timezone?(a=Ja.moment.apply(null,arguments),a.hasTime()&&a.local()):a="UTC"===P.timezone?Ja.moment.utc.apply(null,arguments):Ja.moment.parseZone.apply(null,arguments),"_locale"in a?a._locale=Q:a._lang=Q,a},O.getIsAmbigTimezone=function(){return"local"!==P.timezone&&"UTC"!==P.timezone},O.rezoneDate=function(a){return O.moment(a.toArray())},O.getNow=function(){var a=P.now;return"function"==typeof a&&(a=a()),O.moment(a)},O.getEventEnd=function(a){return a.end?a.end.clone():O.getDefaultEventEnd(a.allDay,a.start)},O.getDefaultEventEnd=function(a,b){var c=b.clone();return a?c.stripTime().add(O.defaultAllDayEventDuration):c.add(O.defaultTimedEventDuration),O.getIsAmbigTimezone()&&c.stripZone(),c},O.humanizeDuration=function(a){return(a.locale||a.lang).call(a,P.lang).humanize()},Ha.call(O,P);var S,T,U,V,W,X,Z,$,_=O.isFetchNeeded,aa=O.fetchEvents,ba=c[0],ca={},da=0,ea=[];$=null!=P.defaultDate?O.moment(P.defaultDate):O.getNow(),O.getSuggestedViewHeight=function(){return void 0===X&&k(),X},O.isHeightAuto=function(){return"auto"===P.contentHeight||"auto"===P.height},O.initialize()}function Ea(b){a.each(nb,function(a,c){null==b[a]&&(b[a]=c(b))})}function Fa(a){var c=b.localeData||b.langData;return c.call(b,a)||c.call(b,"en")}function Ga(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;"title"==e?(g=g.add(a("<h2>&nbsp;</h2>")),h=!1):(f=b.getViewSpec(e),f?(i=function(){b.changeView(e)},p.push(e),j=f.buttonTextOverride,k=f.buttonTextDefault):b[e]&&(i=function(){b[e]()},j=(b.overrides.buttonText||{})[e],k=c.buttonText[e]),i&&(l=c.themeButtonIcons[e],m=c.buttonIcons[e],o=j?R(j):l&&c.theme?"<span class='ui-icon ui-icon-"+l+"'></span>":m&&!c.theme?"<span class='fc-icon fc-icon-"+m+"'></span>":R(k),q=["fc-"+e+"-button",n+"-button",n+"-state-default"],r=a('<button type="button" class="'+q.join(" ")+'">'+o+"</button>").click(function(){r.hasClass(n+"-state-disabled")||(i(),(r.hasClass(n+"-state-active")||r.hasClass(n+"-state-disabled"))&&r.removeClass(n+"-state-hover"))}).mousedown(function(){r.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-down")}).mouseup(function(){r.removeClass(n+"-state-down")}).hover(function(){r.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-hover")},function(){r.removeClass(n+"-state-hover").removeClass(n+"-state-down")}),g=g.add(r)))}),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 Ha(c){function d(a,b){return!N||a.clone().stripZone()<N.clone().stripZone()||b.clone().stripZone()>R.clone().stripZone()}function e(a,b){N=a,R=b,X=[];var c=++V,d=U.length;W=d;for(var e=0;d>e;e++)f(U[e],c)}function f(b,c){g(b,function(d){var e,f,g,h=a.isArray(b.events);if(c==V){if(d)for(e=0;e<d.length;e++)f=d[e],g=h?f:s(f,b),g&&X.push.apply(X,x(g));W--,W||S(X)}})}function g(b,d){var e,f,h=Ja.sourceFetchers;for(e=0;e<h.length;e++){if(f=h[e].call(M,b,N.clone(),R.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)?(M.pushLoading(),i.call(M,N.clone(),R.clone(),c.timezone,function(a){d(a),M.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=Q(b.startParam,c.startParam),q=Q(b.endParam,c.endParam),r=Q(b.timezoneParam,c.timezoneParam);p&&(o[p]=N.format()),q&&(o[q]=R.format()),c.timezone&&"local"!=c.timezone&&(o[r]=c.timezone),M.pushLoading(),a.ajax(a.extend({},ob,b,{data:o,success:function(b){b=b||[];var c=P(l,this,arguments);a.isArray(c)&&(b=c),d(b)},error:function(){P(m,this,arguments),d()},complete:function(){P(n,this,arguments),M.popLoading()}}))}else d()}}function h(a){var b=i(a);b&&(U.push(b),W++,f(b,V))}function i(b){var c,d,e=Ja.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(M,c);return c}}function j(b){U=a.grep(U,function(a){return!k(a,b)}),X=a.grep(X,function(a){return!k(a.source,b)}),S(X)}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=M.moment(a.start),a.end?a.end=M.moment(a.end):a.end=null,y(a,n(a)),S(X)}function n(b){var c={};return a.each(b,function(a,b){o(a)&&void 0!==b&&O(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&&(T.events.push(e),e.source=T),X.push(e));return S(X),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}),X=a.grep(X,b,!0),d=0;d<U.length;d++)a.isArray(U[d].events)&&(U[d].events=a.grep(U[d].events,b,!0));S(X)}function r(b){return a.isFunction(b)?a.grep(X,b):null!=b?(b+="",a.grep(X,function(a){return a._id==b})):X}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"+pb++: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,I(f)&&(f=b.duration(f)),I(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=M.moment(f),!f.isValid()))return!1;g&&(g=M.moment(g),g.isValid()||(g=null)),h=d.allDay,void 0===h&&(h=Q(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),Ia(d)}function u(a){v(a),a.end&&!a.end.isAfter(a.start)&&(a.end=null),a.end||(c.forceEventDuration?a.end=M.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=M.rezoneDate(a.start)),a.end&&!a.end.hasTime()&&(a.end=M.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=M.getDefaultEventEnd(c,b.start)),b}function x(b,c,d){var e,f,g,h,i,j,k,l,m,n=[];if(c=c||N,d=d||R,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?D(a,b,d):c.allDay?C(a,b):B(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():M.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=M.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=M.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),Ia(j),i.push(function(){a.extend(j,k),Ia(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=M.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 E(a,b){var d=b.source||{},e=Q(b.constraint,d.constraint,c.eventConstraint),f=Q(b.overlap,d.overlap,c.eventOverlap);return a=w(a),H(a,e,f,b)}function F(a){return H(a,c.selectConstraint,c.selectOverlap)}function G(b,c){var d,e;return c&&(d=a.extend({},c,b),e=x(s(d))[0]),e?E(b,e):(b=w(b),F(b))}function H(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=J(c),g=!1,i=0;i<f.length;i++)if(K(f[i],b)){g=!0;break}if(!g)return!1}for(h=M.getPeerEvents(e,b),i=0;i<h.length;i++)if(j=h[i],L(j,b)){if(d===!1)return!1;if("function"==typeof d&&!d(j,e))return!1;if(e){if(k=Q(j.overlap,(j.source||{}).overlap),k===!1)return!1;if("function"==typeof k&&!k(e,j))return!1}}return!0}function J(a){return"businessHours"===a?A():"object"==typeof a?x(s(a)):r(a)}function K(a,b){var c=a.start.clone().stripZone(),d=M.getEventEnd(a).stripZone();return b.start>=c&&b.end<=d}function L(a,b){var c=a.start.clone().stripZone(),d=M.getEventEnd(a).stripZone();return b.start<d&&b.end>c}var M=this;M.isFetchNeeded=d,M.fetchEvents=e,M.addEventSource=h,M.removeEventSource=j,M.updateEvent=m,M.renderEvent=p,M.removeEvents=q,M.clientEvents=r,M.mutateEvent=y,M.normalizeEventRange=u,M.normalizeEventRangeTimes=v,M.ensureVisibleEventRange=w;var N,R,S=M.reportEvents,T={events:[]},U=[T],V=0,W=0,X=[];a.each((c.events?[c.events]:[]).concat(c.eventSources||[]),function(a,b){var c=i(b);c&&U.push(c)}),M.getBusinessHoursEvents=A,M.isEventRangeAllowed=E,M.isSelectionRangeAllowed=F,M.isExternalDropRangeAllowed=G,M.getEventCache=function(){return X}}function Ia(a){a._allDay=a.allDay,a._start=a.start.clone(),a._end=a.end?a.end.clone():null}var Ja=a.fullCalendar={version:"2.3.2"},Ka=Ja.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 jb(h,b),h.data("fullCalendar",i),i.render())}),d};var La=["header","buttonText","buttonIcons","themeButtonIcons"];Ja.intersectionToSeg=A,Ja.applyAll=P,Ja.debounce=Y,Ja.isInt=W,Ja.htmlEscape=R,Ja.cssToStr=T,Ja.proxy=X,Ja.capitaliseFirstLetter=U,Ja.getClientRect=p,Ja.getContentRect=q,Ja.getScrollbarWidths=r;var Ma=null;Ja.computeIntervalUnit=E,Ja.durationHasTime=G;var Na,Oa,Pa,Qa=["sun","mon","tue","wed","thu","fri","sat"],Ra=["year","month","week","day","hour","minute","second","millisecond"],Sa={}.hasOwnProperty,Ta=/^\s*\d{4}-\d\d$/,Ua=/^\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+)?)?)?)?)?$/,Va=b.fn,Wa=a.extend({},Va);Ja.moment=function(){return Z(arguments)},Ja.moment.utc=function(){var a=Z(arguments,!0);return a.hasTime()&&a.utc(),a},Ja.moment.parseZone=function(){return Z(arguments,!0,!0)},Va.clone=function(){var a=Wa.clone.apply(this,arguments);return _(this,a),this._fullCalendar&&(a._fullCalendar=!0),a},Va.week=Va.weeks=function(a){var b=(this._locale||this._lang)._fullCalendar_weekCalc;return null==a&&"function"==typeof b?b(this):"ISO"===b?Wa.isoWeek.apply(this,arguments):Wa.week.apply(this,arguments)},Va.time=function(a){if(!this._fullCalendar)return Wa.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())},Va.stripTime=function(){var a;return this._ambigTime||(a=this.toArray(),this.utc(),Oa(this,a.slice(0,3)),this._ambigTime=!0,this._ambigZone=!0),this},Va.hasTime=function(){return!this._ambigTime},Va.stripZone=function(){var a,b;return this._ambigZone||(a=this.toArray(),b=this._ambigTime,this.utc(),Oa(this,a),this._ambigTime=b||!1,this._ambigZone=!0),this},Va.hasZone=function(){return!this._ambigZone},Va.local=function(){var a=this.toArray(),b=this._ambigZone;return Wa.local.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,b&&Pa(this,a),this},Va.utc=function(){return Wa.utc.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,this},a.each(["zone","utcOffset"],function(a,b){Wa[b]&&(Va[b]=function(a){return null!=a&&(this._ambigTime=!1,this._ambigZone=!1),Wa[b].apply(this,arguments)})}),Va.format=function(){return this._fullCalendar&&arguments[0]?ca(this,arguments[0]):this._ambigTime?ba(this,"YYYY-MM-DD"):this._ambigZone?ba(this,"YYYY-MM-DD[T]HH:mm:ss"):Wa.format.apply(this,arguments)},Va.toISOString=function(){return this._ambigTime?ba(this,"YYYY-MM-DD"):this._ambigZone?ba(this,"YYYY-MM-DD[T]HH:mm:ss"):Wa.toISOString.apply(this,arguments)},Va.isWithin=function(a,b){var c=$([this,a,b]);return c[0]>=c[1]&&c[0]<c[2]},Va.isSame=function(a,b){var c;return this._fullCalendar?b?(c=$([this,a],!0),Wa.isSame.call(c[0],c[1],b)):(a=Ja.moment.parseZone(a),Wa.isSame.call(this,a)&&Boolean(this._ambigTime)===Boolean(a._ambigTime)&&Boolean(this._ambigZone)===Boolean(a._ambigZone)):Wa.isSame.apply(this,arguments)},a.each(["isBefore","isAfter"],function(a,b){Va[b]=function(a,c){var d;return this._fullCalendar?(d=$([this,a]),Wa[b].call(d[0],d[1],c)):Wa[b].apply(this,arguments)}}),Na="_d"in b()&&"updateOffset"in b,Oa=Na?function(a,c){a._d.setTime(Date.UTC.apply(Date,c)),b.updateOffset(a,!1)}:aa,Pa=Na?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)}:aa;var Xa={t:function(a){return ba(a,"a").charAt(0)},T:function(a){return ba(a,"A").charAt(0)}};Ja.formatRange=fa;var Ya={Y:"year",M:"month",D:"day",d:"day",A:"second",a:"second",T:"second",t:"second",H:"second",h:"second",m:"second",s:"second"},Za={};Ja.Class=ka,ka.extend=function(a){var b,c=this;return a=a||{},N(a,"constructor")&&(b=a.constructor),"function"!=typeof b&&(b=a.constructor=function(){c.apply(this,arguments)}),b.prototype=K(c.prototype),L(a,b.prototype),M(a,b.prototype),L(c,b),b},ka.mixin=function(a){L(a.prototype||a,this.prototype)};var $a=ka.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=X(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))}}),_a=ka.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}}),ab=ka.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()}}),bb=Ja.DragListener=ka.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,
7
- 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=Y(X(this,"scrollHandler"),100),this.scrollEl.on("scroll",this.scrollHandlerProxy))),a(document).on("mousemove",this.mousemoveProxy=X(this,"mousemove")).on("mouseup",this.mouseupProxy=X(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(X(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(){}}),cb=bb.extend({coordMap:null,origCell:null,cell:null,coordAdjust:null,constructor:function(a,b){bb.prototype.constructor.call(this,b),this.coordMap=a},listenStart:function(a){var b,c,d,e=this.subjectEl;bb.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;bb.prototype.dragStart.apply(this,arguments),b=this.getCell(a.pageX,a.pageY),b&&this.cellOver(b)},drag:function(a,b,c){var d;bb.prototype.drag.apply(this,arguments),d=this.getCell(c.pageX,c.pageY),la(d,this.cell)||(this.cell&&this.cellOut(),d&&this.cellOver(d))},dragStop:function(){this.cellDone(),bb.prototype.dragStop.apply(this,arguments)},cellOver:function(a){this.cell=a,this.trigger("cellOver",a,la(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(){bb.prototype.listenStop.apply(this,arguments),this.origCell=this.cell=null,this.coordMap.clear()},scrollStop:function(){bb.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)}}),db=ka.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=X(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())}}),eb=ka.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+U(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||""}}}),fb=Ja.Grid=eb.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(){eb.apply(this,arguments),this.coordMap=new _a(this),this.elsByFill={},this.externalDragStartProxy=X(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?D(a,b,this.largeUnit):B(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 cb(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?K(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(V),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=T(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()]+'">'+R(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}});fb.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],ma(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 db(a.el,{parentEl:e.el,opacity:e.opt("dragOpacity"),revertDuration:e.opt("dragRevertDuration"),zIndex:2}),l=new cb(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&&G(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=sa(a);d=new cb(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 cb(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&&G(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=pa(b),e=[];return a.each(d,function(a,b){b.length&&e.push.apply(e,na(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(qa),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}}),Ja.compareSegs=ra,Ja.dataAttrPrefix="";var gb=fb.extend({numbersVisible:!1,bottomCoordPadding:0,breakOnWeeks:null,cellDates:null,dayToCellOffsets:null,rowEls:null,dayEls:null,helperEls:null,constructor:function(){fb.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=fb.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}});gb.mixin({rowStructs:null,unrenderEvents:function(){this.removeSegPopover(),fb.prototype.unrenderEvents.apply(this,arguments)},getEventSegs:function(){return fb.prototype.getEventSegs.call(this).concat(this.popoverSegs||[])},renderBgSegs:function(b){var c=a.grep(b,function(a){return a.event.allDay});return fb.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=T(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">'+R(c)+"</span>")),d='<span class="fc-title">'+(R(f.title||"")||"&nbsp;")+"</span>",'<a class="'+j.join(" ")+'"'+(f.url?' href="'+R(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(a.sort(ra),b=0;b<a.length;b++){for(c=a[b],d=0;d<e.length&&ta(c,e[d]);d++);c.level=d,(e[d]||(e[d]=[])).push(c)}for(d=0;d<e.length;d++)e[d].sort(ua);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}}),gb.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)}},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 $a(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">'+R(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,
8
- 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=A(a,g);return b?[b]:[]}),b.sort(ra),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 hb=fb.extend({slotDuration:null,snapDuration:null,minTime:null,maxTime:null,colDates:null,axisFormat:null,dayEls:null,slatEls:null,slatTops:null,helperEl:null,businessHourSegs:null,constructor:function(){fb.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=this.slotDuration.asMinutes()%15===0,i=b.duration(+this.minTime);i<this.maxTime;)a=this.start.clone().time(i),c=a.minutes(),d='<td class="fc-axis fc-time '+e.widgetContentClass+'" '+e.axisStyleAttr()+">"+(h&&c?"":"<span>"+R(a.format(this.axisFormat))+"</span>")+"</td>",g+="<tr "+(c?'class="fc-minor"':"")+">"+(f?"":d)+'<td class="'+e.widgetContentClass+'"/>'+(f?d:"")+"</tr>",i.add(this.slotDuration);return g},processOptions:function(){var a=this.view,c=a.opt("slotDuration"),d=a.opt("snapDuration");c=b.duration(c),d=d?b.duration(d):c,this.slotDuration=c,this.snapDuration=d,this.cellDuration=d,this.minTime=b.duration(a.opt("minTime")),this.maxTime=b.duration(a.opt("maxTime")),this.axisFormat=a.opt("axisFormat")||a.opt("smallTimeFormat")},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=A(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}});hb.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],va(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},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=T(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="'+R(g.url)+'"':"")+(l?' style="'+l+'"':"")+'><div class="fc-content">'+(c?'<div class="fc-time" data-start="'+R(e)+'" data-full="'+R(d)+'"><span>'+R(c)+"</span></div>":"")+(g.title?'<div class="fc-title">'+R(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}});var ib=Ja.View=ka.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,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.documentMousedownProxy=X(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=E(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)),fa(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 Q(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 Q(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}}),jb=Ja.Calendar=ka.extend({dirDefaults:null,langDefaults:null,overrides:null,options:null,viewSpecCache:null,view:null,header:null,loadingLevel:0,constructor:Da,initialize:function(){},initOptions:function(a){var b,e,f,g;a=d(a),b=a.lang,e=kb[b],e||(b=jb.defaults.lang,e=kb[b]||{}),f=Q(a.isRTL,e.isRTL,jb.defaults.isRTL),g=f?jb.rtlDefaults:{},this.dirDefaults=g,this.langDefaults=e,this.overrides=a,this.options=c([jb.defaults,g,e,a]),Ea(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(Ja.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=Ka[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=J(i),d.type=a,d["class"]?(f&&(f=b.duration(f),f.valueOf()&&(d.duration=f,g=E(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([jb.defaults,a.defaults,this.dirDefaults,this.langDefaults,this.overrides,a.overrides]),Ea(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(jb.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}}});jb.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:"*",eventLimit:!1,eventLimitText:"more",eventLimitClick:"popover",dayPopoverFormat:"LL",handleWindowResize:!0,windowResizeDelay:200},jb.englishDefaults={dayPopoverFormat:"dddd, MMMM D"},jb.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 kb=Ja.langs={};Ja.datepickerLang=function(b,c,d){var e=kb[b]||(kb[b]={});e.isRTL=d.isRTL,e.weekNumberTitle=d.weekHeader,a.each(lb,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))},Ja.lang=function(b,d){var e,f;e=kb[b]||(kb[b]={}),d&&(e=kb[b]=c([e,d])),f=Fa(b),a.each(mb,function(a,b){null==e[a]&&(e[a]=b(f,e))}),jb.defaults.lang=b};var lb={buttonText:function(a){return{prev:S(a.prevText),next:S(a.nextText),today:S(a.currentText)}},monthYearFormat:function(a){return a.showMonthAfterYear?"YYYY["+a.yearSuffix+"] MMMM":"MMMM YYYY["+a.yearSuffix+"]"}},mb={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,"")}},nb={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"}};Ja.lang("en",jb.englishDefaults),Ja.sourceNormalizers=[],Ja.sourceFetchers=[];var ob={dataType:"json",cache:!1},pb=1;jb.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 qb=ib.extend({dayGrid:null,dayNumbersVisible:!1,weekNumbersVisible:!1,weekNumberWidth:null,headRowEl:null,initialize:function(){this.dayGrid=new gb(this),this.coordMap=this.dayGrid.coordMap},setRange:function(a){ib.prototype.setRange.call(this,a),this.dayGrid.breakOnWeeks=/year|month|week/.test(this.intervalUnit),this.dayGrid.setRange(a)},computeRange:function(a){var b=ib.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>"+R(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()}}),rb=qb.extend({computeRange:function(a){var b,c=qb.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")}});Ka.basic={"class":qb},Ka.basicDay={type:"basic",duration:{days:1}},Ka.basicWeek={type:"basic",duration:{weeks:1}},Ka.month={"class":rb,duration:{months:1},defaults:{fixedWeekCount:!0}};var sb=ib.extend({timeGrid:null,dayGrid:null,axisWidth:null,noScrollRowEls:null,bottomRuleEl:null,bottomRuleHeight:null,initialize:function(){this.timeGrid=new hb(this),this.opt("allDaySlot")?(this.dayGrid=new gb(this),this.coordMap=new ab([this.dayGrid.coordMap,this.timeGrid.coordMap])):this.coordMap=this.timeGrid.coordMap},setRange:function(a){ib.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>"+R(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")||R(this.opt("allDayText")))+"</span></td>"},slotBgIntroHtml:function(){return'<td class="fc-axis '+this.widgetContentClass+'" '+this.axisStyleAttr()+"></td>"},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),ib.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=tb),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()}}),tb=5;return Ka.agenda={"class":sb,defaults:{allDaySlot:!0,allDayText:"all-day",slotDuration:"00:30:00",minTime:"00:00:00",maxTime:"24:00:00",slotEventOverlap:!0}},Ka.agendaDay={type:"agenda",duration:{days:1}},Ka.agendaWeek={type:"agenda",duration:{weeks:1}},Ja});
 
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/{resources/js/ng-edit_appointment_dialog.js → modules/calendar/resources/js/ng-appointment_dialog.js} RENAMED
@@ -1,18 +1,23 @@
1
  ;(function() {
2
 
3
- var module = angular.module('appointmentForm', ['ui.date', 'newCustomerDialog']);
4
 
5
  /**
6
  * DataSource service.
7
  */
8
  module.factory('dataSource', function($q, $rootScope, $filter) {
9
  var ds = {
 
10
  data : {
11
  staff : [],
12
  customers : [],
13
  start_time : [],
14
  end_time : [],
15
- time_interval : 900
 
 
 
 
16
  },
17
  form : {
18
  id : null,
@@ -22,27 +27,33 @@
22
  start_time : null,
23
  end_time : null,
24
  customers : [],
25
- email_notification : null
26
  },
27
  loadData : function() {
28
  var deferred = $q.defer();
29
- jQuery.get(
30
- ajaxurl,
31
- { action : 'ab_get_data_for_appointment_form' },
32
- function(data) {
33
- ds.data = data;
34
- // Add empty element to beginning of array for single-select customer form
35
- ds.data.customers.unshift({name: ''});
36
-
37
- if (data.staff.length) {
38
- ds.form.staff = data.staff[0];
39
- }
40
- ds.form.start_time = data.start_time[0];
41
- ds.form.end_time = data.end_time[1];
42
- deferred.resolve();
43
- },
44
- 'json'
45
- );
 
 
 
 
 
 
46
  return deferred.promise;
47
  },
48
  findStaff : function(id) {
@@ -94,8 +105,15 @@
94
  },
95
  resetCustomers : function() {
96
  ds.data.customers.forEach(function(customer) {
97
- customer.custom_fields = [];
 
 
98
  customer.number_of_persons = 1;
 
 
 
 
 
99
  });
100
  },
101
  getDataForEndTime : function() {
@@ -143,16 +161,26 @@
143
  end_date.setMinutes(end_time[1]);
144
 
145
  return {
146
- start_date : $filter('date')(start_date, 'yyyy-MM-dd HH:mm:ss'),
147
- end_date : $filter('date')(end_date, 'yyyy-MM-dd HH:mm:ss')
148
  };
149
  },
150
  getTotalNumberOfPersons : function () {
151
  var result = 0;
152
- ds.form.customers.forEach(function(item) {
153
  result += parseInt(item.number_of_persons);
154
  });
155
 
 
 
 
 
 
 
 
 
 
 
156
  return result;
157
  }
158
  };
@@ -161,19 +189,14 @@
161
  });
162
 
163
  /**
164
- * Controller for "create/edit appointment" dialog form.
165
  */
166
  module.controller('appointmentDialogCtrl', function($scope, $element, dataSource) {
167
  // Set up initial data.
168
- $scope.loading = true;
169
  $scope.$calendar = null;
170
  // Set up data source.
171
  $scope.dataSource = dataSource;
172
  $scope.form = dataSource.form; // shortcut
173
- // Populate data source.
174
- dataSource.loadData().then(function() {
175
- $scope.loading = false;
176
- });
177
  // Error messages.
178
  $scope.errors = {};
179
  // Callback to be called after editing appointment.
@@ -187,6 +210,7 @@
187
  * @param function _callback
188
  */
189
  $scope.configureNewForm = function(staff_id, start_date, _callback) {
 
190
  jQuery.extend($scope.form, {
191
  id : null,
192
  staff : dataSource.findStaff(staff_id),
@@ -195,47 +219,74 @@
195
  start_time : dataSource.findTime('start', start_date.format('HH:mm')),
196
  end_time : null,
197
  customers : [],
198
- email_notification : null
 
199
  });
200
  $scope.errors = {};
201
  dataSource.setEndTimeBasedOnService();
202
  callback = _callback;
203
 
204
  $scope.reInitChosen();
 
 
205
  $scope.dataSource.resetCustomers();
206
  };
207
 
208
  /**
209
- * Prepare the form for editing event.
210
  */
211
- $scope.configureEditForm = function(appointment_id, staff_id, start_date, end_date, _callback) {
212
  $scope.loading = true;
213
  jQuery.post(
214
  ajaxurl,
215
- { action : 'ab_get_data_for_appointment', id : appointment_id },
216
  function(response) {
217
  $scope.$apply(function($scope) {
218
  if (response.success) {
 
 
219
  jQuery.extend($scope.form, {
220
  id : appointment_id,
221
- staff : $scope.dataSource.findStaff(staff_id),
222
- service : $scope.dataSource.findService(staff_id, response.data.service_id),
223
  date : start_date.clone().local().toDate(),
224
  start_time : $scope.dataSource.findTime('start', start_date.format('HH:mm')),
225
  end_time : start_date.format('YYYY-MM-DD') == end_date.format('YYYY-MM-DD')
226
  ? $scope.dataSource.findTime('end', end_date.format('HH:mm'))
227
  : $scope.dataSource.findTime('end', (24 + end_date.hour()) + end_date.format(':mm')),
228
- customers : []
 
 
229
  });
230
 
231
  $scope.reInitChosen();
 
 
232
  $scope.dataSource.resetCustomers();
233
 
234
- response.data.customers.forEach(function(item, i, arr) {
235
- var customer = $scope.dataSource.findCustomer(item.id);
236
- customer.custom_fields = item.custom_fields;
237
- customer.number_of_persons = item.number_of_persons;
238
- $scope.form.customers.push(customer);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  });
240
  }
241
  $scope.loading = false;
@@ -259,8 +310,8 @@
259
  staff_id : $scope.form.staff ? $scope.form.staff.id : null,
260
  service_id : $scope.form.service ? $scope.form.service.id : null
261
  },
262
- function(response){
263
- $scope.$apply(function($scope) {
264
  $scope.errors = response;
265
  });
266
  },
@@ -271,6 +322,8 @@
271
  $scope.onServiceChange = function() {
272
  $scope.dataSource.setEndTimeBasedOnService();
273
  $scope.reInitChosen();
 
 
274
  checkTimeInterval();
275
  };
276
 
@@ -291,13 +344,26 @@
291
  $scope.loading = true;
292
 
293
  var dates = $scope.dataSource.getStartAndEndDates(),
294
- customers = [];
295
-
296
- $scope.form.customers.forEach(function(item, i, arr){
 
 
 
 
 
 
 
 
 
297
  customers.push({
298
  id : item.id,
 
299
  custom_fields : item.custom_fields,
300
- number_of_persons : item.number_of_persons
 
 
 
301
  });
302
  });
303
 
@@ -311,7 +377,8 @@
311
  start_date : dates.start_date,
312
  end_date : dates.end_date,
313
  customers : JSON.stringify(customers),
314
- email_notification : $scope.form.email_notification
 
315
  },
316
  function (response) {
317
  $scope.$apply(function($scope) {
@@ -333,12 +400,12 @@
333
  };
334
 
335
  // On 'Cancel' button click.
336
- $scope.closeDialog = function() {
337
  // Close the dialog.
338
  $element.children().modal('hide');
339
  };
340
 
341
- $scope.reInitChosen = function(){
342
  jQuery('#chosen')
343
  .chosen('destroy')
344
  .chosen({
@@ -348,6 +415,10 @@
348
  });
349
  };
350
 
 
 
 
 
351
  /**************************************************************************************************************
352
  * New customer *
353
  **************************************************************************************************************/
@@ -362,7 +433,14 @@
362
  id : customer.id.toString(),
363
  name : customer.name,
364
  custom_fields : customer.custom_fields,
365
- number_of_persons : 1
 
 
 
 
 
 
 
366
  };
367
 
368
  if (customer.email || customer.phone){
@@ -372,109 +450,186 @@
372
  dataSource.data.customers.push(new_customer);
373
 
374
  // Make it selected.
375
- if (!dataSource.form.service || dataSource.form.customers.length < dataSource.form.service.capacity){
376
  dataSource.form.customers.push(new_customer);
377
  }
378
 
379
- setTimeout(function() { jQuery("#chosen").trigger("chosen:updated"); }, 0);
380
  };
381
 
382
  $scope.removeCustomer = function(customer) {
383
- customer.custom_fields = [];
384
- customer.number_of_persons = 1;
385
  $scope.form.customers.splice($scope.form.customers.indexOf(customer), 1);
386
  };
387
 
388
  /**************************************************************************************************************
389
- * Custom fields *
390
  **************************************************************************************************************/
391
 
392
- $scope.editCustomFields = function(customer) {
393
- var $form = jQuery('#ab_custom_fields_dialog form');
394
- $form.find('input.ab-custom-field:text, textarea.ab-custom-field, select.ab-custom-field').val('');
395
- $form.find('input.ab-custom-field:checkbox, input.ab-custom-field:radio').prop('checked', false);
 
396
 
397
- customer.custom_fields.forEach(function(field) {
398
- var $field = $form.find('.ab-formField[data-id="' + field.id + '"]');
399
- switch ($field.data('type')) {
400
  case 'checkboxes':
401
- field.value.forEach(function(value) {
402
- $field.find('.ab-custom-field').filter(function() {
403
  return this.value == value;
404
  }).prop('checked', true);
405
  });
406
  break;
407
  case 'radio-buttons':
408
- $field.find('.ab-custom-field').filter(function() {
409
  return this.value == field.value;
410
  }).prop('checked', true);
411
  break;
412
  default:
413
- $field.find('.ab-custom-field').val(field.value);
414
  break;
415
  }
416
  });
417
 
 
 
 
 
418
  // Prepare select for number of persons.
419
- var $number_of_persons = $form.find('#ab-edit-number-of-persons');
 
420
  var max = $scope.form.service
421
- ? parseInt($scope.form.service.capacity) - $scope.dataSource.getTotalNumberOfPersons() + parseInt(customer.number_of_persons)
422
  : 1;
423
  $number_of_persons.empty();
424
- for (var i = 1; i <= max; ++ i) {
425
- $number_of_persons.append('<option value="' + i +'">' + i + '</option>');
426
  }
427
  if (customer.number_of_persons > max) {
428
- $number_of_persons.append('<option value="' + customer.number_of_persons +'">' + customer.number_of_persons + '</option>');
429
  }
430
  $number_of_persons.val(customer.number_of_persons);
431
-
432
- // this is used in SaveCustomFields()
 
433
  $scope.edit_customer = customer;
434
 
435
- jQuery('#ab_custom_fields_dialog').modal({show:true, backdrop: false});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  };
437
 
438
  $scope.saveCustomFields = function() {
439
  var result = [],
440
- $fields = jQuery('#ab_custom_fields_dialog .ab-formField'),
441
- $number_of_persons = jQuery('#ab_custom_fields_dialog #ab-edit-number-of-persons');
442
-
443
- $fields.each(function() {
444
- var $this = jQuery(this);
445
- var value;
446
- switch ($this.data('type')) {
447
- case 'checkboxes':
448
- value = [];
449
- $this.find('.ab-custom-field:checked').each(function() {
450
- value.push(this.value);
451
- });
452
- break;
453
- case 'radio-buttons':
454
- value = $this.find('.ab-custom-field:checked').val();
455
- break;
456
- default:
457
- value = $this.find('.ab-custom-field').val();
458
- break;
 
 
 
 
 
459
  }
460
- result.push({ id: $this.data('id'), value: value });
461
  });
462
 
 
 
 
 
 
 
 
 
463
  $scope.edit_customer.custom_fields = result;
464
  $scope.edit_customer.number_of_persons = $number_of_persons.val();
 
 
 
 
 
 
 
 
 
 
465
 
466
- jQuery('#ab_custom_fields_dialog').modal('hide');
 
 
 
 
 
 
467
  };
468
 
469
  /**
470
  * Datepicker options.
471
  */
472
  $scope.dateOptions = {
473
- dateFormat : BooklyL10n.dpDateFormat,
474
- dayNamesMin : BooklyL10n.shortDays,
475
- monthNames : BooklyL10n.longMonths,
476
- monthNamesShort : BooklyL10n.shortMonths,
477
- firstDay : BooklyL10n.startOfWeek
478
  };
479
  });
480
 
@@ -501,12 +656,12 @@
501
  module.directive('chosen',function($timeout) {
502
  var linker = function(scope,element,attrs) {
503
  scope.$watch(attrs['chosen'], function() {
504
- element.trigger("chosen:updated");
505
  });
506
 
507
  scope.$watchCollection(attrs['ngModel'], function() {
508
  $timeout(function() {
509
- element.trigger("chosen:updated");
510
  });
511
  });
512
 
@@ -520,53 +675,55 @@
520
  });
521
 
522
  /**
523
- * Directive for Popover jQuery plugin, message in data-content
524
  */
525
- module.directive('content', function() {
526
  return function(scope, element, attrs) {
527
  element.popover({
528
  trigger : 'hover',
529
- content : element.data('content'),
530
- html : true
 
 
531
  });
532
  };
533
  });
534
-
535
  })();
536
 
537
  /**
538
  * @param int appointment_id
539
  * @param int staff_id
540
  * @param moment start_date
541
- * @param moment end_date
542
  * @param function callback
543
  */
544
- var showAppointmentDialog = function (appointment_id, staff_id, start_date, end_date, callback) {
545
- var $scope = angular.element(document.getElementById('ab_appointment_dialog')).scope(),
546
- title = null;
547
  $scope.$apply(function ($scope) {
548
- var $modal_title = jQuery('#ab_appointment_dialog').find('.modal-title');
549
- if (appointment_id) {
550
- $scope.configureEditForm(appointment_id, staff_id, start_date, end_date, callback);
551
- title = BooklyL10n.editAppointment;
552
- $modal_title.text(title);
553
- } else {
554
- $scope.configureNewForm(staff_id, start_date, callback);
555
- title = BooklyL10n.newAppointment;
556
- $modal_title.text(title);
557
- }
 
 
 
558
  });
559
 
560
- // hide custom field dialog, if it remained opened.
561
- if (jQuery('#ab_custom_fields_dialog').hasClass('in')) {
562
- jQuery('#ab_custom_fields_dialog').modal('hide');
563
  }
564
 
565
  // hide new customer dialog, if it remained opened.
566
- if (jQuery('#ab_new_customer_dialog').hasClass('in')) {
567
- jQuery('#ab_new_customer_dialog').modal('hide');
568
  }
569
 
570
- jQuery('#ab_appointment_dialog').modal('show');
571
-
572
- }
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,
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) {
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() {
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
  };
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.
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),
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;
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
  },
322
  $scope.onServiceChange = function() {
323
  $scope.dataSource.setEndTimeBasedOnService();
324
  $scope.reInitChosen();
325
+ $scope.prepareExtras();
326
+ $scope.prepareCustomFields();
327
  checkTimeInterval();
328
  };
329
 
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
 
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) {
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({
415
  });
416
  };
417
 
418
+ $scope.statusToString = function (status) {
419
+ return dataSource.data.status.items[status];
420
+ };
421
+
422
  /**************************************************************************************************************
423
  * New customer *
424
  **************************************************************************************************************/
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){
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
 
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
 
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
+ };
 
backend/modules/calendar/templates/_appointment_dialog.php ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div ng-app="appointmentDialog" ng-controller="appointmentDialogCtrl">
3
+ <div id=bookly-appointment-dialog class="modal fade" tabindex=-1 role="dialog">
4
+ <div class="modal-dialog">
5
+ <div class="modal-content">
6
+ <form ng-submit=processForm()>
7
+ <div class="modal-header">
8
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
9
+ <div class="modal-title h2"><?php _e( 'New appointment', 'bookly' ) ?></div>
10
+ </div>
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>
18
+ </div>
19
+
20
+ <div class=form-group>
21
+ <label for="ab_service"><?php _e( 'Service', 'bookly' ) ?></label>
22
+ <select id="ab_service" class="field form-control" ng-model="form.service"
23
+ ng-options="s.title for s in form.staff.services" ng-change="onServiceChange()">
24
+ <option value=""><?php _e( '-- Select a service --', 'bookly' ) ?></option>
25
+ </select>
26
+ <p class="text-danger" my-slide-up="errors.service_required">
27
+ <?php _e( 'Please select a service', 'bookly' ) ?>
28
+ </p>
29
+ </div>
30
+
31
+ <div class="row">
32
+ <div class="col-sm-4">
33
+ <div class=form-group>
34
+ <label for="ab_date"><?php _e( 'Date', 'bookly' ) ?></label>
35
+ <input id="ab_date" class="form-control" type=text
36
+ ng-model=form.date ui-date="dateOptions" autocomplete="off">
37
+ </div>
38
+ </div>
39
+ <div class="col-sm-8">
40
+ <div class="form-group">
41
+ <div ng-hide="form.service.duration == 86400">
42
+ <label for="ab_period"><?php _e( 'Period', 'bookly' ) ?></label>
43
+ <div class="bookly-flexbox">
44
+ <div class="bookly-flex-cell">
45
+ <select id="ab_period" class="form-control" ng-model=form.start_time
46
+ ng-options="t.title for t in dataSource.data.start_time"
47
+ ng-change=onStartTimeChange()></select>
48
+ </div>
49
+ <div class="bookly-flex-cell" style="width: 4%">
50
+ <div class="bookly-margin-horizontal-md"><?php _e( 'to', 'bookly' ) ?></div>
51
+ </div>
52
+ <div class="bookly-flex-cell" style="width: 48%">
53
+ <select class="form-control" ng-model=form.end_time
54
+ ng-options="t.title for t in dataSource.getDataForEndTime()"
55
+ ng-change=onEndTimeChange()></select>
56
+ </div>
57
+ </div>
58
+ <p class="text-success" my-slide-up=errors.date_interval_warning id=date_interval_warning_msg>
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>
75
+ <ul class="ab-customer-list">
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>
100
+ <span class="input-group-btn">
101
+ <a href="#bookly-customer-dialog" class="btn btn-success" data-toggle="modal">
102
+ <i class="glyphicon glyphicon-plus"></i>
103
+ <?php _e( 'New customer', 'bookly' ) ?>
104
+ </a>
105
+ </span>
106
+ </div>
107
+ <p class="text-danger" my-slide-up="errors.customers_required">
108
+ <?php _e( 'Please select a customer', 'bookly' ) ?>
109
+ </p>
110
+ <p class="text-danger" my-slide-up="errors.overflow_capacity" ng-bind="errors.overflow_capacity"></p>
111
+ </div>
112
+ </div>
113
+ </div>
114
+
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>
122
+ </select>
123
+ </div>
124
+
125
+ <div class=form-group>
126
+ <label for="ab_internal_note"><?php _e( 'Internal note', 'bookly' ) ?></label>
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>
139
+ </div><!-- /.modal-content -->
140
+ </div><!-- /.modal-dialog -->
141
+ </div><!-- /.modal -->
142
+ <?php ?>
143
+ <div customer-dialog=createCustomer(customer)></div>
144
+ <div payment-details-dialog="completePayment(payment_id, payment_title)"></div>
145
+
146
+ <?php include '_customer_details_dialog.php' ?>
147
+ <?php \BooklyLite\Backend\Modules\Customers\Components::getInstance()->renderCustomerDialog() ?>
148
+ <?php \BooklyLite\Backend\Modules\Payments\Components::getInstance()->renderPaymentDetailsDialog() ?>
149
+ </div>
backend/modules/calendar/templates/_appointment_form.php DELETED
@@ -1,108 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <style>
3
- .search-choice { display: none; }
4
- </style>
5
- <div ng-controller=appointmentDialogCtrl>
6
- <div id=ab_appointment_dialog class="modal fade">
7
- <div class="modal-dialog">
8
- <div ng-show=loading class="modal-content loading-indicator">
9
- <div class="modal-body">
10
- <span class="ab-loader"></span>
11
- </div>
12
- </div>
13
- <div ng-hide=loading class="modal-content">
14
- <form ng-submit=processForm() class=form-horizontal>
15
- <div class="modal-header">
16
- <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
17
- <h4 class="modal-title"><?php _e( 'New appointment', 'bookly' ) ?></h4>
18
- </div>
19
- <div class="modal-body">
20
-
21
- <div style="padding: 0 15px;">
22
- <div class=form-group>
23
- <label for="ab_provider"><?php _e( 'Provider', 'bookly' ) ?></label>
24
- <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>
25
- </div>
26
-
27
- <div class=form-group>
28
- <label for="ab_service"><?php _e( 'Service', 'bookly' ) ?></label>
29
- <div my-slide-up="errors.service_required" style="color: red; margin-top: 5px;">
30
- <?php _e( 'Please select a service', 'bookly' ) ?>
31
- </div>
32
- <select id="ab_service" class="field form-control" ng-model="form.service" ng-options="s.title for s in form.staff.services" ng-change="onServiceChange()">
33
- <option value=""><?php _e( '-- Select a service --', 'bookly' ) ?></option>
34
- </select>
35
- </div>
36
-
37
- <div class=form-group>
38
- <label for="ab_date"><?php _e( 'Date', 'bookly' ) ?></label>
39
- <input id="ab_date" class="form-control ab-auto-w" type=text ng-model=form.date ui-date="dateOptions" autocomplete="off" />
40
- </div>
41
- <div class="form-group">
42
- <div ng-hide="form.service.duration == 86400">
43
- <label for="ab_period"><?php _e( 'Period', 'bookly' ) ?></label>
44
- <div>
45
- <select id="ab_period" style="display: inline" class="form-control ab-auto-w" ng-model=form.start_time
46
- ng-options="t.title for t in dataSource.data.start_time"
47
- ng-change=onStartTimeChange()></select>
48
- <span>&nbsp;<?php _e( 'to', 'bookly' ) ?>&nbsp;</span>
49
- <select style="display: inline" class="form-control ab-auto-w" ng-model=form.end_time
50
- ng-options="t.title for t in dataSource.getDataForEndTime()"
51
- ng-change=onEndTimeChange()></select>
52
-
53
- <div my-slide-up=errors.date_interval_warning id=date_interval_warning_msg style="color: green; margin-top: 5px;">
54
- <?php _e( 'Selected period doesn\'t match service duration', 'bookly' ) ?>
55
- </div>
56
- <div my-slide-up="errors.time_interval" ng-bind="errors.time_interval" style="color: red; margin-top: 5px;"></div>
57
- </div>
58
- </div>
59
- <div my-slide-up=errors.date_interval_not_available id=date_interval_not_available_msg style="color: red; margin-top: 5px;">
60
- <?php _e( 'The selected period is occupied by another appointment', 'bookly' ) ?>
61
- </div>
62
- </div>
63
-
64
- <div class=form-group>
65
- <label>
66
- <?php _e( 'Customers', 'bookly' ) ?>
67
- <span ng-show="form.service" title="<?php echo esc_attr( __( 'Selected / maximum', 'bookly' ) ) ?>">({{dataSource.getTotalNumberOfPersons()}}/{{form.service.capacity}})</span>
68
- </label>
69
- <div my-slide-up="errors.customers_required" style="color: red; margin-top: 5px;"><?php _e( 'Please select a customer', 'bookly' ) ?></div>
70
- <div my-slide-up="errors.overflow_capacity" ng-bind="errors.overflow_capacity" style="color: red; margin-top: 5px;"></div>
71
- <ul class="ab-customer-list">
72
- <li ng-repeat="customer in form.customers">
73
- {{customer.number_of_persons}}&times;<i class="glyphicon glyphicon-user"></i>
74
- <a ng-click="editCustomFields(customer)" title="<?php echo esc_attr( __( 'Edit booking details', 'bookly' ) ) ?>">{{customer.name}}</a>
75
- <span ng-click="removeCustomer(customer)" class="glyphicon glyphicon-remove ab-pointer" title="<?php echo esc_attr( __( 'Remove customer', 'bookly' ) ) ?>"></span>
76
- </li>
77
- </ul>
78
-
79
- <div ng-show="!form.service || dataSource.getTotalNumberOfPersons() < form.service.capacity">
80
- <select id="chosen" multiple data-placeholder="<?php echo esc_attr( __( '-- Search customers --', 'bookly' ) ) ?>"
81
- class="field chzn-select form-control" chosen="dataSource.data.customers"
82
- ng-model="form.customers" ng-options="c.name for c in dataSource.data.customers">
83
- </select><br/>
84
- <a href=#ab_new_customer_dialog class="{{btn_class}}" data-backdrop={{backdrop}} data-toggle="modal"><?php _e( 'New customer', 'bookly' ) ?></a>
85
- </div>
86
- </div>
87
-
88
- <div class=form-group>
89
- <label></label>
90
- <input class="form-control" style="margin-top: 0" type="checkbox" ng-model=form.email_notification /> <?php _e( 'Send email notifications', 'bookly' ) ?>
91
- <?php AB_Utils::popover( __( 'If email or SMS notifications are enabled and you want the customer or the staff member to be notified about this appointment after saving, tick this checkbox before clicking Save.', 'bookly' ), 'width:16px;margin-left:0;' ) ?>
92
- </div>
93
- </div>
94
-
95
- </div>
96
- <div class="modal-footer">
97
- <div class=dialog-button-wrapper>
98
- <?php AB_Utils::submitButton() ?>
99
- <a ng-click=closeDialog() class=ab-reset-form href="" data-dismiss="modal"><?php _e( 'Cancel', 'bookly' ) ?></a>
100
- </div>
101
- </div>
102
- </form>
103
- </div><!-- /.modal-content -->
104
- </div><!-- /.modal-dialog -->
105
- </div><!-- /.modal -->
106
- <div style="margin-bottom: 2px;" class="ab-inline-block ab-create-customer" new-customer-dialog=createCustomer(customer) backdrop=false btn-class=""></div>
107
- <?php include '_custom_fields_form.php' ?>
108
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/templates/_custom_fields_form.php DELETED
@@ -1,77 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div id="ab_custom_fields_dialog" class="modal fade">
3
- <div class="modal-dialog">
4
- <div class="modal-content">
5
- <div class="modal-header">
6
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
7
- <h4 class="modal-title"><?php _e( 'Edit booking details', 'bookly' ) ?></h4>
8
- </div>
9
- <form class="form-horizontal" ng-hide=loading style="z-index: 1050">
10
- <div class="modal-body">
11
-
12
- <fieldset>
13
- <legend><?php _e( 'Participants', 'bookly' ) ?></legend>
14
- <div class="col-md-12">
15
- <div class="form-group">
16
- <label for="ab-edit-number-of-persons"><?php _e( 'Number of persons', 'bookly' ) ?></label>
17
- <select class="ab-custom-field form-control" id="ab-edit-number-of-persons"></select>
18
- </div>
19
- </div>
20
- </fieldset>
21
- <fieldset>
22
- <legend><?php _e( 'Custom Fields', 'bookly' ) ?></legend>
23
- <?php foreach ( json_decode( get_option( 'ab_custom_fields' ) ) as $custom_field ): ?>
24
- <div class="col-md-12">
25
- <div class="form-group">
26
- <label class="ab-formLabel"><?php echo $custom_field->label ?></label>
27
- <div class="ab-formField" data-type="<?php echo esc_attr( $custom_field->type )?>" data-id="<?php echo esc_attr( $custom_field->id ) ?>">
28
-
29
- <?php if ( $custom_field->type == 'text-field' ): ?>
30
- <input type="text" class="ab-custom-field form-control" />
31
-
32
- <?php elseif ( $custom_field->type == 'textarea' ): ?>
33
- <textarea rows="3" class="ab-custom-field form-control"></textarea>
34
-
35
- <?php elseif ( $custom_field->type == 'checkboxes' ): ?>
36
- <?php foreach ( $custom_field->items as $item ): ?>
37
- <div class="checkbox">
38
- <label>
39
- <input class="ab-custom-field" type="checkbox" value="<?php echo esc_attr( $item ) ?>" />
40
- <?php echo $item ?>
41
- </label>
42
- </div>
43
- <?php endforeach ?>
44
-
45
- <?php elseif ( $custom_field->type == 'radio-buttons' ): ?>
46
- <?php foreach ( $custom_field->items as $item ): ?>
47
- <div class="radio">
48
- <label>
49
- <input type="radio" name="<?php echo $custom_field->id ?>" class="ab-custom-field" value="<?php echo esc_attr( $item ) ?>" />
50
- <?php echo $item ?>
51
- </label>
52
- </div>
53
- <?php endforeach ?>
54
-
55
- <?php elseif ( $custom_field->type == 'drop-down' ): ?>
56
- <select class="ab-custom-field form-control">
57
- <option value=""></option>
58
- <?php foreach ( $custom_field->items as $item ): ?>
59
- <option value="<?php echo esc_attr( $item ) ?>"><?php echo $item ?></option>
60
- <?php endforeach ?>
61
- </select>
62
- <?php endif ?>
63
- </div>
64
- </div>
65
- </div>
66
- <?php endforeach ?>
67
- </fieldset>
68
-
69
- </div>
70
- <div class="modal-footer">
71
- <input type="button" data-customer="" ng-click=saveCustomFields() class="btn btn-info ab-popup-save" value="<?php _e( 'Apply', 'bookly' ) ?>">
72
- <input type="button" class="ab-reset-form" data-dismiss=modal value="<?php _e( 'Cancel', 'bookly' ) ?>" aria-hidden=true>
73
- </div>
74
- </form>
75
- </div><!-- /.modal-content -->
76
- </div><!-- /.modal-dialog -->
77
- </div><!-- /.modal -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/templates/_customer_details_dialog.php ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 -->
backend/modules/calendar/templates/calendar.php CHANGED
@@ -4,59 +4,89 @@
4
  ?>
5
  <style>
6
  .fc-slats tr { height: <?php echo max( 21, intval( 620 / (1440 / get_option( 'ab_settings_time_slot_length' ) ) ) ) ?>px; }
 
7
  </style>
8
- <div class="panel panel-default">
9
- <div class="panel-heading">
10
- <h3 class="panel-title"><?php _e( 'Calendar', 'bookly' ) ?></h3>
11
- </div>
12
- <div class="ab-calendar-inner panel-body">
13
- <div ng-app=appointmentForm>
14
- <div id="full_calendar_wrapper">
15
- <div class="tabbable" style="margin-bottom: 15px;">
16
- <ul class="nav nav-tabs" style="margin-bottom:0;border-bottom: 6px solid #1f6a8c">
17
- <?php foreach ( $staff_members as $i => $staff ) : ?>
18
- <li class="ab-calendar-tab" data-staff_id="<?php echo $staff->id ?>" style="display: none">
19
- <a href="#" data-toggle="tab"><?php echo $staff->full_name ?></a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  </li>
21
- <?php endforeach ?>
22
- <?php if( AB_Utils::isCurrentUserAdmin() ): ?>
23
- <li class="ab-calendar-tab" data-staff_id="0">
24
- <a href="#" data-toggle="tab"><?php _e( 'All', 'bookly' ) ?></a>
25
- </li>
26
- <li class="pull-right">
27
- <div class="btn-group pull-right">
28
- <button class="btn btn-info ab-staff-filter-button" data-toggle="dropdown">
29
- <i class="glyphicon glyphicon-user"></i>
30
- <span id="ab-staff-button"></span>
31
- </button>
32
- <button class="btn btn-info dropdown-toggle ab-staff-filter-button" data-toggle="dropdown"><span class="caret"></span></button>
33
- <ul class="dropdown-menu pull-right">
34
- <li>
35
- <a href="javascript:void(0)">
36
- <input style="margin-right: 5px;" type="checkbox" id="ab-filter-all-staff" class="left">
37
- <label for="ab-filter-all-staff"><?php _e( 'All staff', 'bookly' ) ?></label>
38
- </a>
39
- <?php foreach ( $staff_members as $i => $staff ): ?>
40
- <a style="padding-left: 35px;" href="javascript:void(0)">
41
- <input style="margin-right: 5px;" 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="ab-staff-filter left" />
42
- <label style="padding-right: 15px;" for="ab-filter-staff-<?php echo $staff->id ?>"><?php echo $staff->full_name ?></label>
43
- </a>
44
- <?php endforeach ?>
45
- </li>
46
- </ul>
47
- </div>
48
- </li>
49
- <?php endif ?>
50
- </ul>
51
- </div>
52
- <div class="table-responsive">
53
- <div class="ab-loading-inner" style="display: none">
54
- <span class="ab-loader"></span>
 
 
 
 
 
 
 
 
 
55
  </div>
56
- <div class="ab-calendar-element"></div>
57
- </div>
58
  </div>
59
- <?php include '_appointment_form.php' ?>
60
  </div>
61
  </div>
62
  </div>
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>
backend/modules/coupons/AB_CouponsController.php DELETED
@@ -1,43 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_CouponsController
5
- */
6
- class AB_CouponsController extends AB_Controller {
7
-
8
- /**
9
- * Default action
10
- */
11
- public function index()
12
- {
13
- $this->enqueueStyles( array(
14
- 'backend' => array(
15
- 'css/bookly.main-backend.css',
16
- 'bootstrap/css/bootstrap.min.css',
17
- ),
18
- 'module' => array(
19
- 'css/coupons.css',
20
- )
21
- ) );
22
-
23
- $this->enqueueScripts( array(
24
- 'backend' => array(
25
- 'js/ab_popup.js' => array( 'jquery' ),
26
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
27
- ),
28
- 'module' => array(
29
- 'js/coupons.js' => array( 'jquery' ),
30
- )
31
- ) );
32
-
33
- wp_localize_script( 'ab-coupons.js', 'BooklyL10n', array(
34
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
35
- 'please_select_at_least_one_coupon' => __( 'Please select at least one coupon.', 'bookly' ),
36
- ) );
37
-
38
- $this->coupons_collection = false;
39
-
40
- $this->render( 'index' );
41
- }
42
-
43
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/coupons/Controller.php ADDED
@@ -0,0 +1,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
+ /**
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
+ }
backend/modules/coupons/forms/Coupon.php ADDED
@@ -0,0 +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
+ }
backend/modules/coupons/resources/css/coupons.css DELETED
@@ -1,20 +0,0 @@
1
- #ab_coupons_wrapper .list-wrapper .list-actions { overflow: hidden; margin-top: 10px; }
2
- #ab_coupons_wrapper .list-wrapper .list-actions .add-service { float: left }
3
- #ab_coupons_wrapper .list-wrapper .list-actions .delete { float: right }
4
- #coupons_list td.last,
5
- #coupons_list th.last { width: 16px; vertical-align: middle; }
6
- #coupons_list th, #coupons_list td { padding:5px }
7
- #coupons_list .service-color-cell { width: 28px; }
8
-
9
- #ab-coupons-list .displayed-value { border: 1px solid transparent; padding: 4px 12px; min-height: 20px; }
10
- #ab-coupons-list .ab-text-focus,
11
- #ab-coupons-list .ab-text-focus:focus { text-align: right; padding-right: 2px; width: 58px; margin: 0!important; height: 30px; }
12
- #ab-coupons-list .displayed-value:hover {
13
- border: 1px solid #aaa;
14
- background: #fff;
15
- border-radius: 3px;
16
- -webkit-box-shadow: 1px 1px 2px rgba(0,0,0,0.1);
17
- -moz-box-shadow: 1px 1px 2px rgba(0,0,0,0.1);
18
- -o-box-shadow: 1px 1px 2px rgba(0,0,0,0.1);
19
- box-shadow: 1px 1px 2px rgba(0,0,0,0.1);
20
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/coupons/resources/js/coupons.js CHANGED
@@ -1,5 +1,64 @@
1
  jQuery(function($) {
2
- $('.add-coupon, .delete').on('click', function(){
3
- $('#lite_notice').modal('show');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  });
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
  });
backend/modules/coupons/templates/_list.php DELETED
@@ -1,4 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="alert alert-warning" >
3
- <?php _e('This function is disabled in the lite version of Bookly. If you find the plugin useful for your business please consider buying a licence for the full version. It costs just $46 and for this money you will get many useful functions, lifetime free update and excellent support! More information can be found here', 'bookly'); ?>: <a href="http://booking-wp-plugin.com" target="_blank">http://booking-wp-plugin.com</a>
4
- </div>
 
 
 
 
backend/modules/coupons/templates/_modal.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
backend/modules/coupons/templates/index.php CHANGED
@@ -1,35 +1,45 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
-
3
- <div id="ab_coupons_wrapper" class="panel panel-default">
4
- <div class="panel-heading">
5
- <h3 class="panel-title"><?php _e( 'Coupons', 'bookly' ) ?></h3>
6
- </div>
7
- <div class="panel-body">
8
- <div class="list-wrapper">
9
- <div id="ab-coupons-list">
10
- <?php include '_list.php' ?>
11
  </div>
12
  </div>
13
- </div>
14
- <div class="panel-footer">
15
- <div class="list-actions">
16
- <a class="add-coupon btn btn-info" href="#"><?php _e( 'Add Coupon', 'bookly' ) ?></a>
17
- <a class="delete btn btn-info" href="#"><?php _e( 'Delete', 'bookly' ) ?></a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  </div>
19
  </div>
20
- </div>
21
- <div class="modal fade" id="lite_notice" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
22
- <div class="modal-dialog">
23
- <div class="modal-content">
24
- <div class="modal-header">
25
- <h4 class="modal-title"><?php _e('Notice', 'bookly') ?></h4>
26
- </div>
27
- <div class="modal-body">
28
- <?php _e('This function is disabled in the lite version of Bookly. If you find the plugin useful for your business please consider buying a licence for the full version. It costs just $46 and for this money you will get many useful functions, lifetime free update and excellent support! More information can be found here', 'bookly'); ?>: <a href="http://booking-wp-plugin.com" target="_blank">http://booking-wp-plugin.com</a>
29
- </div>
30
- <div class="modal-footer">
31
- <button type="button" class="btn btn-default" data-dismiss="modal"><?php _e('Close', 'bookly') ?></button>
32
- </div>
33
- </div><!-- /.modal-content -->
34
- </div><!-- /.modal-dialog -->
35
- </div><!-- /.modal -->
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>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/custom_fields/AB_CustomFieldsController.php DELETED
@@ -1,63 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_CustomFieldsController
5
- */
6
- class AB_CustomFieldsController extends AB_Controller {
7
-
8
- /**
9
- * Default Action
10
- */
11
- public function index()
12
- {
13
- $this->enqueueStyles( array(
14
- 'module' => array(
15
- 'css/custom_fields.css'
16
- ),
17
- 'frontend' => array(
18
- 'css/ladda.min.css'
19
- ),
20
- 'backend' => array(
21
- 'css/bookly.main-backend.css',
22
- 'bootstrap/css/bootstrap.min.css',
23
- )
24
- ) );
25
-
26
- $this->enqueueScripts( array(
27
- 'module' => array(
28
- 'js/custom_fields.js' => array( 'jquery-ui-sortable' )
29
- ),
30
- 'frontend' => array(
31
- 'js/spin.min.js' => array( 'jquery' ),
32
- 'js/ladda.min.js' => array( 'jquery' ),
33
- )
34
- ) );
35
-
36
- wp_localize_script( 'ab-custom_fields.js', 'BooklyL10n', array(
37
- 'custom_fields' => get_option( 'ab_custom_fields' )
38
- ) );
39
-
40
- $this->render( 'index' );
41
- } // index
42
-
43
- /**
44
- * Save custom fields.
45
- */
46
- public function executeSaveCustomFields()
47
- {
48
- update_option( 'ab_custom_fields', $this->getParameter( 'fields' ) );
49
-
50
- wp_send_json_success();
51
- }
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
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/custom_fields/Controller.php ADDED
@@ -0,0 +1,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
+ /**
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
+ }
backend/modules/custom_fields/resources/css/custom_fields.css DELETED
@@ -1,86 +0,0 @@
1
- ul#ab-custom-fields {
2
- list-style-type: none;
3
- margin: 0 0 15px 0;
4
- padding: 0;
5
- }
6
- ul#ab-custom-fields .input-group { margin-left: 15px; }
7
- ul#ab-custom-fields > li {
8
- margin: 0 0 10px;
9
- padding: 15px 20px;
10
- background: #fff;
11
- border: 1px solid #ddd;
12
- }
13
- ul#ab-custom-fields > li .ab-field-title {
14
- font-size: 16px;
15
- font-weight: normal;
16
- margin: 2px 0px 13px 0px;
17
- line-height: normal;
18
- }
19
- ul#ab-custom-fields > li input[type=text] {
20
- margin-bottom: 0;
21
- width: 300px;
22
- }
23
- ul#ab-custom-fields > li input.ab-label {
24
- width: 322px;
25
- }
26
- ul#ab-custom-fields > li .input-group {
27
- margin-bottom: 0;
28
- }
29
- ul#ab-custom-fields > li .ab-required {
30
- margin: -2px 0 0 0;
31
- border-radius: 0;
32
- -webkit-border-radius: 0;
33
- }
34
- ul#ab-custom-fields > li span.input-group-addon {
35
- width: auto;
36
- }
37
- ul#ab-custom-fields > li span.input-group-addon label {
38
- margin: 0;
39
- }
40
- ul#ab-custom-fields > li span.input-group-addon span {
41
- display: inline-block;
42
- position: relative;
43
- top: 1px;
44
- font-size: 13px;
45
- }
46
- ul#ab-custom-fields > li button {
47
- margin-left: 37px;
48
- }
49
- ul#ab-custom-fields > li i.ab-handle{
50
- display: block;
51
- float: left;
52
- margin: 0 8px 0 -8px;
53
- line-height: 20px;
54
- text-align: center;
55
- cursor: move;
56
- }
57
- ul#ab-custom-fields > li i.ab-inner-handle {
58
- float: left;
59
- margin: 10px 8px 0 0;
60
- cursor: move;
61
- }
62
- ul#ab-custom-fields ul.ab-items {
63
- margin: 0 0 15px 0;
64
- }
65
- ul#ab-custom-fields ul.ab-items > li {
66
- margin: 0;
67
- padding: 15px 15px 0 15px;
68
- background: transparent;
69
- }
70
- ul#ab-custom-fields li .ab-delete {
71
- cursor: pointer;
72
- }
73
- #ab-add-fields { margin-bottom: 15px; }
74
-
75
- @media screen and (max-width: 782px) {
76
- ul#ab-custom-fields > li .ab-required {
77
- height: 16px;
78
- width: 16px;
79
- }
80
- ul#ab-custom-fields > li input[type=text] {
81
- width: 127px;
82
- }
83
- ul#ab-custom-fields > li input.ab-label {
84
- width: 150px;
85
- }
86
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/custom_fields/resources/js/custom_fields.js CHANGED
@@ -1,10 +1,19 @@
1
  jQuery(function($) {
2
 
3
- var $fields = $("ul#ab-custom-fields");
 
4
 
5
  $fields.sortable({
6
  axis : 'y',
7
- handle : '.ab-handle'
 
 
 
 
 
 
 
 
8
  });
9
 
10
  /**
@@ -29,7 +38,8 @@ jQuery(function($) {
29
  /**
30
  * Delete field or checkbox/radio button/drop-down option.
31
  */
32
- $fields.on('click', '.ab-delete', function() {
 
33
  $(this).closest('li').fadeOut('fast', function() { $(this).remove(); });
34
  });
35
 
@@ -40,22 +50,27 @@ jQuery(function($) {
40
  e.preventDefault();
41
  var data = [];
42
  $fields.children('li').each(function() {
43
- var $this = $(this);
44
- var field = {};
45
  switch ($this.data('type')) {
46
  case 'checkboxes':
47
  case 'radio-buttons':
48
  case 'drop-down':
49
  field.items = [];
50
- $this.find('li').each(function() {
51
  field.items.push($(this).find('input').val());
52
  });
53
- case 'text-field':
54
  case 'textarea':
 
 
 
55
  field.type = $this.data('type');
56
  field.label = $this.find('.ab-label').val();
57
  field.required = $this.find('.ab-required').prop('checked');
58
  field.id = $this.data('ab-field-id');
 
 
 
59
  }
60
  data.push(field);
61
  });
@@ -66,9 +81,10 @@ jQuery(function($) {
66
  type : 'POST',
67
  url : ajaxurl,
68
  xhrFields : { withCredentials: true },
69
- data : { action: 'ab_save_custom_fields', fields: JSON.stringify(data) },
70
  complete : function() {
71
  ladda.stop();
 
72
  }
73
  });
74
  });
@@ -88,9 +104,10 @@ jQuery(function($) {
88
  * @param id
89
  * @param label
90
  * @param required
91
- * @return {*|jQuery}
 
92
  */
93
- function addField(type, id, label, required) {
94
  var $new_field = $('ul#ab-templates > li[data-type=' + type + ']').clone();
95
  // Set id, label and required.
96
  if (typeof id == 'undefined') {
@@ -111,14 +128,22 @@ jQuery(function($) {
111
  })
112
  .next('label').attr('for', 'required-' + id)
113
  .end().end()
114
- .find('.ab-label').val(label);
 
 
 
 
 
 
 
 
115
  // Add new field to the list.
116
  $fields.append($new_field);
117
  $new_field.fadeIn('fast');
118
  // Make it sortable.
119
  $new_field.find('ul.ab-items').sortable({
120
  axis : 'y',
121
- handle : '.ab-inner-handle'
122
  });
123
  // Set focus to label field.
124
  $new_field.find('.ab-label').focus();
@@ -150,17 +175,59 @@ jQuery(function($) {
150
  function restoreFields() {
151
  if (BooklyL10n.custom_fields) {
152
  var custom_fields = jQuery.parseJSON(BooklyL10n.custom_fields);
153
- $.each(custom_fields, function(i, field) {
154
- var $new_field = addField(field.type, field.id, field.label, field.required);
155
-
156
- //add children
157
  if (field.items) {
158
- $.each(field.items, function(i, value) {
159
- addItem($new_field.find('ul'), field.type + '-item', value);
160
  });
161
  }
162
  });
163
  }
 
 
 
 
164
  $(':focus').blur();
165
  }
166
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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',
8
+ handle : '.bookly-js-handle'
9
+ });
10
+
11
+ $cf_per_service.change(function() {
12
+ if($(this).val() == 1){
13
+ $(this).val('0');
14
+ booklyAlert({error: [BooklyL10n.limitations]});
15
+ $(this).find('option:gt(0)').prop('disabled', true);
16
+ }
17
  });
18
 
19
  /**
38
  /**
39
  * Delete field or checkbox/radio button/drop-down option.
40
  */
41
+ $fields.on('click', '.ab-delete', function(e) {
42
+ e.preventDefault();
43
  $(this).closest('li').fadeOut('fast', function() { $(this).remove(); });
44
  });
45
 
50
  e.preventDefault();
51
  var data = [];
52
  $fields.children('li').each(function() {
53
+ var $this = $(this),
54
+ field = {};
55
  switch ($this.data('type')) {
56
  case 'checkboxes':
57
  case 'radio-buttons':
58
  case 'drop-down':
59
  field.items = [];
60
+ $this.find('ul.ab-items li').each(function() {
61
  field.items.push($(this).find('input').val());
62
  });
 
63
  case 'textarea':
64
+ case 'text-field':
65
+ case 'text-content':
66
+ case 'captcha':
67
  field.type = $this.data('type');
68
  field.label = $this.find('.ab-label').val();
69
  field.required = $this.find('.ab-required').prop('checked');
70
  field.id = $this.data('ab-field-id');
71
+ field.services = $this.find('.ab--services-holder input:checked')
72
+ .map(function() { return this.value; })
73
+ .get();
74
  }
75
  data.push(field);
76
  });
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]});
88
  }
89
  });
90
  });
104
  * @param id
105
  * @param label
106
  * @param required
107
+ * @param services
108
+ * @returns {*|jQuery}
109
  */
110
+ function addField(type, id, label, required, services) {
111
  var $new_field = $('ul#ab-templates > li[data-type=' + type + ']').clone();
112
  // Set id, label and required.
113
  if (typeof id == 'undefined') {
128
  })
129
  .next('label').attr('for', 'required-' + id)
130
  .end().end()
131
+ .find('.ab-label').val(label)
132
+ .end()
133
+ .find('.ab--services-holder input:checkbox').each(function (index) {
134
+ if (services && $.inArray(this.value, services) > -1) {
135
+ this.checked = true;
136
+ }
137
+ this.id = 'check-' + id + '-' + index;
138
+ $(this).next().attr('for', 'check-' + id + '-' + index);
139
+ });
140
  // Add new field to the list.
141
  $fields.append($new_field);
142
  $new_field.fadeIn('fast');
143
  // Make it sortable.
144
  $new_field.find('ul.ab-items').sortable({
145
  axis : 'y',
146
+ handle : '.bookly-js-handle'
147
  });
148
  // Set focus to label field.
149
  $new_field.find('.ab-label').focus();
175
  function restoreFields() {
176
  if (BooklyL10n.custom_fields) {
177
  var custom_fields = jQuery.parseJSON(BooklyL10n.custom_fields);
178
+ $.each(custom_fields, function (i, field) {
179
+ var $new_field = addField(field.type, field.id, field.label, field.required, field.services);
180
+ // add children
 
181
  if (field.items) {
182
+ $.each(field.items, function (i, value) {
183
+ addItem($new_field.find('ul.ab-items'), field.type + '-item', value);
184
  });
185
  }
186
  });
187
  }
188
+ $cf_per_service.change();
189
+ $('.ab--services-holder').each(function (id, elem) {
190
+ updateServiceButton($(elem));
191
+ });
192
  $(':focus').blur();
193
  }
194
+
195
+ $('.ab-popover').popover({trigger: 'hover'});
196
+
197
+ function updateServiceButton($holder) {
198
+ var service_checked = $holder.find('.bookly-js-check-entity:checked').length;
199
+ if (service_checked == 0) {
200
+ $holder.find('.ab-count').text(BooklyL10n.selector.nothing_selected);
201
+ $holder.find('.bookly-check-all-entities').prop('checked', false);
202
+ } else if (service_checked == 1) {
203
+ $holder.find('.ab-count').text($holder.find('.bookly-js-check-entity:checked').data('title'));
204
+ $holder.find('.bookly-check-all-entities').prop('checked', (service_checked == $holder.find('.bookly-js-check-entity').length));
205
+ } else {
206
+ if( service_checked == $holder.find('.bookly-js-check-entity').length) {
207
+ $holder.find('.bookly-check-all-entities').prop('checked', true);
208
+ $holder.find('.ab-count').text(BooklyL10n.selector.all_selected);
209
+ } else {
210
+ $holder.find('.bookly-check-all-entities').prop('checked', false);
211
+ $holder.find('.ab-count').text(service_checked + '/' + $holder.find('.bookly-js-check-entity').length);
212
+ }
213
+ }
214
+ }
215
+
216
+ $(document).on('click', '.bookly-check-all-entities', function () {
217
+ var $holder = $(this).parents('.ab--services-holder');
218
+ $holder.find('.bookly-js-check-entity').prop('checked', $(this).prop('checked'));
219
+ updateServiceButton($holder);
220
+ });
221
+
222
+ $(document).on('click', '.ab--services-holder ul.dropdown-menu li a[href]', function (e) {
223
+ updateServiceButton($(this).parents('.ab--services-holder'));
224
+ e.stopPropagation();
225
+ });
226
+
227
+ $('[data-toggle="popover"]').popover({
228
+ html: true,
229
+ placement: 'top',
230
+ trigger: 'hover',
231
+ 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>'
232
+ });
233
+ });
backend/modules/custom_fields/templates/_services.php ADDED
@@ -0,0 +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>
backend/modules/custom_fields/templates/index.php CHANGED
@@ -1,134 +1,308 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="panel panel-default">
3
- <div class="panel-heading">
4
- <h3 class="panel-title"><?php _e( 'Custom Fields', 'bookly' ) ?></h3>
5
- </div>
6
- <div class="panel-body">
7
- <ul id="ab-custom-fields"></ul>
8
-
9
- <div id="ab-add-fields">
10
- <button class="button" data-type="text-field"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Text Field', 'bookly' ) ?></button>&nbsp;
11
- <button class="button" data-type="textarea"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Text Area', 'bookly' ) ?></button>&nbsp;
12
- <button class="button" data-type="checkboxes"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Checkbox Group', 'bookly' ) ?></button>&nbsp;
13
- <button class="button" data-type="radio-buttons"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Radio Button Group', 'bookly' ) ?></button>&nbsp;
14
- <button class="button" data-type="drop-down"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Drop Down', 'bookly' ) ?></button>
15
  </div>
16
-
17
- <ul id="ab-templates" style="display:none">
18
-
19
- <li data-type="text-field">
20
- <i class="ab-handle glyphicon glyphicon-align-justify" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
21
- <h2 class="ab-field-title">
22
- <?php _e( 'Text Field', 'bookly' ) ?>
23
- <i class="ab-delete glyphicon glyphicon-trash" title="<?php echo esc_attr( __( 'Remove field', 'bookly' ) ) ?>"></i>
24
- </h2>
25
- <div class="input-group">
26
- <input class="ab-label form-control" type="text" value="" placeholder="<?php echo esc_attr( __( 'Enter a label', 'bookly' ) ) ?>" />
27
- <span class="input-group-addon">
28
- <label>
29
- <input class="ab-required" type="checkbox" />
30
- <span><?php _e( 'Required field', 'bookly' ) ?></span>
31
- </label>
32
- </span>
33
- </div>
34
- </li>
35
-
36
- <li data-type="textarea">
37
- <i class="ab-handle glyphicon glyphicon-align-justify" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
38
- <h2 class="ab-field-title">
39
- <?php _e( 'Text Area', 'bookly' ) ?>
40
- <i class="ab-delete glyphicon glyphicon-trash" title="<?php echo esc_attr( __( 'Remove field', 'bookly' ) ) ?>"></i>
41
- </h2>
42
- <div class="input-group">
43
- <input class="ab-label form-control" type="text" value="" placeholder="<?php echo esc_attr( __( 'Enter a label', 'bookly' ) ) ?>" />
44
- <span class="input-group-addon">
45
- <label>
46
- <input class="ab-required" type="checkbox" />
47
- <span><?php _e( 'Required field', 'bookly' ) ?></span>
48
- </label>
49
- </span>
50
- </div>
51
- </li>
52
-
53
- <li data-type="checkboxes">
54
- <i class="ab-handle glyphicon glyphicon-align-justify" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
55
- <h2 class="ab-field-title">
56
- <?php _e( 'Checkbox Group', 'bookly' ) ?>
57
- <i class="ab-delete glyphicon glyphicon-trash" title="<?php echo esc_attr( __( 'Remove field', 'bookly' ) ) ?>"></i>
58
- </h2>
59
- <div class="input-group">
60
- <input class="ab-label form-control" type="text" value="" placeholder="<?php echo esc_attr( __( 'Enter a label', 'bookly' ) ) ?>" />
61
- <span class="input-group-addon">
62
- <label>
63
- <input class="ab-required" type="checkbox" />
64
- <span><?php _e( 'Required field', 'bookly' ) ?></span>
65
- </label>
66
- </span>
67
  </div>
68
- <ul class="ab-items"></ul>
69
- <button class="button" data-type="checkboxes-item"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Checkbox', 'bookly' ) ?></button>
70
- </li>
71
-
72
- <li data-type="radio-buttons">
73
- <i class="ab-handle glyphicon glyphicon-align-justify" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
74
- <h2 class="ab-field-title">
75
- <?php _e( 'Radio Button Group', 'bookly' ) ?>
76
- <i class="ab-delete glyphicon glyphicon-trash" title="<?php echo esc_attr( __( 'Remove field', 'bookly' ) ) ?>"></i>
77
- </h2>
78
- <div class="input-group">
79
- <input class="ab-label form-control" type="text" value="" placeholder="<?php echo esc_attr( __( 'Enter a label', 'bookly' ) ) ?>" />
80
- <span class="input-group-addon">
81
- <label>
82
- <input class="ab-required" type="checkbox" />
83
- <span><?php _e( 'Required field', 'bookly' ) ?></span>
84
- </label>
85
- </span>
86
- </div>
87
- <ul class="ab-items"></ul>
88
- <button class="button" data-type="radio-buttons-item"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Radio Button', 'bookly' ) ?></button>
89
- </li>
90
-
91
- <li data-type="drop-down">
92
- <i class="ab-handle glyphicon glyphicon-align-justify" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
93
- <h2 class="ab-field-title">
94
- <?php _e( 'Drop Down', 'bookly' ) ?>
95
- <i class="ab-delete glyphicon glyphicon-trash" title="<?php echo esc_attr( __( 'Remove field', 'bookly' ) ) ?>"></i>
96
- </h2>
97
- <div class="input-group">
98
- <input class="ab-label form-control" type="text" value="" placeholder="<?php echo esc_attr( __( 'Enter a label', 'bookly' ) ) ?>" />
99
- <span class="input-group-addon">
100
- <label>
101
- <input class="ab-required" type="checkbox" />
102
- <span><?php _e( 'Required field', 'bookly' ) ?></span>
103
- </label>
104
- </span>
105
  </div>
106
- <ul class="ab-items"></ul>
107
- <button class="button" data-type="drop-down-item"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Option', 'bookly' ) ?></button>
108
- </li>
109
-
110
- <li data-type="checkboxes-item">
111
- <i class="ab-inner-handle glyphicon glyphicon-align-justify" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
112
- <input class="form-control ab-inline-block" type="text" value="" placeholder="<?php echo esc_attr( __( 'Enter a label', 'bookly' ) ) ?>" />
113
- <i class="ab-delete glyphicon glyphicon-trash" title="<?php echo esc_attr( __( 'Remove item', 'bookly' ) ) ?>"></i>
114
- </li>
115
-
116
- <li data-type="radio-buttons-item">
117
- <i class="ab-inner-handle glyphicon glyphicon-align-justify" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
118
- <input class="form-control ab-inline-block" type="text" value="" placeholder="<?php echo esc_attr( __( 'Enter a label', 'bookly' ) ) ?>" />
119
- <i class="ab-delete glyphicon glyphicon-trash" title="<?php echo esc_attr( __( 'Remove item', 'bookly' ) ) ?>"></i>
120
- </li>
121
-
122
- <li data-type="drop-down-item">
123
- <i class="ab-inner-handle glyphicon glyphicon-align-justify" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
124
- <input class="form-control ab-inline-block" type="text" value="" placeholder="<?php echo esc_attr( __( 'Enter a label', 'bookly' ) ) ?>" />
125
- <i class="ab-delete glyphicon glyphicon-trash" title="<?php echo esc_attr( __( 'Remove item', 'bookly' ) ) ?>"></i>
126
- </li>
127
-
128
- </ul>
129
- </div>
130
- <div class="panel-footer">
131
- <?php AB_Utils::submitButton( 'ajax-send-custom-fields' ) ?>
132
- <?php AB_Utils::resetButton() ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  </div>
134
- </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( '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
+
23
+ <hr />
24
+
25
+ <ul id="ab-custom-fields"></ul>
26
+
27
+ <div id="ab-add-fields">
28
+ <button class="btn btn-default bookly-margin-bottom-sm bookly-margin-right-sm" data-type="text-field"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Text Field', 'bookly' ) ?></button>
29
+ <button class="btn btn-default bookly-margin-bottom-sm bookly-margin-right-sm" data-type="textarea"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Text Area', 'bookly' ) ?></button>
30
+ <button class="btn btn-default bookly-margin-bottom-sm bookly-margin-right-sm" data-type="text-content"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Text Content', 'bookly' ) ?></button>
31
+ <button class="btn btn-default bookly-margin-bottom-sm bookly-margin-right-sm" data-type="checkboxes"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Checkbox Group', 'bookly' ) ?></button>
32
+ <button class="btn btn-default bookly-margin-bottom-sm bookly-margin-right-sm" data-type="radio-buttons"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Radio Button Group', 'bookly' ) ?></button>
33
+ <button class="btn btn-default bookly-margin-bottom-sm bookly-margin-right-sm" data-type="drop-down"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Drop Down', 'bookly' ) ?></button>
34
+ <button class="btn btn-default bookly-margin-bottom-sm bookly-margin-right-sm" data-type="captcha"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Captcha', 'bookly' ) ?></button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  </div>
36
+ <p class="help-block"><?php _e( 'HTML allowed in all texts and labels.', 'bookly' ) ?></p>
37
+
38
+ <ul id="ab-templates" style="display:none">
39
+
40
+ <li data-type="textarea">
41
+ <div class="bookly-flexbox">
42
+ <div class="bookly-flex-cell">
43
+ <i title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>" class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move"></i>
44
+ </div>
45
+ <div class="bookly-flex-cell" style="width: 100%">
46
+ <p><b><?php _e( 'Text Area', 'bookly' ) ?></b><a class="ab-delete glyphicon glyphicon-trash text-danger bookly-margin-left-sm" href="#"
47
+ title="<?php esc_attr_e( 'Remove field', 'bookly' ) ?>"></a></p>
48
+ <div class="row">
49
+ <div class="col-md-8">
50
+ <div class="input-group">
51
+ <input class="ab-label form-control" type="text" value="" placeholder="<?php esc_attr_e( 'Enter a label', 'bookly' ) ?>">
52
+ <label class="input-group-addon">
53
+ <input class="ab-required" type="checkbox">
54
+ <span class="hidden-xs"><?php _e( 'Required field', 'bookly' ) ?></span>
55
+ <i class="visible-xs-inline-block glyphicon glyphicon-warning"></i>
56
+ </label>
57
+ </div>
58
+ </div>
59
+ <div class="col-md-4">
60
+ <?php echo $services_html ?>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ <hr>
66
+ </li>
67
+
68
+ <li data-type="text-content">
69
+ <div class="bookly-flexbox">
70
+ <div class="bookly-flex-cell">
71
+ <i title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>" class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move"></i>
72
+ </div>
73
+ <div class="bookly-flex-cell" style="width: 100%">
74
+ <p><b><?php _e( 'Text Content', 'bookly' ) ?></b><a class="ab-delete glyphicon glyphicon-trash text-danger bookly-margin-left-sm" href="#"
75
+ title="<?php esc_attr_e( 'Remove field', 'bookly' ) ?>"></a></p>
76
+ <div class="row">
77
+ <div class="col-md-8">
78
+ <textarea class="ab-label form-control" type="text" rows="3"
79
+ placeholder="<?php esc_attr_e( 'Enter a content', 'bookly' ) ?>"></textarea>
80
+ <input class="ab-required hidden" type="checkbox" disabled="disabled">
81
+ </div>
82
+ <div class="col-md-4">
83
+ <?php echo $services_html ?>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ <hr>
89
+ </li>
90
+
91
+ <li data-type="text-field">
92
+ <div class="bookly-flexbox">
93
+ <div class="bookly-flex-cell">
94
+ <i title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>" class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move"></i>
95
+ </div>
96
+ <div class="bookly-flex-cell" style="width: 100%">
97
+ <p><b><?php _e( 'Text Field', 'bookly' ) ?></b><a class="ab-delete glyphicon glyphicon-trash text-danger bookly-margin-left-sm" href="#"
98
+ title="<?php esc_attr_e( 'Remove field', 'bookly' ) ?>"></a></p>
99
+ <div class="row">
100
+ <div class="col-md-8">
101
+ <div class="input-group">
102
+ <input class="ab-label form-control" type="text" value=""
103
+ placeholder="<?php esc_attr_e( 'Enter a label', 'bookly' ) ?>">
104
+ <label class="input-group-addon">
105
+ <input class="ab-required" type="checkbox">
106
+ <span class="hidden-xs"><?php _e( 'Required field', 'bookly' ) ?></span>
107
+ <i class="visible-xs-inline-block glyphicon glyphicon-warning"></i>
108
+ </label>
109
+ </div>
110
+ </div>
111
+ <div class="col-md-4">
112
+ <?php echo $services_html ?>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ <hr>
118
+ </li>
119
+
120
+ <li data-type="checkboxes">
121
+ <div class="bookly-flexbox">
122
+ <div class="bookly-flex-cell">
123
+ <i title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>" class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move"></i>
124
+ </div>
125
+ <div class="bookly-flex-cell" style="width: 100%">
126
+ <p><b><?php _e( 'Checkbox Group', 'bookly' ) ?></b><a class="ab-delete glyphicon glyphicon-trash text-danger bookly-margin-left-sm" href="#"
127
+ title="<?php esc_attr_e( 'Remove field', 'bookly' ) ?>"></a></p>
128
+ <div class="row">
129
+ <div class="col-md-8">
130
+ <div class="input-group">
131
+ <input class="ab-label form-control" type="text" value=""
132
+ placeholder="<?php esc_attr_e( 'Enter a label', 'bookly' ) ?>">
133
+ <label class="input-group-addon">
134
+ <input class="ab-required" type="checkbox">
135
+ <span class="hidden-xs"><?php _e( 'Required field', 'bookly' ) ?></span>
136
+ <i class="visible-xs-inline-block glyphicon glyphicon-warning"></i>
137
+ </label>
138
+ </div>
139
+
140
+ <ul class="ab-items bookly-margin-top-sm"></ul>
141
+ <button class="btn btn-sm btn-default" data-type="checkboxes-item">
142
+ <i class="glyphicon glyphicon-plus"></i> <?php _e( 'Checkbox', 'bookly' ) ?>
143
+ </button>
144
+ </div>
145
+ <div class="col-md-4">
146
+ <?php echo $services_html ?>
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ <hr>
152
+ </li>
153
+
154
+ <li data-type="radio-buttons">
155
+ <div class="bookly-flexbox">
156
+ <div class="bookly-flex-cell">
157
+ <i title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>" class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move"></i>
158
+ </div>
159
+ <div class="bookly-flex-cell" style="width: 100%">
160
+ <p><b><?php _e( 'Radio Button Group', 'bookly' ) ?></b><a class="ab-delete glyphicon glyphicon-trash text-danger bookly-margin-left-sm" href="#"
161
+ title="<?php esc_attr_e( 'Remove field', 'bookly' ) ?>"></a></p>
162
+ <div class="row">
163
+ <div class="col-md-8">
164
+ <div class="input-group">
165
+ <input class="ab-label form-control" type="text" value=""
166
+ placeholder="<?php esc_attr_e( 'Enter a label', 'bookly' ) ?>">
167
+ <label class="input-group-addon">
168
+ <input class="ab-required" type="checkbox">
169
+ <span class="hidden-xs"><?php _e( 'Required field', 'bookly' ) ?></span>
170
+ <i class="visible-xs-inline-block glyphicon glyphicon-warning"></i>
171
+ </label>
172
+ </div>
173
+
174
+ <ul class="ab-items bookly-margin-top-sm"></ul>
175
+ <button class="btn btn-sm btn-default" data-type="radio-buttons-item">
176
+ <i class="glyphicon glyphicon-plus"></i> <?php _e( 'Radio Button', 'bookly' ) ?>
177
+ </button>
178
+ </div>
179
+ <div class="col-md-4">
180
+ <?php echo $services_html ?>
181
+ </div>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ <hr>
186
+ </li>
187
+
188
+ <li data-type="drop-down">
189
+ <div class="bookly-flexbox">
190
+ <div class="bookly-flex-cell">
191
+ <i title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>" class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move"></i>
192
+ </div>
193
+ <div class="bookly-flex-cell" style="width: 100%">
194
+ <p><b><?php _e( 'Drop Down', 'bookly' ) ?></b><a class="ab-delete glyphicon glyphicon-trash text-danger bookly-margin-left-sm" href="#"
195
+ title="<?php esc_attr_e( 'Remove field', 'bookly' ) ?>"></a></p>
196
+ <div class="row">
197
+ <div class="col-md-8">
198
+ <div class="input-group">
199
+ <input class="ab-label form-control" type="text" value=""
200
+ placeholder="<?php esc_attr_e( 'Enter a label', 'bookly' ) ?>">
201
+ <label class="input-group-addon">
202
+ <input class="ab-required" type="checkbox">
203
+ <span class="hidden-xs"><?php _e( 'Required field', 'bookly' ) ?></span>
204
+ <i class="visible-xs-inline-block glyphicon glyphicon-warning"></i>
205
+ </label>
206
+ </div>
207
+
208
+ <ul class="ab-items bookly-margin-top-sm"></ul>
209
+ <button class="btn btn-sm btn-default" data-type="drop-down-item">
210
+ <i class="glyphicon glyphicon-plus"></i> <?php _e( 'Option', 'bookly' ) ?>
211
+ </button>
212
+ </div>
213
+ <div class="col-md-4">
214
+ <?php echo $services_html ?>
215
+ </div>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ <hr>
220
+ </li>
221
+
222
+ <li data-type="captcha">
223
+ <div class="bookly-flexbox">
224
+ <div class="bookly-flex-cell">
225
+ <i title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>" class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move"></i>
226
+ </div>
227
+ <div class="bookly-flex-cell" style="width: 100%">
228
+ <p><b><?php _e( 'Captcha', 'bookly' ) ?></b><a class="ab-delete glyphicon glyphicon-trash text-danger bookly-margin-left-sm" href="#"
229
+ title="<?php esc_attr_e( 'Remove field', 'bookly' ) ?>"></a></p>
230
+ <div class="row">
231
+ <div class="col-md-8">
232
+ <div class="input-group">
233
+ <input class="ab-label form-control" type="text" value=""
234
+ placeholder="<?php esc_attr_e( 'Enter a label', 'bookly' ) ?>">
235
+ <label class="input-group-addon">
236
+ <input class="ab-required hidden" type="checkbox">
237
+ <input type="checkbox" disabled="disabled" checked="checked">
238
+ <span class="hidden-xs"><?php _e( 'Required field', 'bookly' ) ?></span>
239
+ <i class="visible-xs-inline-block glyphicon glyphicon-warning"></i>
240
+ </label>
241
+ </div>
242
+ </div>
243
+ <div class="col-md-4">
244
+ <?php echo $services_html ?>
245
+ </div>
246
+ </div>
247
+ </div>
248
+ </div>
249
+ <hr>
250
+ </li>
251
+
252
+ <li data-type="checkboxes-item">
253
+ <div class="bookly-flexbox">
254
+ <div class="bookly-flex-cell bookly-vertical-middle">
255
+ <i title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>" class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move"></i>
256
+ </div>
257
+ <div class="bookly-flex-cell bookly-vertical-middle" style="width: 100%">
258
+ <input class="form-control" type="text" value=""
259
+ placeholder="<?php esc_attr_e( 'Enter a label', 'bookly' ) ?>">
260
+ </div>
261
+ <div class="bookly-flex-cell bookly-vertical-middle">
262
+ <a class="ab-delete glyphicon glyphicon-trash text-danger bookly-margin-left-sm" href="#"
263
+ title="<?php esc_attr_e( 'Remove item', 'bookly' ) ?>"></a>
264
+ </div>
265
+ </div>
266
+ </li>
267
+
268
+ <li data-type="radio-buttons-item">
269
+ <div class="bookly-flexbox">
270
+ <div class="bookly-flex-cell bookly-vertical-middle">
271
+ <i title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>" class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move"></i>
272
+ </div>
273
+ <div class="bookly-flex-cell bookly-vertical-middle" style="width: 100%">
274
+ <input class="form-control" type="text" value=""
275
+ placeholder="<?php esc_attr_e( 'Enter a label', 'bookly' ) ?>">
276
+ </div>
277
+ <div class="bookly-flex-cell bookly-vertical-middle">
278
+ <a class="ab-delete glyphicon glyphicon-trash text-danger bookly-margin-left-sm" href="#"
279
+ title="<?php esc_attr_e( 'Remove item', 'bookly' ) ?>"></a>
280
+ </div>
281
+ </div>
282
+ </li>
283
+
284
+ <li data-type="drop-down-item">
285
+ <div class="bookly-flexbox">
286
+ <div class="bookly-flex-cell bookly-vertical-middle">
287
+ <i title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>" class="bookly-js-handle bookly-icon bookly-icon-draghandle bookly-margin-right-sm bookly-cursor-move"></i>
288
+ </div>
289
+ <div class="bookly-flex-cell bookly-vertical-middle" style="width: 100%">
290
+ <input class="form-control" type="text" value=""
291
+ placeholder="<?php esc_attr_e( 'Enter a label', 'bookly' ) ?>">
292
+ </div>
293
+ <div class="bookly-flex-cell bookly-vertical-middle">
294
+ <a class="ab-delete glyphicon glyphicon-trash text-danger bookly-margin-left-sm" href="#"
295
+ title="<?php esc_attr_e( 'Remove item', 'bookly' ) ?>"></a>
296
+ </div>
297
+ </div>
298
+ </li>
299
+ </ul>
300
+ </div>
301
+
302
+ <div class="panel-footer">
303
+ <?php \BooklyLite\Lib\Utils\Common::submitButton( 'ajax-send-custom-fields' ) ?>
304
+ <?php \BooklyLite\Lib\Utils\Common::resetButton() ?>
305
+ </div>
306
+ </div>
307
  </div>
308
+ </div>
backend/modules/customer/AB_CustomerController.php DELETED
@@ -1,254 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- class AB_CustomerController extends AB_Controller {
4
-
5
- const page_slug = 'ab-customers';
6
-
7
- protected function getPermissions()
8
- {
9
- return array(
10
- 'executeSaveCustomer' => 'user',
11
- 'executeGetNgNewCustomerDialogTemplate' => 'user',
12
- );
13
- }
14
-
15
- public function index()
16
- {
17
- if ( $this->hasParameter( 'import-customers' ) ) {
18
- $this->importCustomers();
19
- }
20
-
21
- $this->enqueueStyles( array(
22
- 'module' => array(
23
- 'css/customers.css' => array( 'ab-intlTelInput.css' ),
24
- ),
25
- 'backend' => array(
26
- 'css/bookly.main-backend.css',
27
- 'bootstrap/css/bootstrap.min.css',
28
- ),
29
- 'frontend' => array(
30
- 'css/intlTelInput.css',
31
- )
32
- ) );
33
-
34
- $this->enqueueScripts( array(
35
- 'backend' => array(
36
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
37
- 'js/angular.min.js',
38
- 'js/angular-sanitize.min.js',
39
- 'js/angular-ui-utils-0.2.1.min.js',
40
- 'js/angular-ui-date-0.0.8.js',
41
- 'js/ng-new_customer_dialog.js' => array( 'jquery', 'ab-angular.min.js' ),
42
- ),
43
- 'module' => array(
44
- 'js/ng-app.js' => array(
45
- 'jquery',
46
- 'ab-angular.min.js',
47
- 'ab-angular-ui-utils-0.2.1.min.js',
48
- 'ab-angular-ui-date-0.0.8.js',
49
- 'ab-intlTelInput.utils.js',
50
- ),
51
- ),
52
- 'frontend' => array(
53
- 'js/intlTelInput.min.js' => array( 'jquery' ),
54
- 'js/intlTelInput.utils.js' => array( 'jquery' )
55
- )
56
- ) );
57
-
58
- wp_localize_script( 'ab-ng-app.js', 'BooklyL10n', array(
59
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
60
- 'wp_users' => $this->getWpUsers(),
61
- 'country' => get_option( 'ab_settings_phone_default_country' ),
62
- 'intlTelInput_utils' => plugins_url( 'intlTelInput.utils.js', AB_PATH . '/frontend/resources/js/intlTelInput.utils.js' ),
63
- 'please_select_at_least_one_row' => __( 'Please select at least one customer.', 'bookly' ),
64
- ) );
65
-
66
- $this->render( 'index' );
67
- }
68
-
69
- /**
70
- * Get list of customers.
71
- */
72
- public function executeGetCustomers()
73
- {
74
- $wpdb = $this->getWpdb();
75
- $items_per_page = 20;
76
- $response = array(
77
- 'customers' => array(),
78
- 'total' => 0,
79
- 'pages' => 0,
80
- 'active_page' => 0,
81
- );
82
-
83
- $page = intval( $this->getParameter( 'page' ) );
84
- $sort = in_array( $this->getParameter( 'sort' ), array( 'name', 'phone', 'email', 'notes', 'last_appointment', 'total_appointments', 'payments', 'wp_user' ) )
85
- ? $this->getParameter( 'sort' ) : 'name';
86
- $order = in_array( $this->getParameter( 'order' ), array( 'asc', 'desc' ) ) ? $this->getParameter( 'order' ) : 'asc';
87
- $filter = $wpdb->_real_escape( $this->getParameter( 'filter' ) );
88
-
89
- $query = AB_Customer::query( 'c' );
90
- // WHERE
91
- if ( $filter !== '' ) {
92
- $query->whereLike( 'c.name', "%{$filter}%")
93
- ->whereLike( 'c.phone', "%{$filter}%", 'OR' )
94
- ->whereLike( 'c.email', "%{$filter}%", 'OR' );
95
- }
96
- $total = $query->count();
97
-
98
- $pages = ceil( $total / $items_per_page );
99
- if ( $page < 1 || $page > $pages ) {
100
- $page = 1;
101
- }
102
-
103
- $data = $query->select( 'c.*, MAX(a.start_date) AS last_appointment,
104
- COUNT(a.id) AS total_appointments,
105
- COALESCE(SUM(p.total),0) AS payments,
106
- wpu.display_name AS wp_user' )
107
- ->leftJoin( 'AB_CustomerAppointment', 'ca', 'ca.customer_id = c.id' )
108
- ->leftJoin( 'AB_Appointment', 'a', 'a.id = ca.appointment_id' )
109
- ->leftJoin( 'AB_Payment', 'p', 'p.customer_appointment_id = ca.id' )
110
- ->tableJoin( $wpdb->users, 'wpu', 'wpu.ID = c.wp_user_id' )
111
- ->groupBy( 'c.id' )
112
- ->sortBy( $sort )
113
- ->order( $order )
114
- ->limit( $items_per_page )
115
- ->offset( ( $page - 1 ) * $items_per_page )
116
- ->fetchArray();
117
-
118
- array_walk( $data, function ( &$row ) {
119
- if ( $row['last_appointment'] ) {
120
- $row['last_appointment'] = AB_DateTimeUtils::formatDateTime( $row['last_appointment'] );
121
- }
122
- $row['payments'] = AB_Utils::formatPrice( $row['payments'] );
123
- } );
124
-
125
- // Populate response.
126
- $response[ 'customers' ] = $data;
127
- $response[ 'total' ] = $total;
128
- $response[ 'pages' ] = $pages;
129
- $response[ 'active_page' ] = $page;
130
-
131
- wp_send_json_success( $response );
132
- }
133
-
134
- /**
135
- * Get WP users array.
136
- *
137
- * @return array
138
- */
139
- public function getWpUsers()
140
- {
141
- return get_users( array( 'fields' => array( 'ID', 'display_name' ), 'orderby' => 'display_name' ) );
142
- }
143
-
144
- /**
145
- * Create or edit a customer.
146
- */
147
- public function executeSaveCustomer()
148
- {
149
- $response = array();
150
- $form = new AB_CustomerForm();
151
-
152
- do {
153
- if ( $this->getParameter( 'name' ) !== '' ) {
154
- $form->bind( $this->getPostParameters() );
155
- /** @var AB_Customer $customer */
156
- $customer = $form->save();
157
- if ( $customer ) {
158
- $response[ 'success' ] = true;
159
- $response[ 'customer' ] = array(
160
- 'id' => $customer->id,
161
- 'name' => $customer->name,
162
- 'wp_user_id' => $customer->wp_user_id,
163
- 'phone' => $customer->phone,
164
- 'email' => $customer->email,
165
- 'notes' => $customer->notes,
166
- 'jsonString' => json_encode( array(
167
- 'name' => $customer->name,
168
- 'phone' => $customer->phone,
169
- 'email' => $customer->email,
170
- 'notes' => $customer->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
- $csv_mime_types = array(
191
- 'text/csv',
192
- 'application/csv',
193
- 'text/comma-separated-values',
194
- 'application/excel',
195
- 'application/vnd.ms-excel',
196
- 'application/vnd.msexcel'
197
- );
198
-
199
- if ( in_array( $_FILES[ 'import_customers_file' ][ 'type' ], $csv_mime_types ) ) {
200
- $file = fopen ( $_FILES[ 'import_customers_file' ][ 'tmp_name' ], 'r' );
201
- while ( $line = fgetcsv( $file, null, $this->getParameter( 'import_customers_delimiter' ) ) ) {
202
- if ( !empty ( $line[ 0 ] ) ) {
203
- $customer = new AB_Customer();
204
- $customer->set( 'name', $line[ 0 ] );
205
- if ( isset ( $line[ 1 ] ) ) {
206
- $customer->set( 'phone', $line[ 1 ] );
207
- }
208
- if ( isset ( $line[ 2 ] ) ) {
209
- $customer->set( 'email', $line[ 2 ] );
210
- }
211
- $customer->save();
212
- }
213
- }
214
- }
215
- }
216
-
217
- /**
218
- * Get angulars template for new customer dialog.
219
- */
220
- public function executeGetNgNewCustomerDialogTemplate()
221
- {
222
- $this->render( 'ng-new_customer_dialog', array(
223
- 'custom_fields' => json_decode( get_option( 'ab_custom_fields' ) ),
224
- 'module' => $this->getParameter( 'module' ),
225
- 'wp_users' => $this->getWpUsers()
226
- ) );
227
- exit ( 0 );
228
- }
229
-
230
- /**
231
- * Delete a customer.
232
- */
233
- public function executeDeleteCustomer()
234
- {
235
- foreach ( $this->getParameter( 'ids' ) as $id ) {
236
- $customer = new AB_Customer();
237
- $customer->load( $id );
238
- $customer->deleteWithWPUser( (bool) $this->getParameter( 'with_wp_user' ) );
239
- }
240
- wp_send_json_success();
241
- }
242
-
243
- /**
244
- * Override parent method to add 'wp_ajax_ab_' prefix
245
- * so current 'execute*' methods look nicer.
246
- *
247
- * @param string $prefix
248
- */
249
- protected function registerWpActions( $prefix = '' )
250
- {
251
- parent::registerWpActions( 'wp_ajax_ab_' );
252
- }
253
-
254
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/customer/forms/AB_CustomerForm.php DELETED
@@ -1,28 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_CustomerForm
5
- */
6
- class AB_CustomerForm extends AB_Form {
7
-
8
- /**
9
- * Constructor.
10
- */
11
- public function __construct()
12
- {
13
- parent::$entity_class = 'AB_Customer';
14
- parent::__construct();
15
- }
16
-
17
- public function configure()
18
- {
19
- $this->setFields( array(
20
- 'name',
21
- 'wp_user_id',
22
- 'phone',
23
- 'email',
24
- 'notes'
25
- ) );
26
- }
27
-
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/customer/resources/css/customers.css DELETED
@@ -1,10 +0,0 @@
1
- /* intlTelInput.js */
2
- .iti-flag{background-image:url("../../../../../frontend/resources/images/flags.png");}
3
- @media only screen and (min-resolution: 2dppx){.iti-flag{background-image:url("../../../../../frontend/resources/images/flags@2x.png")}}
4
- #ab_customers_list .intl-tel-input,
5
- #ab_customers_list td.ab-phone .displayed-value { width: 170px; }
6
- .ab-customers-list .table-responsive { overflow: visible; }
7
- /* media query */
8
- @media screen and (max-width: 782px) {
9
- .ab-customers-list .table-responsive { overflow-y: hidden; }
10
- }
 
 
 
 
 
 
 
 
 
 
backend/modules/customer/resources/js/ng-app.js DELETED
@@ -1,356 +0,0 @@
1
- ;(function() {
2
- var module = angular.module('customers', ['ui.utils', 'ui.date', 'newCustomerDialog', 'ngSanitize']);
3
-
4
- module.factory('dataSource', function($q, $rootScope) {
5
- var ds = {
6
- customers : [],
7
- total : 0,
8
- pages : [],
9
- form : {
10
- new_customer : {
11
- name : null,
12
- wp_user_id : null,
13
- phone : null,
14
- email : null,
15
- notes : null
16
- }
17
- },
18
- loadData : function(params) {
19
- var deferred = $q.defer();
20
- jQuery.ajax({
21
- url : ajaxurl,
22
- type : 'POST',
23
- data : jQuery.extend({ action : 'ab_get_customers' }, params),
24
- dataType : 'json',
25
- success : function(response) {
26
- if (response.success) {
27
-
28
- ds.customers = response.data.customers;
29
- ds.total = response.data.total;
30
- ds.pages = [];
31
- ds.paginator = {beg : false, end: false};
32
- var neighbor = 5;
33
- var beg = Math.max(1, response.data.active_page - neighbor);
34
- var end = Math.min(response.data.pages, (response.data.active_page + neighbor));
35
- if (beg > 1) {
36
- ds.paginator.beg = true;
37
- beg++;
38
- }
39
- for (var i = beg; i < end; i++) {
40
- ds.pages.push({ number : i, active : response.data.active_page == i });
41
- }
42
- if (end >= response.data.pages) {
43
- ds.pages.push({number: response.data.pages, active: response.data.active_page == response.data.pages});
44
- } else {
45
- ds.paginator.end = {number: response.data.pages, active: false};
46
- }
47
- for (var i = 0; i < ds.customers.length; ++ i) {
48
- for (var j = 0; j < BooklyL10n.wp_users.length; ++ j) {
49
- if (ds.customers[i].wp_user_id == BooklyL10n.wp_users[j].ID) {
50
- ds.customers[i].wp_user = BooklyL10n.wp_users[j];
51
- break;
52
- }
53
- }
54
- }
55
- }
56
- $rootScope.$apply(deferred.resolve);
57
- },
58
- error : function() {
59
- ds.customers = [];
60
- ds.total = 0;
61
- $rootScope.$apply(deferred.resolve);
62
- }
63
- });
64
- return deferred.promise;
65
- }
66
- };
67
- ds.wp_users = BooklyL10n.wp_users;
68
- return ds;
69
- });
70
-
71
- module.factory('intlTelInputSrv', function() {
72
- var srv = {
73
- elements: {},
74
- init: function(id, element) {
75
- if (srv.elements[id]) {
76
- srv.destroy(id);
77
- }
78
- srv.elements[id] = element;
79
- srv.elements[id].intlTelInput({
80
- preferredCountries: [BooklyL10n.country],
81
- defaultCountry: BooklyL10n.country,
82
- geoIpLookup: function(callback) {
83
- jQuery.get(ajaxurl, {action: 'ab_ip_info'}, function() {}, 'json' ).always(function(resp) {
84
- var countryCode = (resp && resp.country) ? resp.country : '';
85
- callback(countryCode);
86
- });
87
- },
88
- utilsScript: BooklyL10n.intlTelInput_utils
89
- });
90
- },
91
- destroy: function(id) {
92
- if (srv.elements[id]) {
93
- srv.elements[id].val(srv.getNumber(id));
94
- srv.elements[id].intlTelInput('destroy');
95
- delete srv.elements[id];
96
- }
97
- },
98
- getNumber: function(id) {
99
- return srv.elements[id] ? srv.elements[id].intlTelInput('getNumber') : null;
100
- }
101
- };
102
-
103
- return srv;
104
- });
105
-
106
- module.controller('customersCtrl', function($scope, dataSource, intlTelInputSrv) {
107
- // Set up initial data.
108
- var params = {
109
- page : 1,
110
- sort : 'name',
111
- order : 'asc',
112
- filter : ''
113
- };
114
- $scope.loading = true;
115
- $scope.css_class = {
116
- name : 'asc',
117
- wp_user : '',
118
- phone : '',
119
- email : '',
120
- notes : '',
121
- last_appointment : '',
122
- total_appointments : '',
123
- payments : ''
124
- };
125
- // Set up data source (data will be loaded in reload function).
126
- $scope.dataSource = dataSource;
127
-
128
- $scope.reload = function( opt ) {
129
- $scope.loading = true;
130
- if (opt !== undefined) {
131
- if (opt.sort !== undefined) {
132
- if (params.sort === opt.sort) {
133
- // Toggle order when sorting by the same field.
134
- params.order = params.order === 'asc' ? 'desc' : 'asc';
135
- } else {
136
- params.order = 'asc';
137
- }
138
- $scope.css_class = {
139
- name : '',
140
- wp_user : '',
141
- phone : '',
142
- email : '',
143
- notes : '',
144
- last_appointment : '',
145
- total_appointments : '',
146
- payments : ''
147
- };
148
- $scope.css_class[opt.sort] = params.order;
149
- }
150
- jQuery.extend(params, opt);
151
- }
152
- dataSource.loadData(params).then(function() {
153
- $scope.loading = false;
154
- });
155
- };
156
-
157
- var filter_delay = null;
158
- $scope.$watch('filter', function() {
159
- if (filter_delay !== null) {
160
- clearTimeout(filter_delay);
161
- }
162
- filter_delay = setTimeout(function() {
163
- filter_delay = null;
164
- $scope.$apply(function($scope) {
165
- $scope.reload({filter: $scope.filter});
166
- });
167
- }, 400);
168
- });
169
-
170
- /**
171
- * Edit customer.
172
- *
173
- * @param object customer
174
- */
175
- $scope.saveCustomer = function(customer) {
176
- customer.edit_name = false;
177
- customer.edit_wp_user = false;
178
- customer.edit_phone = false;
179
- customer.edit_email = false;
180
- customer.edit_notes = false;
181
- customer.errors = {};
182
-
183
- $scope.loading = true;
184
- jQuery.ajax({
185
- url : ajaxurl,
186
- type : 'POST',
187
- data : {
188
- action : 'ab_save_customer',
189
- id : customer.id,
190
- wp_user_id : customer.wp_user ? customer.wp_user.ID : null,
191
- name : customer.name,
192
- phone : customer.phone,
193
- email : customer.email,
194
- notes : customer.notes
195
- },
196
- dataType : 'json',
197
- success : function(response) {
198
- $scope.$apply(function($scope) {
199
- if ( response.success == false) {
200
- jQuery.each(response.errors, function(field, errors) {
201
- customer.errors[field] = {};
202
- customer['edit_' + field] = true;
203
- jQuery.each(errors, function(key, error) {
204
- customer.errors[field][error] = true;
205
- });
206
- });
207
- }
208
- $scope.loading = false;
209
- });
210
- },
211
- error : function(response) {
212
- $scope.$apply(function($scope) {
213
- $scope.loading = false;
214
- });
215
- }
216
- });
217
- };
218
-
219
- $scope.saveCustomerPhone = function(customer) {
220
- if (customer.edit_phone) {
221
- customer.phone = intlTelInputSrv.getNumber(customer.id);
222
- $scope.saveCustomer(customer);
223
- }
224
- };
225
-
226
- /**
227
- * Callback for creating new customer.
228
- *
229
- * @param object customer
230
- */
231
- $scope.createCustomer = function(customer) {
232
- dataSource.customers.push(customer);
233
- $scope.reload(params.page);
234
- };
235
-
236
- /**
237
- * Delete customer.
238
- */
239
- $scope.deleteCustomers = function() {
240
- var ids = [];
241
- jQuery('table input[type=checkbox]:checked').each(function() {
242
- ids.push(jQuery(this).data('customer_id'));
243
- });
244
- if (ids.length) {
245
- if (delete_customers_choice === null) {
246
- $modal.data('customer_ids', ids).modal('show');
247
- } else {
248
- deleteCustomers(ids, delete_customers_choice);
249
- }
250
- } else {
251
- alert(BooklyL10n.please_select_at_least_one_row);
252
- }
253
- };
254
-
255
- /**
256
- * Popup for deleting customer.
257
- */
258
- var delete_customers_choice = null;
259
- var deleteCustomers = function(ids, with_wp_user) {
260
- $scope.loading = true;
261
- jQuery.ajax({
262
- url: ajaxurl,
263
- type: 'POST',
264
- data: {
265
- action: 'ab_delete_customer',
266
- ids : ids,
267
- with_wp_user: with_wp_user ? 1 : 0
268
- },
269
- dataType : 'json',
270
- success : function (response) {
271
- $scope.$apply(function ($scope) {
272
- $scope.reload();
273
- });
274
- }
275
- });
276
- };
277
- var $modal = jQuery('#ab-customer-delete');
278
- $modal
279
- .on('click', '.ab-yes', function () {
280
- $modal.modal('hide');
281
- if ( jQuery('#ab-remember-my-choice').prop('checked') ) {
282
- delete_customers_choice = true;
283
- }
284
- deleteCustomers($modal.data('customer_ids'), true);
285
- })
286
- .on('click', '.ab-no', function () {
287
- if ( jQuery('#ab-remember-my-choice').prop('checked') ) {
288
- delete_customers_choice = false;
289
- }
290
- deleteCustomers($modal.data('customer_ids'), false);
291
- });
292
-
293
- });
294
-
295
- /**
296
- * Directive for setting focus to element.
297
- */
298
- module.directive('focusMe', function($timeout) {
299
- return {
300
- link: function(scope, element, attrs) {
301
- scope.$watch(attrs.focusMe, function(value) {
302
- if (value) {
303
- $timeout(function() {
304
- element[0].focus();
305
- });
306
- }
307
- });
308
- }
309
- };
310
- });
311
-
312
- module.directive('intlTelInput', ['intlTelInputSrv', function(intlTelInputSrv) {
313
- return function(scope, element, attrs) {
314
- scope.$watch(attrs.intlTelInput, function(value) {
315
- if (value) {
316
- intlTelInputSrv.init(scope.$eval(attrs.intlTelInputId), element);
317
- } else {
318
- intlTelInputSrv.destroy(scope.$eval(attrs.intlTelInputId));
319
- }
320
- });
321
- };
322
- }]);
323
-
324
- module.directive('clickOutside', ['$document', function ($document) {
325
- return {
326
- restrict: 'A',
327
- scope: {
328
- clickOutside: '&'
329
- },
330
- link: function ($scope, elem, attr) {
331
- $document.on('click', function (e) {
332
- var element;
333
-
334
- if (!e.target) return;
335
-
336
- for (element = e.target; element; element = element.parentNode) {
337
- if (element == elem.get(0)) {
338
- return;
339
- }
340
- }
341
-
342
- $scope.$apply(function($scope) {
343
- $scope.$eval($scope.clickOutside);
344
- });
345
- });
346
- }
347
- };
348
- }]);
349
-
350
- module.filter('nl2br', function() {
351
- return function(input) {
352
- return ('' + input).split('\n').join('<br>');
353
- };
354
- });
355
-
356
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/customer/templates/_import.php DELETED
@@ -1,37 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div id="ab_import_customers_dialog" class="modal fade" tabindex=-1 role="dialog" aria-labelledby="importCustomersModalLabel" aria-hidden="true">
3
- <div class="modal-dialog">
4
- <form class="form-horizontal" enctype="multipart/form-data" action="<?php echo AB_Utils::escAdminUrl( AB_CustomerController::page_slug ) ?>" method="POST">
5
- <div class="modal-content">
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"><?php _e( 'Import', 'bookly' ) ?></h4>
9
- </div>
10
- <div class="modal-body">
11
- <div class="container-fluid">
12
- <div class="form-group">
13
- <label><?php _e( 'Note', 'bookly' ) ?></label>
14
- <?php _e( 'You may import list of clients in CSV format. The file needs to have three columns: Name, Phone and Email.', 'bookly' ) ?>
15
- </div>
16
- <div class="form-group">
17
- <label for="import_customers_file"><?php _e( 'Select file', 'bookly' ) ?></label>
18
- <input name="import_customers_file" id="import_customers_file" type="file">
19
- </div>
20
- <div class="form-group">
21
- <label for="import_customers_delimiter"><?php _e( 'Delimiter', 'bookly' ) ?></label>
22
- <select name="import_customers_delimiter" id="import_customers_delimiter" class="form-control">
23
- <option value=","><?php _e( 'Comma (,)', 'bookly' ) ?></option>
24
- <option value=";"><?php _e( 'Semicolon (;)', 'bookly' ) ?></option>
25
- </select>
26
- </div>
27
- <input type="hidden" name="import">
28
- </div>
29
- </div>
30
- <div class="modal-footer">
31
- <button type="submit" class="btn btn-info ab-popup-save" name="import-customers"><?php _e( 'Import', 'bookly' ) ?></button>
32
- <button class="ab-reset-form" data-dismiss="modal" aria-hidden="true"><?php _e( 'Cancel', 'bookly' ) ?></button>
33
- </div>
34
- </div>
35
- </form>
36
- </div>
37
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/customer/templates/index.php DELETED
@@ -1,136 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="panel panel-default">
3
- <div class="panel-heading">
4
- <h3 class="panel-title"><?php _e( 'Customers', 'bookly' ) ?></h3>
5
- </div>
6
- <div class="panel-body ab-customers-list">
7
- <div ng-app="customers" ng-controller="customersCtrl" class="form-horizontal ng-cloak">
8
- <div class="row">
9
- <div class="col-md-6 col-xs-12">
10
- <div class="control-group">
11
- <label for="ab_filter"><?php _e( 'Quick search customer', 'bookly' ) ?></label>
12
- <div class=controls>
13
- <input id="ab_filter" style="display: inline-block;width: auto;margin-bottom: 20px" class="form-control" type=text ng-model=filter />
14
- </div>
15
- </div>
16
- </div>
17
- <div class="col-md-6 col-xs-12">
18
- <div style="display: inline;" new-customer-dialog="createCustomer(customer)" backdrop="true" btn-class="btn btn-info"></div>
19
- <a href="#ab_import_customers_dialog" class="btn btn-info pull-right" data-toggle="modal"><?php _e( 'Import', 'bookly' ) ?></a>
20
- <a style="margin-right: 5px;" href=#ab_new_customer_dialog class="btn btn-info pull-right" data-backdrop=true data-toggle="modal"><?php _e( 'New customer', 'bookly' ) ?></a>
21
- <?php include "_import.php" ?>
22
- </div>
23
- </div>
24
-
25
- <div class="table-responsive">
26
- <table id="ab_customers_list" class="table table-striped" cellspacing=0 cellpadding=0 border=0 style="clear: both;">
27
- <thead>
28
- <tr>
29
- <th style="width: 150px" ng-class=css_class.name><a href="" ng-click=reload({sort:'name'})><?php _e( 'Name', 'bookly' ) ?></a></th>
30
- <th style="width: 150px" ng-class=css_class.wp_user><a href="" ng-click=reload({sort:'wp_user'})><?php _e( 'User', 'bookly' ) ?></a></th>
31
- <th style="width: 100px" ng-class=css_class.phone><a href="" ng-click=reload({sort:'phone'})><?php _e( 'Phone', 'bookly' ) ?></a></th>
32
- <th style="width: 250px" ng-class=css_class.email><a href="" ng-click=reload({sort:'email'})><?php _e( 'Email', 'bookly' ) ?></a></th>
33
- <th style="width: 250px" ng-class=css_class.notes><a href="" ng-click=reload({sort:'notes'})><?php _e( 'Notes', 'bookly' ) ?></a></th>
34
- <th ng-class=css_class.last_appointment><a href="" ng-click=reload({sort:'last_appointment'})><?php _e( 'Last appointment', 'bookly' ) ?></a></th>
35
- <th ng-class=css_class.total_appointments><a href="" ng-click=reload({sort:'total_appointments'})><?php _e( 'Total appointments', 'bookly') ?></a></th>
36
- <th ng-class=css_class.payments><a href="" ng-click=reload({sort:'payments'})><?php _e( 'Payments', 'bookly') ?></a></th>
37
- <th></th>
38
- </tr>
39
- </thead>
40
- <tbody>
41
- <tr ng-repeat="customer in dataSource.customers">
42
- <td>
43
- <div ng-click="customer.edit_name = true" ng-hide=customer.edit_name class=displayed-value>{{customer.name}}</div>
44
- <span ng-show=customer.errors.name.required><?php _e( 'Required', 'bookly' ) ?></span>
45
- <input class="form-control ab-value" ng-model=customer.name ui-event="{blur:'saveCustomer(customer)'}" ng-show=customer.edit_name focus-me=customer.edit_name required />
46
- </td>
47
- <td>
48
- <div ng-click="customer.edit_wp_user = true" ng-hide=customer.edit_wp_user class=displayed-value>{{customer.wp_user.display_name}}</div>
49
- <select ng-model="customer.wp_user" ng-options="wp_user as wp_user.display_name for wp_user in dataSource.wp_users" ui-event="{blur:'saveCustomer(customer)'}" ng-show=customer.edit_wp_user
50
- focus-me=customer.edit_wp_user class="form-control">
51
- <option value=""></option>
52
- </select>
53
- </td>
54
- <td class="ab-phone" click-outside="saveCustomerPhone(customer)">
55
- <div ng-click="customer.edit_phone = true" ng-hide=customer.edit_phone class=displayed-value>{{customer.phone}}</div>
56
- <input class="form-control ab-value" intl-tel-input=customer.edit_phone intl-tel-input-id=customer.id ng-model=customer.phone ng-show=customer.edit_phone focus-me=customer.edit_phone />
57
- </td>
58
- <td>
59
- <div ng-click="customer.edit_email = true" ng-hide=customer.edit_email class=displayed-value>{{customer.email}}</div>
60
- <input class="form-control ab-value" ng-model=customer.email ui-event="{blur:'saveCustomer(customer)'}" ng-show=customer.edit_email focus-me=customer.edit_email />
61
- </td>
62
- <td>
63
- <div ng-click="customer.edit_notes = true" ng-hide=customer.edit_notes class=displayed-value ng-bind-html="customer.notes | nl2br"></div>
64
- <textarea class="form-control ab-value" ng-model="customer.notes" ui-event="{blur:'saveCustomer(customer)'}" ng-show=customer.edit_notes focus-me=customer.edit_notes></textarea>
65
- </td>
66
- <td>
67
- <div ng-model=customer.last_appointment >{{customer.last_appointment}}</div>
68
- </td>
69
- <td class="text-right">
70
- <div ng-model=customer.total_appointments >{{customer.total_appointments}}</div>
71
- </td>
72
- <td class="text-right">
73
- <div ng-model=customer.payments >{{customer.payments}}</div>
74
- </td>
75
- <td><input type="checkbox" data-customer_id="{{customer.id}}"></td>
76
- </tr>
77
- </tbody>
78
- </table>
79
- <div ng-hide="dataSource.customers.length || loading">
80
- <td colspan=9><div class="alert alert-info"><?php _e( 'No customers.', 'bookly' ) ?></div></td>
81
- </div>
82
- </div>
83
-
84
- <div class="btn-toolbar">
85
- <div class="col-xs-8">
86
- <div ng-show="dataSource.pages.length > 1">
87
- <div class="btn-group" ng-show="dataSource.paginator.beg">
88
- <button ng-click=reload({page:1}) class="btn btn-default">
89
- 1
90
- </button>
91
- </div>
92
- <div class="btn-group">
93
- <button ng-click=reload({page:page.number}) class="btn btn-default" ng-class="{'active': page.active}" ng-repeat="page in dataSource.pages">
94
- {{page.number}}
95
- </button>
96
- </div>
97
- <div class="btn-group" ng-show="dataSource.paginator.end != false">
98
- <button ng-click=reload({page:dataSource.paginator.end.number}) class="btn btn-default">
99
- {{dataSource.paginator.end.number}}
100
- </button>
101
- </div>
102
- </div>
103
- </div>
104
- <div class="col-xs-4">
105
- <a class="btn btn-info pull-right" ng-click="deleteCustomers()"><?php _e( 'Delete', 'bookly' ) ?></a>
106
- </div>
107
- </div>
108
- <div ng-show="loading" class="loading-indicator">
109
- <span class="ab-loader"></span>
110
- </div>
111
- </div>
112
- </div>
113
- </div>
114
-
115
- <div class="modal fade" id="ab-customer-delete">
116
- <div class="modal-dialog">
117
- <div class="modal-content">
118
- <div class="modal-header">
119
- <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
120
- <h4 class="modal-title"><?php _e( 'Delete customers', 'bookly' ) ?></h4>
121
- </div>
122
- <div class="modal-body">
123
- <?php _e( 'You are about to delete customers which may have WordPress accounts associated to them. Do you want to delete those accounts too (if there are any)?', 'bookly' ) ?>
124
- <div class="checkbox">
125
- <label>
126
- <input id="ab-remember-my-choice" type="checkbox"> <?php _e( 'Remember my choice', 'bookly' ) ?>
127
- </label>
128
- </div>
129
- </div>
130
- <div class="modal-footer">
131
- <button type="button" class="btn btn-default ab-no" data-dismiss="modal"><?php _e( 'No, delete just customers', 'bookly' ) ?></button>
132
- <button type="button" class="btn btn-info ab-yes"><?php _e( 'Yes', 'bookly' ) ?></button>
133
- </div>
134
- </div>
135
- </div>
136
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/customer/templates/ng-new_customer_dialog.php DELETED
@@ -1,110 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div>
3
- <div id="ab_new_customer_dialog" class="modal fade">
4
- <div class="modal-dialog">
5
- <form style="margin-bottom: 0;" ng-hide=loading>
6
- <div class="modal-content">
7
- <div class="modal-header">
8
- <button type=button class=close data-dismiss=modal aria-hidden=true>×</button>
9
- <h4 class="modal-title"><?php _e( 'New Customer', 'bookly' ) ?></h4>
10
- </div>
11
- <div class="modal-body">
12
- <fieldset>
13
- <legend><?php _e( 'Personal Information', 'bookly' ) ?></legend>
14
- <div class="col-md-12">
15
- <div class="form-group">
16
- <label for="wp_user"><?php _e( 'User', 'bookly' ) ?></label>
17
- <select ng-model="form.wp_user_id" class="form-control" id="wp_user">
18
- <option value=""></option>
19
- <?php foreach ( $wp_users as $wp_user ): ?>
20
- <option value="<?php echo $wp_user->ID ?>">
21
- <?php echo $wp_user->display_name ?>
22
- </option>
23
- <?php endforeach ?>
24
- </select>
25
- </div>
26
-
27
- <div class="form-group">
28
- <label for="username"><?php _e( 'Name', 'bookly' ) ?></label>
29
- <input class="form-control" type="text" ng-model="form.name" id="username"/>
30
- <span style="font-size: 11px;color: red" ng-show="errors.name.required"><?php _e( 'Required', 'bookly' ) ?></span>
31
- </div>
32
- <div class="form-group">
33
- <label for="phone"><?php _e( 'Phone', 'bookly' ) ?></label><br/>
34
- <input class="form-control" type="text" ng-model=form.phone id="phone"/>
35
- </div>
36
- <div class="form-group">
37
- <label for="email"><?php _e( 'Email', 'bookly' ) ?></label>
38
- <input class="form-control" type="text" ng-model=form.email id="email"/>
39
- </div>
40
- <div class="form-group">
41
- <label for="notes"><?php _e( 'Notes', 'bookly' ) ?></label>
42
- <textarea class="form-control" ng-model=form.notes id="notes"></textarea>
43
- </div>
44
- </div>
45
- </fieldset>
46
-
47
- <?php if ( $module !== 'customer' ): ?>
48
- <fieldset>
49
- <legend><?php _e( 'Custom Fields', 'bookly' ) ?></legend>
50
- <div class="new-customer-custom-fields">
51
- <div class="col-md-12">
52
- <?php foreach ( $custom_fields as $custom_field ): ?>
53
- <div class="form-group">
54
- <label><?php echo $custom_field->label ?></label>
55
- <div class="ab-formField" data-type="<?php echo $custom_field->type ?>" data-id="<?php echo $custom_field->id ?>">
56
- <?php if ( $custom_field->type == 'text-field' ): ?>
57
- <input type="text" class="form-control ab-custom-field" />
58
-
59
- <?php elseif ( $custom_field->type == 'textarea' ): ?>
60
- <textarea rows="3" class="form-control ab-custom-field"></textarea>
61
-
62
- <?php elseif ( $custom_field->type == 'checkboxes' ): ?>
63
- <?php foreach ( $custom_field->items as $item ): ?>
64
- <div class="checkbox">
65
- <label>
66
- <input class="ab-custom-field" type="checkbox" value="<?php echo esc_attr( $item ) ?>" />
67
- <?php echo $item ?>
68
- </label>
69
- </div>
70
- <?php endforeach ?>
71
-
72
- <?php elseif ( $custom_field->type == 'radio-buttons' ): ?>
73
- <?php foreach ( $custom_field->items as $item ): ?>
74
- <div class="radio">
75
- <label>
76
- <input type="radio" name="<?php echo $custom_field->id ?>" class="ab-custom-field" value="<?php echo esc_attr( $item ) ?>" />
77
- <?php echo $item ?>
78
- </label>
79
- </div>
80
- <?php endforeach ?>
81
-
82
- <?php elseif ( $custom_field->type == 'drop-down' ): ?>
83
- <select class="form-control ab-custom-field">
84
- <option value=""></option>
85
- <?php foreach ( $custom_field->items as $item ): ?>
86
- <option value="<?php echo esc_attr( $item ) ?>"><?php echo $item ?></option>
87
- <?php endforeach ?>
88
- </select>
89
-
90
- <?php endif ?>
91
- </div>
92
- </div>
93
- <?php endforeach ?>
94
- </div>
95
- </div>
96
- </fieldset>
97
- <?php endif ?>
98
- </div>
99
- <div class="modal-footer">
100
- <button ng-click=processForm() class="btn btn-info ab-popup-save"><?php _e( 'Create customer', 'bookly' ) ?></button>
101
- <button class=ab-reset-form data-dismiss=modal aria-hidden=true><?php _e( 'Cancel', 'bookly' ) ?></button>
102
- </div>
103
- </div><!-- /.modal-content -->
104
- </form>
105
- <div ng-show=loading class=loading-indicator>
106
- <span class="ab-loader"></span>
107
- </div>
108
- </div><!-- /.modal-dialog -->
109
- </div><!-- /.modal -->
110
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/customers/Components.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Customers;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Components
8
+ * @package BooklyLite\Backend\Modules\Customers
9
+ */
10
+ class Components extends Lib\Base\Components
11
+ {
12
+ /**
13
+ * Render customer dialog.
14
+ * @throws \Exception
15
+ */
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
+
41
+ $this->render( '_customer_dialog' );
42
+ }
43
+
44
+ }
backend/modules/customers/Controller.php ADDED
@@ -0,0 +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
+ }
backend/modules/customers/forms/Customer.php ADDED
@@ -0,0 +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
+ }
backend/modules/customers/resources/js/customers.js ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(function($) {
2
+
3
+ var
4
+ $customers_list = $('#bookly-customers-list'),
5
+ $filter = $('#bookly-filter'),
6
+ $check_all_button = $('#bookly-check-all'),
7
+ $customer_dialog = $('#bookly-customer-dialog'),
8
+ $add_button = $('#bookly-add'),
9
+ $delete_button = $('#bookly-delete'),
10
+ $delete_dialog = $('#bookly-delete-dialog'),
11
+ $delete_button_no = $('#bookly-delete-no'),
12
+ $delete_button_yes = $('#bookly-delete-yes'),
13
+ $remember_choice = $('#bookly-delete-remember-choice'),
14
+ remembered_choice,
15
+ row
16
+ ;
17
+
18
+ /**
19
+ * Init DataTables.
20
+ */
21
+ var dt = $customers_list.DataTable({
22
+ order: [[ 0, 'asc' ]],
23
+ info: false,
24
+ searching: false,
25
+ lengthChange: false,
26
+ pageLength: 25,
27
+ pagingType: 'numbers',
28
+ processing: true,
29
+ responsive: true,
30
+ serverSide: true,
31
+ ajax: {
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' },
49
+ {
50
+ responsivePriority: 1,
51
+ orderable: false,
52
+ searchable: false,
53
+ render: function ( data, type, row, meta ) {
54
+ return '<button type="button" class="btn btn-default" data-toggle="modal" data-target="#bookly-customer-dialog"><i class="glyphicon glyphicon-edit"></i> ' + BooklyL10n.edit + '</button>';
55
+ }
56
+ },
57
+ {
58
+ responsivePriority: 1,
59
+ orderable: false,
60
+ searchable: false,
61
+ render: function ( data, type, row, meta ) {
62
+ return '<input type="checkbox" value="' + row.id + '">';
63
+ }
64
+ }
65
+ ],
66
+ dom: "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
67
+ "<'row'<'col-sm-12'tr>>" +
68
+ "<'row pull-left'<'col-sm-12 bookly-margin-top-lg'p>>",
69
+ language: {
70
+ zeroRecords: BooklyL10n.zeroRecords,
71
+ processing: BooklyL10n.processing
72
+ }
73
+ });
74
+
75
+ /**
76
+ * Select all coupons.
77
+ */
78
+ $check_all_button.on('change', function () {
79
+ $customers_list.find('tbody input:checkbox').prop('checked', this.checked);
80
+ });
81
+
82
+ /**
83
+ * On coupon select.
84
+ */
85
+ $customers_list.on('change', 'tbody input:checkbox', function () {
86
+ $check_all_button.prop('checked', $customers_list.find('tbody input:not(:checked)').length == 0);
87
+ });
88
+
89
+ /**
90
+ * Edit customer.
91
+ */
92
+ $customers_list.on('click', 'button', function () {
93
+ row = dt.row($(this).closest('td'));
94
+ });
95
+
96
+ /**
97
+ * New coupon.
98
+ */
99
+ $add_button.on('click', function () {
100
+ row = null;
101
+ });
102
+
103
+ /**
104
+ * On show modal.
105
+ */
106
+ $customer_dialog.on('show.bs.modal', function () {
107
+ var $title = $customer_dialog.find('.modal-title');
108
+ var $button = $customer_dialog.find('.modal-footer button:first');
109
+ var customer;
110
+ if (row) {
111
+ customer = row.data();
112
+ $title.text(BooklyL10n.edit_customer);
113
+ $button.text(BooklyL10n.save);
114
+ } else {
115
+ customer = {
116
+ id : '',
117
+ wp_user_id : '',
118
+ name : '',
119
+ phone : '',
120
+ email : '',
121
+ notes : ''
122
+ };
123
+ $title.text(BooklyL10n.new_customer);
124
+ $button.text(BooklyL10n.create);
125
+ }
126
+
127
+ var $scope = angular.element(this).scope();
128
+ $scope.$apply(function ($scope) {
129
+ $scope.customer.id = customer.id;
130
+ $scope.customer.wp_user_id = customer.wp_user_id;
131
+ $scope.customer.name = customer.name;
132
+ $scope.customer.phone = customer.phone;
133
+ $scope.customer.email = customer.email;
134
+ $scope.customer.notes = customer.notes;
135
+ setTimeout(function() {
136
+ $customer_dialog.find('#phone').intlTelInput('setNumber', customer.phone);
137
+ }, 0);
138
+ });
139
+ });
140
+
141
+ /**
142
+ * Delete customers.
143
+ */
144
+ $delete_button.on('click', function () {
145
+ if (!remembered_choice) {
146
+ $delete_dialog.modal('show');
147
+ } else {
148
+ deleteCustomers(this, remembered_choice);
149
+ }}
150
+ );
151
+
152
+ $delete_button_no.on('click', function () {
153
+ if ($remember_choice.prop('checked')) {
154
+ remembered_choice = false;
155
+ }
156
+ deleteCustomers(this, false);
157
+ });
158
+
159
+ $delete_button_yes.on('click', function () {
160
+ if ($remember_choice.prop('checked')) {
161
+ remembered_choice = true;
162
+ }
163
+ deleteCustomers(this, true);
164
+ });
165
+
166
+ function deleteCustomers(button, with_wp_user) {
167
+ var ladda = Ladda.create(button);
168
+ ladda.start();
169
+
170
+ var data = [];
171
+ var $checkboxes = $customers_list.find('tbody input:checked');
172
+ $checkboxes.each(function () {
173
+ data.push(this.value);
174
+ });
175
+
176
+ $.ajax({
177
+ url : ajaxurl,
178
+ type : 'POST',
179
+ data : {
180
+ action : 'ab_delete_customers',
181
+ data : data,
182
+ with_wp_user : with_wp_user
183
+ },
184
+ dataType : 'json',
185
+ success : function(response) {
186
+ ladda.stop();
187
+ $delete_dialog.modal('hide');
188
+ if (response.success) {
189
+ dt.ajax.reload(null, false);
190
+ } else {
191
+ alert(response.data.message);
192
+ }
193
+ }
194
+ });
195
+ }
196
+
197
+ /**
198
+ * On filters change.
199
+ */
200
+ $filter.on('keyup', function () { dt.ajax.reload(); });
201
+
202
+ $('.bookly-limitation').on('click', function () {
203
+ booklyAlert({error: [BooklyL10n.limitations]});
204
+ });
205
+ });
206
+
207
+ (function() {
208
+ var module = angular.module('customer', ['customerDialog']);
209
+ module.controller('customerCtrl', function($scope) {
210
+ $scope.customer = {
211
+ id : '',
212
+ wp_user_id : '',
213
+ name : '',
214
+ phone : '',
215
+ email : '',
216
+ notes : ''
217
+ };
218
+ $scope.saveCustomer = function(customer) {
219
+ jQuery('#bookly-customers-list').DataTable().ajax.reload(null, false);
220
+ };
221
+ });
222
+ })();
backend/modules/customers/resources/js/ng-customer_dialog.js ADDED
@@ -0,0 +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
+ })();
backend/modules/customers/templates/_customer_dialog.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <script type="text/ng-template" id="bookly-customer-dialog.tpl">
3
+ <div id="bookly-customer-dialog" class="modal fade" tabindex=-1 role="dialog">
4
+ <div class="modal-dialog">
5
+ <div class="modal-content">
6
+ <div class="modal-header">
7
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
8
+ <div class="modal-title h2"><?php _e( 'New Customer', 'bookly' ) ?></div>
9
+ </div>
10
+ <div ng-show=loading class="modal-body">
11
+ <div class="bookly-loading"></div>
12
+ </div>
13
+ <div class="modal-body" ng-hide="loading">
14
+ <div class="form-group">
15
+ <label for="wp_user"><?php _e( 'User', 'bookly' ) ?></label>
16
+ <select ng-model="form.wp_user_id" class="form-control" id="wp_user">
17
+ <option value=""></option>
18
+ <?php foreach ( get_users( array( 'fields' => array( 'ID', 'display_name' ), 'orderby' => 'display_name' ) ) as $wp_user ) : ?>
19
+ <option value="<?php echo $wp_user->ID ?>">
20
+ <?php echo $wp_user->display_name ?>
21
+ </option>
22
+ <?php endforeach ?>
23
+ </select>
24
+ </div>
25
+
26
+ <div class="form-group">
27
+ <label for="username"><?php _e( 'Name', 'bookly' ) ?></label>
28
+ <input class="form-control" type="text" ng-model="form.name" id="username">
29
+ <span style="font-size: 11px;color: red" ng-show="errors.name.required"><?php _e( 'Required', 'bookly' ) ?></span>
30
+ </div>
31
+
32
+ <div class="form-group">
33
+ <label for="phone"><?php _e( 'Phone', 'bookly' ) ?></label>
34
+ <input class="form-control" type="text" ng-model=form.phone id="phone">
35
+ </div>
36
+
37
+ <div class="form-group">
38
+ <label for="email"><?php _e( 'Email', 'bookly' ) ?></label>
39
+ <input class="form-control" type="text" ng-model=form.email id="email">
40
+ </div>
41
+
42
+ <div class="form-group">
43
+ <label for="notes"><?php _e( 'Notes', 'bookly' ) ?></label>
44
+ <textarea class="form-control" ng-model=form.notes id="notes"></textarea>
45
+ </div>
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>
58
+ </div>
59
+ </div>
60
+ </script>
backend/modules/customers/templates/_export.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
backend/modules/customers/templates/_import.php ADDED
@@ -0,0 +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>
backend/modules/customers/templates/index.php ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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( '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>
30
+
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>
41
+ <th><?php _e( 'Payments', 'bookly' ) ?></th>
42
+ <th></th>
43
+ <th width="16"><input type="checkbox" id="bookly-check-all"></th>
44
+ </tr>
45
+ </thead>
46
+ </table>
47
+
48
+ <div class="text-right bookly-margin-top-lg">
49
+ <?php \BooklyLite\Lib\Utils\Common::deleteButton() ?>
50
+ </div>
51
+ </div>
52
+ </div>
53
+
54
+ <?php include '_import.php' ?>
55
+ <?php include '_export.php' ?>
56
+
57
+ <div id="bookly-delete-dialog" class="modal fade" tabindex=-1 role="dialog">
58
+ <div class="modal-dialog">
59
+ <div class="modal-content">
60
+ <div class="modal-header">
61
+ <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
62
+ <div class="modal-title h2"><?php _e( 'Delete customers', 'bookly' ) ?></div>
63
+ </div>
64
+ <div class="modal-body">
65
+ <?php _e( 'You are about to delete customers which may have WordPress accounts associated to them. Do you want to delete those accounts too (if there are any)?', 'bookly' ) ?>
66
+ <div class="checkbox">
67
+ <label>
68
+ <input id="bookly-delete-remember-choice" type="checkbox" /><?php _e( 'Remember my choice', 'bookly' ) ?>
69
+ </label>
70
+ </div>
71
+ </div>
72
+ <div class="modal-footer">
73
+ <button type="button" class="btn btn-danger ladda-button" id="bookly-delete-no" data-spinner-size="40" data-style="zoom-in">
74
+ <span class="ladda-label"><i class="glyphicon glyphicon-trash"></i> <?php _e( 'No, delete just customers', 'bookly' ) ?></span>
75
+ </button>
76
+ <button type="button" class="btn btn-danger ladda-button" id="bookly-delete-yes" data-spinner-size="40" data-style="zoom-in">
77
+ <span class="ladda-label"><i class="glyphicon glyphicon-trash"></i> <?php _e( 'Yes', 'bookly' ) ?></span>
78
+ </button>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>
83
+
84
+ <div ng-app="customer" ng-controller="customerCtrl">
85
+ <div customer-dialog=saveCustomer(customer) customer="customer"></div>
86
+ <?php \BooklyLite\Backend\Modules\Customers\Components::getInstance()->renderCustomerDialog() ?>
87
+ </div>
88
+ </div>
89
+ </div>
backend/modules/debug/Controller.php ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Debug;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Controller
8
+ * @package BooklyLite\Backend\Modules\Debug
9
+ */
10
+ class Controller extends Lib\Base\Controller
11
+ {
12
+
13
+ const TABLE_STATUS_OK = 1;
14
+ const TABLE_STATUS_ERROR = 0;
15
+ const TABLE_STATUS_WARNING = 2;
16
+
17
+ /**
18
+ * Default action
19
+ */
20
+ public function index()
21
+ {
22
+ $this->enqueueStyles( array(
23
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
24
+ 'module' => array( 'css/style.css' ),
25
+ ) );
26
+
27
+ $this->enqueueScripts( array(
28
+ 'backend' => array( 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ) ),
29
+ 'module' => array( 'js/debug.js' => array( 'jquery' ) ),
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
+ /**
97
+ * Get table structure
98
+ *
99
+ * @param string $tableName
100
+ * @return array
101
+ */
102
+ public static function getTableStructure( $tableName )
103
+ {
104
+ global $wpdb;
105
+
106
+ $tableStructure = array();
107
+ $results = $wpdb->get_results( 'DESCRIBE `' . $tableName . '`;' );
108
+ if ( $results ) {
109
+ foreach ( $results as $row ) {
110
+ $tableStructure[] = $row->Field;
111
+ }
112
+ }
113
+
114
+ return $tableStructure;
115
+ }
116
+
117
+ /**
118
+ * Get table constraints
119
+ *
120
+ * @param string $tableName
121
+ * @return array
122
+ */
123
+ public static function getTableConstraints( $tableName )
124
+ {
125
+ global $wpdb;
126
+
127
+ $tableConstraints = array();
128
+ $results = $wpdb->get_results(
129
+ 'SELECT
130
+ COLUMN_NAME,
131
+ CONSTRAINT_NAME,
132
+ REFERENCED_COLUMN_NAME,
133
+ REFERENCED_TABLE_NAME
134
+ FROM information_schema.KEY_COLUMN_USAGE
135
+ WHERE
136
+ TABLE_NAME = "' . $tableName . '"
137
+ AND CONSTRAINT_SCHEMA = SCHEMA()
138
+ AND CONSTRAINT_NAME <> "PRIMARY";'
139
+ );
140
+ if ( $results ) {
141
+ foreach ( $results as $row ) {
142
+ $constraint = array(
143
+ 'column_name' => $row->COLUMN_NAME,
144
+ 'referenced_table_name' => $row->REFERENCED_COLUMN_NAME,
145
+ 'referenced_column_name' => $row->REFERENCED_TABLE_NAME,
146
+ );
147
+ $key = $row->COLUMN_NAME . $row->REFERENCED_TABLE_NAME . $row->REFERENCED_COLUMN_NAME;
148
+ $tableConstraints[ $key ] = $constraint;
149
+ }
150
+ }
151
+
152
+ return $tableConstraints;
153
+ }
154
+
155
+ /**
156
+ * Verifying if table exists
157
+ *
158
+ * @param string $tableName
159
+ * @return int
160
+ */
161
+ public static function tableExists( $tableName )
162
+ {
163
+ global $wpdb;
164
+
165
+ return $wpdb->query( 'SHOW TABLES LIKE "' . $tableName . '"' );
166
+ }
167
+
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
+ }
backend/modules/debug/resources/css/style.css ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .panel {
2
+ margin-right: 20px;
3
+ margin-top: 20px;
4
+ }
5
+
6
+ .panel-body .list-group{
7
+ margin-bottom: 0;
8
+ }
9
+
10
+ .list-group-item{
11
+ padding: 5px 10px;
12
+ }
backend/modules/debug/resources/js/debug.js ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ jQuery(function($) {
2
+ $('.collapse').collapse('hide');
3
+ });
backend/modules/debug/templates/index.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
7
+ </div>
8
+ </div>
9
+ <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
10
+ <?php foreach ( $debug as $tableName => $table ) : ?>
11
+ <div class="panel <?php echo $table['status'] == 1 ? 'panel-success' : 'panel-danger' ?>">
12
+ <div class="panel-heading" role="tab" id="heading_<?php echo $tableName ?>">
13
+ <h4 class="panel-title">
14
+ <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#<?php echo $tableName ?>" aria-expanded="true" aria-controls="<?php echo $tableName ?>">
15
+ <?php echo $tableName ?>
16
+ </a>
17
+ </h4>
18
+ </div>
19
+ <div id="<?php echo $tableName ?>" class="panel-collapse collapse" role="tabpanel" aria-labelledby="<?php echo $tableName ?>">
20
+ <div class="panel-body">
21
+ <?php if ( $table['status'] ) : ?>
22
+ <h4>Columns</h4>
23
+ <table class="table table-condensed">
24
+ <thead>
25
+ <tr>
26
+ <th>Column name</th>
27
+ <th width="50">Status</th>
28
+ </tr>
29
+ </thead>
30
+ <tbody>
31
+ <?php foreach ( $table['fields'] as $field => $status ) : ?>
32
+ <tr class="<?php echo $status ? 'default' : 'danger' ?>">
33
+ <td><?php echo $field ?></td>
34
+ <td><?php echo $status ? 'OK' : 'ERROR' ?></td>
35
+ </tr>
36
+ <?php endforeach ?>
37
+ </tbody>
38
+ </table>
39
+ <?php if ( $table['constraints'] ) : ?>
40
+ <h4>Constraints</h4>
41
+ <table class="table table-condensed">
42
+ <thead>
43
+ <tr>
44
+ <th>Column name</th>
45
+ <th>Referenced table name</th>
46
+ <th>Referenced column name</th>
47
+ <th width="50">Status</th>
48
+ </tr>
49
+ </thead>
50
+ <tbody>
51
+ <?php foreach ( $table['constraints'] as $key => $constraint ) : ?>
52
+ <tr class="<?php echo $constraint['status'] ? 'default' : 'danger' ?>">
53
+ <td><?php echo $constraint['column_name'] ?></td>
54
+ <td><?php echo $constraint['referenced_table_name'] ?></td>
55
+ <td><?php echo $constraint['referenced_column_name'] ?></td>
56
+ <td><?php echo $constraint['status'] ? 'OK' : 'ERROR' ?></td>
57
+ </tr>
58
+ <?php endforeach ?>
59
+ </tbody>
60
+ </table>
61
+ <?php endif ?>
62
+ <?php else: ?>
63
+ Table does not exist
64
+ <?php endif ?>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ <?php endforeach ?>
69
+ </div>
70
+ </div>
71
+ </div>
backend/modules/notifications/AB_NotificationsController.php DELETED
@@ -1,70 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- class AB_NotificationsController extends AB_Controller {
4
-
5
- public function index()
6
- {
7
- $this->enqueueStyles( array(
8
- 'frontend' => array(
9
- 'css/ladda.min.css'
10
- ),
11
- 'module' => array(
12
- 'css/notifications.css'
13
- ),
14
- 'backend' => array(
15
- 'css/bookly.main-backend.css',
16
- 'bootstrap/css/bootstrap.min.css',
17
- )
18
- ) );
19
-
20
- $this->enqueueScripts( array(
21
- 'backend' => array(
22
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
23
- ),
24
- 'module' => array(
25
- 'js/notification.js' => array( 'jquery' ),
26
- ),
27
- 'frontend' => array(
28
- 'js/spin.min.js' => array( 'jquery' ),
29
- 'js/ladda.min.js' => array( 'jquery' ),
30
- )
31
- ) );
32
-
33
- $this->form = new AB_NotificationsForm( 'email' );
34
- $this->message = '';
35
- // Save action.
36
- if ( !empty ( $_POST ) ) {
37
- $this->message = __( 'Notification settings were updated successfully.', 'bookly' );
38
- // sender name
39
- if ( $this->hasParameter( 'ab_settings_sender_name' ) ) {
40
- update_option( 'ab_settings_sender_name', $this->getParameter( 'ab_settings_sender_name' ) );
41
- }
42
- // sender email
43
- if ( $this->hasParameter( 'ab_settings_sender_email' ) ) {
44
- update_option( 'ab_settings_sender_email', $this->getParameter( 'ab_settings_sender_email' ) );
45
- }
46
- if ( $this->hasParameter( 'ab_email_notification_reply_to_customers' ) ) {
47
- update_option( 'ab_email_notification_reply_to_customers', $this->getParameter( 'ab_email_notification_reply_to_customers' ) );
48
- }
49
- if ( $this->hasParameter( 'ab_email_content_type' ) ) {
50
- update_option( 'ab_email_content_type', $this->getParameter( 'ab_email_content_type' ) );
51
- }
52
- }
53
-
54
- $this->render( 'index' );
55
- }
56
-
57
- // Protected methods.
58
-
59
- /**
60
- * Override parent method to add 'wp_ajax_ab_' prefix
61
- * so current 'execute*' methods look nicer.
62
- *
63
- * @param string $prefix
64
- */
65
- protected function registerWpActions( $prefix = '' )
66
- {
67
- parent::registerWpActions( 'wp_ajax_ab_' );
68
- }
69
-
70
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/notifications/Controller.php ADDED
@@ -0,0 +1,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
+ 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
+ }
backend/modules/notifications/forms/AB_NotificationsForm.php DELETED
@@ -1,166 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- class AB_NotificationsForm extends AB_Form {
4
-
5
- public $types = array(
6
- 'client_new_appointment',
7
- 'staff_new_appointment',
8
- 'staff_cancelled_appointment',
9
- 'client_new_wp_user',
10
- 'client_reminder',
11
- 'client_follow_up',
12
- 'staff_agenda',
13
- );
14
- public $gateway;
15
-
16
- public function __construct( $gateway = 'email' )
17
- {
18
- /*
19
- * make Visual Mode as default (instead of Text Mode)
20
- * allowed: tinymce - Visual Mode, html - Text Mode, test - no one Mode selected
21
- */
22
- add_filter( 'wp_default_editor', create_function( '', 'return \'tinymce\';' ) );
23
- $this->gateway = $gateway;
24
- $this->setFields( array(
25
- 'active',
26
- 'subject',
27
- 'message',
28
- 'copy',
29
- ) );
30
-
31
- $this->load();
32
- }
33
-
34
- public function bind( array $_post = array(), array $files = array() )
35
- {
36
- foreach ( $this->types as $type ) {
37
- foreach ( $this->fields as $field ) {
38
- if ( isset($_post[ $type ][ $field ] ) ) {
39
- $this->data[ $type ][ $field ] = $_post[ $type ][ $field ];
40
- }
41
- }
42
- }
43
- }
44
-
45
- /**
46
- * @return bool|void
47
- */
48
- public function save()
49
- {
50
- return;
51
- }
52
-
53
- public function load()
54
- {
55
- foreach ( $this->types as $type ) {
56
- if ( ( $object = new AB_Notification() ) && $object->loadBy( array( 'type' => $type, 'gateway' => $this->gateway ) ) ) {
57
- $this->data[ $type ][ 'active' ] = $object->get( 'active' );
58
- $this->data[ $type ][ 'subject' ] = $object->get( 'subject' );
59
- $this->data[ $type ][ 'message' ] = $object->get( 'message' );
60
- $this->data[ $type ][ 'name' ] = $this->getNotificationName( $type );
61
- if ( $type == 'staff_new_appointment' || $type == 'staff_cancelled_appointment' ) {
62
- $this->data[ $type ][ 'copy' ] = $object->get( 'copy' );
63
- }
64
- }
65
- }
66
- }
67
-
68
- /**
69
- * @param $type
70
- * @return mixed
71
- */
72
- public function getNotificationName ( $type )
73
- {
74
- $notifications_name = array(
75
- 'client_new_appointment' => __( 'Notification to customer about appointment details', 'bookly' ),
76
- 'staff_new_appointment' => __( 'Notification to staff member about appointment details', 'bookly' ),
77
- 'staff_cancelled_appointment' => __( 'Notification to staff member about appointment cancellation', 'bookly' ),
78
- 'client_new_wp_user' => __( 'Notification to customer about their WordPress user login details', 'bookly' ),
79
- 'client_reminder' => __( 'Evening reminder to customer about next day appointment (requires cron setup)', 'bookly' ),
80
- 'client_follow_up' => __( 'Follow-up message in the same day after appointment (requires cron setup)', 'bookly' ),
81
- 'staff_agenda' => __( 'Evening notification with the next day agenda to staff member (requires cron setup)', 'bookly' ),
82
- );
83
-
84
- return $notifications_name[ $type ];
85
- }
86
-
87
- /**
88
- * Render the "active" form
89
- *
90
- * @param $type
91
- * @return string
92
- */
93
- public function renderActive( $type )
94
- {
95
- $title = isset( $this->data[ $type ]['name'] ) ? $this->data[ $type ]['name'] : '';
96
-
97
- return $title;
98
- }
99
-
100
- /**
101
- * @param $type
102
- * @return string
103
- */
104
- public function renderSubject( $type )
105
- {
106
- $id = $type . '_subject';
107
- $name = $type . '[subject]';
108
- $value = isset( $this->data[ $type ]['subject'] ) ? $this->data[$type]['subject'] : '';
109
-
110
- return "<label class='ab-form-label' for='{$id}' style='margin-top: 19px;'>" . __( 'Subject', 'bookly' ) . "</label><input type='text' size='70' id='{$id}' name='{$name}' value='{$value}' class='form-control' />";
111
- }
112
-
113
- /**
114
- * @param $type
115
- * @return string
116
- */
117
- public function renderMessage( $type )
118
- {
119
- $id = $type.'_message';
120
- $name = $type.'[message]';
121
- $value = isset( $this->data[$type]['message'] ) ? $this->data[$type]['message'] : '';
122
-
123
- if ( 'sms' == $this->gateway ) {
124
- return "<textarea rows='6' id='{$id}' name='{$name}' class='ab-sms-message pull-left'>".esc_textarea( $value )."</textarea>";
125
- } else {
126
- $settings = array(
127
- 'textarea_name' => $name,
128
- 'media_buttons' => false,
129
- 'tinymce' => array(
130
- 'theme_advanced_buttons1' => 'formatselect,|,bold,italic,underline,|,'.
131
- 'bullist,blockquote,|,justifyleft,justifycenter'.
132
- ',justifyright,justifyfull,|,link,unlink,|'.
133
- ',spellchecker,wp_fullscreen,wp_adv'
134
- )
135
- );
136
-
137
- wp_editor( $value, $id, $settings );
138
- }
139
- }
140
-
141
- /**
142
- * Render the "copy" form
143
- *
144
- * @param $type
145
- * @return string
146
- */
147
- public function renderCopy( $type )
148
- {
149
- $id = $type . '_copy';
150
- $name = $type . '[copy]';
151
- $checked = checked( ( isset( $this->data[ $type ]['copy'] ) && intval( $this->data[ $type ]['copy'] ) ), true, false );
152
- $title = __( 'Send copy to administrators', 'bookly' );
153
-
154
- return "<div class='ab-form-row'>
155
- <label class='ab-form-label'></label>
156
- <div>
157
- <legend>
158
- <input name='{$name}' type=hidden value=0 />
159
- <input id='{$id}' name='{$name}' type=checkbox value=1 {$checked} />
160
- <label for='{$id}'> {$title}</label>
161
- </legend>
162
- </div>
163
- </div>";
164
- }
165
-
166
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/notifications/forms/Notifications.php ADDED
@@ -0,0 +1,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_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
+ }
backend/modules/notifications/resources/css/notifications.css DELETED
@@ -1,29 +0,0 @@
1
- /* notifications */
2
- .ab-notifications tr td { padding-left: 10px; }
3
- .ab-notifications input[type=text],
4
- .ab-notifications select { margin-top: 10px; min-width: 250px; }
5
- .ab-notifications .ab-form-field { padding-top: 10px; }
6
- .ab-notifications .ab-toggle-arrow { position: absolute; right: 0; top: 9px; width: 24px; height: 24px; cursor: pointer; }
7
- .ab-notifications .ab-form-label { display: block; float: left; margin-top: 3px; min-width: 80px; }
8
- .ab-notifications .ab-form-row { padding: 3px 0; overflow: hidden; }
9
- .ab-notifications .ab-form-row input[type="text"] { width: 700px!important; position: relative; z-index: 9; }
10
- .ab-notifications #message_editor { margin-top: -20px; }
11
- .ab-notifications legend { margin: 0; border: 0; line-height: normal; }
12
- .ab-notifications legend input { margin: 0!important; }
13
- .ab-notifications legend label { display: inline; font-size: 17px; }
14
- .ab-notifications #message_editor .wp-editor-wrap { margin-left: 81px; }
15
- .ab-notifications .panel-title > input { margin: 0 5px 0 0; }
16
- .ab-notifications .ab-codes table tr td { padding: 0 20px 0 0; font-size: 12px;}
17
- .ab-notifications .ab-codes table tr td input { cursor: pointer; border: none; width: 250px; }
18
- .ab-notifications .wp-editor-tools { position: relative; top: 26px; }
19
- .ab-notifications a[data-toggle="collapse"] {
20
- outline: none;
21
- box-shadow: none;
22
- padding-right: 25px;
23
- background: url("../../../../resources/images/notifications-arrow-up.png") 100% 50% no-repeat;
24
- background-size: 17px 17px;
25
- }
26
- .ab-notifications a[data-toggle="collapse"].collapsed {
27
- background: url("../../../../resources/images/notifications-arrow-down.png") 100% 50% no-repeat;
28
- background-size: 17px 17px;
29
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/notifications/resources/js/ng-app.js ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ;(function() {
2
+ var module = angular.module('notifications', []);
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
+ },
28
+ error : function() {
29
+ $rootScope.$apply(deferred.resolve);
30
+ }
31
+ });
32
+
33
+ return deferred.promise;
34
+ }
35
+ };
36
+
37
+ return ds;
38
+ });
39
+
40
+ module.controller('emailNotifications', function($scope, dataSource) {
41
+ $scope.showTestEmailNotificationDialog = function(){
42
+ showTestEmailNotificationDialog();
43
+ }
44
+ });
45
+
46
+ module.controller('testEmailNotificationsDialogCtrl', function($scope, dataSource, $timeout) {
47
+ $scope.loading = true;
48
+ $scope.mailSentAlert = false;
49
+ $scope.allNotifications = false;
50
+ $scope.toEmail = 'admin@example.com';
51
+ $scope.dataSource = dataSource;
52
+
53
+ dataSource.loadData().then(function(){
54
+ $scope.loading = false;
55
+ $scope.allNotificationsChecked();
56
+ });
57
+
58
+ $scope.$watch('notifications', function(newVal, oldVal){
59
+ $scope.allNotificationsChecked();
60
+ }, true);
61
+
62
+ $scope.toggleAllNotifications = function(){
63
+ var active = $scope.allNotifications ? '1' : '0';
64
+ angular.forEach($scope.dataSource.notifications, function(notification){
65
+ notification.active = active;
66
+ });
67
+ };
68
+
69
+ $scope.allNotificationsChecked = function(){
70
+ var count = $scope.selectedNotificationsCount();
71
+ var totalCount = Object.keys($scope.dataSource.notifications).length;
72
+ $scope.allNotifications = count===totalCount;
73
+ };
74
+
75
+ $scope.notificationChecked = function(){
76
+ $scope.allNotificationsChecked();
77
+ };
78
+
79
+ $scope.selectedNotificationsCount = function(){
80
+ var count = 0;
81
+ angular.forEach($scope.dataSource.notifications, function(notification){
82
+ count += notification.active==='1'?1:0;
83
+ });
84
+ return count;
85
+ };
86
+
87
+ $scope.testEmailNotifications = function(){
88
+ };
89
+
90
+ });
91
+
92
+ })();
93
+
94
+ var showTestEmailNotificationDialog = function () {
95
+ jQuery('#ab_test_email_notifications_dialog').modal('show');
96
+ };
backend/modules/notifications/resources/js/notification.js CHANGED
@@ -6,25 +6,29 @@ jQuery(function($) {
6
  $('#toplevel_page_ab-system > ul').css('margin-left', '0px');
7
 
8
  /* exclude checkboxes in form */
9
- var $checkboxes = $('.ab-notifications .panel-title > input:checkbox[id!=_active]');
10
 
11
  $checkboxes.change(function () {
12
  $(this).parents('.panel-heading').next().collapse(this.checked ? 'show' : 'hide');
13
- $('#lite_notice').modal('show');
14
  });
15
 
16
- // filter sender name and email
17
- var escapeXSS = function (infected) {
18
- var regexp = /([<|(]("[^"]*"|'[^']*'|[^'">])*[>|)])/gi;
19
- return infected.replace(regexp, '');
20
- };
21
- $('input.ab-sender').on('change', function() {
22
- var $val = $(this).val();
23
- $(this).val(escapeXSS($val));
24
  });
25
 
26
- $('.ab-popover').popover({
27
- trigger : 'hover'
 
 
 
 
 
28
  });
29
 
 
 
 
30
  });
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
@@ -1,24 +1,33 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <tr><td><input value="[[APPOINTMENT_DATE]]" readonly="readonly" onclick="this.select()" /> - <?php _e('date of appointment', 'bookly') ?></td></tr>
3
- <tr><td><input value="[[APPOINTMENT_TIME]]" readonly="readonly" onclick="this.select()" /> - <?php _e('time of appointment', 'bookly') ?></td></tr>
4
- <tr><td><input value="[[CANCEL_APPOINTMENT]]" readonly="readonly" onclick="this.select()" /> - <?php _e('cancel appointment link', 'bookly') ?></td></tr>
5
- <tr><td><input value="[[CANCEL_APPOINTMENT_URL]]" readonly="readonly" onclick="this.select()" /> - <?php echo esc_html( __('URL for cancel appointment link (to use inside <a> tag)', 'bookly') ) ?></td></tr>
6
- <tr><td><input value="[[CATEGORY_NAME]]" readonly="readonly" onclick="this.select()" /> - <?php _e('name of category', 'bookly') ?></td></tr>
7
- <tr><td><input value="[[CLIENT_EMAIL]]" readonly="readonly" onclick="this.select()" /> - <?php _e('email of client', 'bookly') ?></td></tr>
8
- <tr><td><input value="[[CLIENT_NAME]]" readonly="readonly" onclick="this.select()" /> - <?php _e('name of client', 'bookly') ?></td></tr>
9
- <tr><td><input value="[[CLIENT_PHONE]]" readonly="readonly" onclick="this.select()" /> - <?php _e('phone of client', 'bookly') ?></td></tr>
10
- <tr><td><input value="[[COMPANY_NAME]]" readonly="readonly" onclick="this.select()" /> - <?php _e('name of your company', 'bookly') ?></td></tr>
11
- <tr><td><input value="[[COMPANY_LOGO]]" readonly="readonly" onclick="this.select()" /> - <?php _e('your company logo', 'bookly') ?></td></tr>
12
- <tr><td><input value="[[COMPANY_ADDRESS]]" readonly="readonly" onclick="this.select()" /> - <?php _e('address of your company', 'bookly') ?></td></tr>
13
- <tr><td><input value="[[COMPANY_PHONE]]" readonly="readonly" onclick="this.select()" /> - <?php _e('your company phone', 'bookly') ?></td></tr>
14
- <tr><td><input value="[[COMPANY_WEBSITE]]" readonly="readonly" onclick="this.select()" /> - <?php _e('this web-site address', 'bookly') ?></td></tr>
15
- <tr><td><input value="[[CUSTOM_FIELDS]]" readonly="readonly" onclick="this.select()" /> - <?php _e('combined values of all custom fields', 'bookly') ?></td></tr>
16
- <tr><td><input value="[[CUSTOM_FIELDS_2C]]" readonly="readonly" onclick="this.select()" /> - <?php _e('combined values of all custom fields (formatted in 2 columns)', 'bookly') ?></td></tr>
17
- <tr><td><input value="[[NUMBER_OF_PERSONS]]" readonly="readonly" onclick="this.select()" /> - <?php _e('number of persons', 'bookly') ?></td></tr>
18
- <tr><td><input value="[[SERVICE_NAME]]" readonly="readonly" onclick="this.select()" /> - <?php _e('name of service', 'bookly') ?></td></tr>
19
- <tr><td><input value="[[SERVICE_PRICE]]" readonly="readonly" onclick="this.select()" /> - <?php _e('price of service', 'bookly') ?></td></tr>
20
- <tr><td><input value="[[STAFF_EMAIL]]" readonly="readonly" onclick="this.select()" /> - <?php _e('email of staff', 'bookly') ?></td></tr>
21
- <tr><td><input value="[[STAFF_NAME]]" readonly="readonly" onclick="this.select()" /> - <?php _e('name of staff', 'bookly') ?></td></tr>
22
- <tr><td><input value="[[STAFF_PHONE]]" readonly="readonly" onclick="this.select()" /> - <?php _e('phone of staff', 'bookly') ?></td></tr>
23
- <tr><td><input value="[[STAFF_PHOTO]]" readonly="readonly" onclick="this.select()" /> - <?php _e('photo of staff', 'bookly') ?></td></tr>
24
- <tr><td><input value="[[TOTAL_PRICE]]" readonly="readonly" onclick="this.select()" /> - <?php _e('total price of booking (service price multiplied by the number of persons)', 'bookly') ?></td></tr>
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ $codes = array(
3
+ array( 'code' => 'appointment_date', 'description' => __( 'date of appointment', 'bookly' ) ),
4
+ array( 'code' => 'appointment_time', 'description' => __( 'time of appointment', 'bookly' ) ),
5
+ array( 'code' => 'booking_number', 'description' => __( 'booking number', 'bookly' ) ),
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' ) ),
12
+ array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) ),
13
+ array( 'code' => 'company_name', 'description' => __( 'name of company', 'bookly' ) ),
14
+ array( 'code' => 'company_logo', 'description' => __( 'company logo', 'bookly' ) ),
15
+ array( 'code' => 'company_address', 'description' => __( 'address of company', 'bookly' ) ),
16
+ array( 'code' => 'company_phone', 'description' => __( 'company phone', 'bookly' ) ),
17
+ array( 'code' => 'company_website', 'description' => __( 'company web-site address', 'bookly' ) ),
18
+ array( 'code' => 'custom_fields', 'description' => __( 'combined values of all custom fields', 'bookly' ) ),
19
+ array( 'code' => 'custom_fields_2c', 'description' => __( 'combined values of all custom fields (formatted in 2 columns)', 'bookly' ) ),
20
+ array( 'code' => 'google_calendar_url', 'description' => esc_html__( 'URL for adding event to client\'s Google Calendar (to use inside <a> tag)', 'bookly' ) ),
21
+ array( 'code' => 'number_of_persons', 'description' => __( 'number of persons', 'bookly' ) ),
22
+ array( 'code' => 'payment_type', 'description' => __( 'payment type', 'bookly' ) ),
23
+ array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) ),
24
+ array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) ),
25
+ array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ) ),
26
+ array( 'code' => 'staff_email', 'description' => __( 'email of staff', 'bookly' ) ),
27
+ array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) ),
28
+ array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) ),
29
+ array( 'code' => 'staff_phone', 'description' => __( 'phone of staff', 'bookly' ) ),
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 ) );
backend/modules/notifications/templates/_codes_cart.php ADDED
@@ -0,0 +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 ) );
backend/modules/notifications/templates/_codes_client_new_wp_user.php CHANGED
@@ -1,12 +1,15 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <tr><td><input value="[[CLIENT_EMAIL]]" readonly="readonly" onclick="this.select()" /> - <?php _e('email of client', 'bookly') ?></td></tr>
3
- <tr><td><input value="[[CLIENT_NAME]]" readonly="readonly" onclick="this.select()" /> - <?php _e('name of client', 'bookly') ?></td></tr>
4
- <tr><td><input value="[[CLIENT_PHONE]]" readonly="readonly" onclick="this.select()" /> - <?php _e('phone of client', 'bookly') ?></td></tr>
5
- <tr><td><input value="[[COMPANY_NAME]]" readonly="readonly" onclick="this.select()" /> - <?php _e('name of your company', 'bookly') ?></td></tr>
6
- <tr><td><input value="[[COMPANY_LOGO]]" readonly="readonly" onclick="this.select()" /> - <?php _e('your company logo', 'bookly') ?></td></tr>
7
- <tr><td><input value="[[COMPANY_ADDRESS]]" readonly="readonly" onclick="this.select()" /> - <?php _e('address of your company', 'bookly') ?></td></tr>
8
- <tr><td><input value="[[COMPANY_PHONE]]" readonly="readonly" onclick="this.select()" /> - <?php _e('your company phone', 'bookly') ?></td></tr>
9
- <tr><td><input value="[[COMPANY_WEBSITE]]" readonly="readonly" onclick="this.select()" /> - <?php _e('this web-site address', 'bookly') ?></td></tr>
10
- <tr><td><input value="[[NEW_USERNAME]]" readonly="readonly" onclick="this.select()" /> - <?php _e('customer new username', 'bookly') ?></td></tr>
11
- <tr><td><input value="[[NEW_PASSWORD]]" readonly="readonly" onclick="this.select()" /> - <?php _e('customer new password', 'bookly') ?></td></tr>
12
- <tr><td><input value="[[SITE_ADDRESS]]" readonly="readonly" onclick="this.select()" /> - <?php _e('site address', 'bookly') ?></td></tr>
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ $codes = array(
3
+ array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) ),
4
+ array( 'code' => 'client_name', 'description' => __( 'name of client', 'bookly' ) ),
5
+ array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) ),
6
+ array( 'code' => 'company_name', 'description' => __( 'name of your company', 'bookly' ) ),
7
+ array( 'code' => 'company_logo', 'description' => __( 'your company logo', 'bookly' ) ),
8
+ array( 'code' => 'company_address', 'description' => __( 'address of your company', 'bookly' ) ),
9
+ array( 'code' => 'company_phone', 'description' => __( 'your company phone', 'bookly' ) ),
10
+ array( 'code' => 'company_website', 'description' => __( 'this web-site address', 'bookly' ) ),
11
+ array( 'code' => 'new_username', 'description' => __( 'customer new username', 'bookly' ) ),
12
+ array( 'code' => 'new_password', 'description' => __( 'customer new password', 'bookly' ) ),
13
+ array( 'code' => 'site_address', 'description' => __( 'site address', 'bookly' ) ),
14
+ );
15
+ \BooklyLite\Lib\Utils\Common::Codes( $codes );
backend/modules/notifications/templates/_codes_staff_agenda.php CHANGED
@@ -1,4 +1,7 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <tr><td><input value="[[TOMORROW_DATE]]" readonly="readonly" onclick="this.select()" /> - <?php _e('date of next day', 'bookly') ?></td></tr>
3
- <tr><td><input value="[[NEXT_DAY_AGENDA]]" readonly="readonly" onclick="this.select()" /> - <?php _e('staff agenda for next day', 'bookly') ?></td></tr>
4
- <tr><td><input value="[[STAFF_NAME]]" readonly="readonly" onclick="this.select()" /> - <?php _e('name of staff', 'bookly') ?></td></tr>
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ $codes = array(
3
+ array( 'code' => 'tomorrow_date', 'description' => __( 'date of next day', 'bookly' ) ),
4
+ array( 'code' => 'next_day_agenda', 'description' => __( 'staff agenda for next day', 'bookly' ) ),
5
+ array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) ),
6
+ );
7
+ \BooklyLite\Lib\Utils\Common::Codes( $codes );
backend/modules/notifications/templates/_test_email_notifications_modal.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div ng-controller=testEmailNotificationsDialogCtrl>
3
+ <div id=ab_test_email_notifications_dialog class="modal fade" tabindex=-1 role="dialog">
4
+ <div class="modal-dialog">
5
+ <div class="modal-content">
6
+ <div ng-show=loading class="bookly-loading"></div>
7
+
8
+ <div ng-hide=loading>
9
+ <form ng-submit="testEmailNotifications()">
10
+ <div class="modal-header">
11
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
12
+ <div class="modal-title h2"><?php _e( 'Test Email Notifications', 'bookly' ) ?></div>
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>
47
+ <div>
48
+ <div class="btn-group bookly-margin-bottom-lg">
49
+ <button class="btn btn-default btn-block dropdown-toggle bookly-flexbox" data-toggle="dropdown">
50
+ <div class="bookly-flex-cell text-left" style="width: 100%">
51
+ <?php _e( 'Notification templates', 'bookly' ) ?> ({{selectedNotificationsCount()}})
52
+ </div>
53
+ <div class="bookly-flex-cell"><div class="bookly-margin-left-md"><span class="caret"></span></div></div>
54
+ </button>
55
+ <ul class="dropdown-menu" style="width: 570px">
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>
63
+ </li>
64
+ <li role="separator" class="divider"></li>
65
+
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>
78
+ </div>
79
+ </div>
80
+ <div class="modal-footer">
81
+ <?php \BooklyLite\Lib\Utils\Common::submitButton( '', '', __( 'Send', 'bookly' ) ) ?>
82
+ </div>
83
+ </form>
84
+ </div>
85
+ </div><!-- /.modal-content -->
86
+ </div><!-- /.modal-dialog -->
87
+ </div><!-- /.modal -->
88
+
89
+ <div class="modal fade" id="ab--modal" tabindex="-1" role="dialog">
90
+ <div class="modal-dialog" role="document">
91
+ <div class="modal-content">
92
+ <div class="modal-header">
93
+ <button type="button" class="close" data-dismiss="modal" aria-label="<?php esc_attr_e( 'Close', 'bookly' ) ?>"><span aria-hidden="true">&times;</span></button>
94
+ <div class="modal-title h2"></div>
95
+ </div>
96
+ <div class="modal-body"></div>
97
+ <div class="modal-footer">
98
+ <button type="button" class="btn btn-default" data-dismiss="modal">
99
+ <?php _e( 'Close', 'bookly' ) ?>
100
+ </button>
101
+ </div>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </div>
backend/modules/notifications/templates/index.php CHANGED
@@ -3,135 +3,149 @@
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
  ?>
7
- <form method="post">
8
- <div class="panel panel-default">
9
- <div class="panel-heading">
10
- <h3 class="panel-title"><?php _e( 'Email Notifications', 'bookly' ) ?></h3>
11
- </div>
12
- <div class="panel-body">
13
- <?php AB_Utils::notice( $message ) ?>
14
- <div class="ab-notifications">
15
- <table>
16
- <tr><!-- sender name -->
17
- <td>
18
- <label for="ab_settings_sender_name" style="display: inline;"><?php _e( 'Sender name', 'bookly' ) ?></label>
19
- </td>
20
- <td>
21
- <input id="ab_settings_sender_name" name="ab_settings_sender_name" class="form-control ab-inline-block ab-auto-w ab-sender" type="text" value="<?php echo esc_attr( $ab_settings_sender_name ) ?>"/>
22
- </td>
23
- <td></td>
24
- </tr>
25
- <tr><!-- sender email -->
26
- <td>
27
- <label for="ab_settings_sender_email" style="display: inline;"><?php _e( 'Sender email', 'bookly' ) ?></label>
28
- </td>
29
- <td>
30
- <input id="ab_settings_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 ) ?>"/>
31
- </td>
32
- <td></td>
33
- </tr>
34
- <tr>
35
- <td>
36
- <label for="ab_email_notification_reply_to_customers" style="display: inline;"><?php _e( 'Reply directly to customers', 'bookly' ) ?></label>
37
- </td>
38
- <td>
39
- <?php AB_Utils::optionToggle( 'ab_email_notification_reply_to_customers' ) ?>
40
- </td>
41
- <td>
42
- <?php AB_Utils::popover( __( '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' ) ) ?>
43
- </td>
44
- </tr>
45
- <tr>
46
- <td>
47
- <label for="ab_email_content_type" style="display: inline;"><?php _e( 'Send emails as', 'bookly' ) ?></label>
48
- </td>
49
- <td>
50
- <?php AB_Utils::optionToggle( 'ab_email_content_type', array( 't' => array( 'html', __( 'HTML', 'bookly' ) ), 'f' => array( 'plain', __( 'Text', 'bookly' ) ) ) ) ?>
51
- </td>
52
- <td>
53
- <?php AB_Utils::popover( __( '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' ) ) ?>
54
- </td>
55
- </tr>
56
- </table>
57
  </div>
58
- <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
59
- <?php $notif_id = 0; ?>
60
- <?php foreach ( $form->types as $type ): ?>
61
- <?php $notif_id += 1;
62
- $form_data = $form->getData();
63
- $active = isset($form_data[ $type ]['active']) ? $form_data[ $type ]['active'] : false;
64
- ?>
65
- <div class="panel panel-default ab-notifications">
66
- <div class="panel-heading" role="tab" id="headingOne">
67
- <h4 class="panel-title">
68
- <input name="<?php echo $type ?>[active]" value="0" type="checkbox" checked="checked" class="hidden">
69
- <input id="<?php echo $type ?>_active" name="<?php echo $type ?>[active]" value="1" type="checkbox" <?php checked( $active ) ?> />
70
- <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse_<?php echo $notif_id ?>">
71
- <?php echo $form->renderActive( $type ) ?>
72
- </a>
73
- </h4>
74
- </div>
75
- <div id="collapse_<?php echo $notif_id ?>" class="panel-collapse collapse">
76
- <div class="panel-body">
77
- <div class="ab-form-field">
78
- <div class="ab-form-row">
79
- <?php echo $form->renderSubject( $type ) ?>
80
  </div>
81
- <div id="message_editor" class="ab-form-row">
82
- <label class="ab-form-label" style="margin-top: 35px;"><?php _e( 'Message', 'bookly' ) ?></label>
83
- <?php echo $form->renderMessage( $type ) ?>
 
 
 
 
 
 
 
 
 
 
 
 
84
  </div>
85
- <?php if ( $type == 'staff_new_appointment' || $type == 'staff_cancelled_appointment' ): ?>
86
- <?php echo $form->renderCopy( $type ) ?>
87
- <?php endif ?>
88
- <div class="ab-form-row">
89
- <label class="ab-form-label"><?php _e( 'Codes', 'bookly' ) ?></label>
90
- <div class="ab-codes left">
91
- <table>
92
- <tbody>
93
- <?php
94
- switch ( $type ) {
95
- case 'staff_agenda': include '_codes_staff_agenda.php'; break;
96
- case 'client_new_wp_user': include '_codes_client_new_wp_user.php'; break;
97
- default: include '_codes.php';
98
- }
99
- ?>
100
- </tbody>
101
- </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  </div>
103
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  </div>
106
  </div>
107
  </div>
108
- <?php endforeach ?>
109
- </div>
110
- <div class="ab-notifications">
111
- <?php
112
- echo '<i>' . __( 'To send scheduled notifications please execute the following script hourly with your cron:', 'bookly' ) . '</i><br />';
113
- echo '<b>php -f ' . realpath( AB_PATH . '/lib/utils/send_notifications_cron.php' ) . '</b>';
114
- ?>
115
- </div>
116
- </div>
117
- <div class="panel-footer">
118
- <?php AB_Utils::submitButton() ?>
119
- <?php AB_Utils::resetButton() ?>
120
  </div>
121
- </div>
122
- </form>
123
- <div class="modal fade" id="lite_notice" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
124
- <div class="modal-dialog">
125
- <div class="modal-content">
126
- <div class="modal-header">
127
- <h4 class="modal-title"><?php _e('Notice', 'bookly') ?></h4>
128
- </div>
129
- <div class="modal-body">
130
- <?php _e('This function is disabled in the lite version of Bookly. If you find the plugin useful for your business please consider buying a licence for the full version. It costs just $46 and for this money you will get many useful functions, lifetime free update and excellent support! More information can be found here', 'bookly'); ?>: <a href="http://booking-wp-plugin.com" target="_blank">http://booking-wp-plugin.com</a>
131
- </div>
132
- <div class="modal-footer">
133
- <button type="button" class="btn btn-default" data-dismiss="modal"><?php _e('Close', 'bookly') ?></button>
134
- </div>
135
- </div><!-- /.modal-content -->
136
- </div><!-- /.modal-dialog -->
137
- </div><!-- /.modal -->
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>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/payment/AB_PaymentController.php DELETED
@@ -1,84 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- class AB_PaymentController extends AB_Controller {
4
-
5
- public function index() {
6
- /** @var WP_Locale $wp_locale */
7
- global $wp_locale;
8
-
9
- $this->enqueueStyles( array(
10
- 'backend' => array(
11
- 'css/bookly.main-backend.css',
12
- 'bootstrap/css/bootstrap.min.css',
13
- 'css/daterangepicker.css',
14
- 'css/bootstrap-select.min.css',
15
- )
16
- ) );
17
-
18
- $this->enqueueScripts( array(
19
- 'backend' => array(
20
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
21
- 'js/moment.min.js',
22
- 'js/daterangepicker.js' => array( 'jquery' ),
23
- 'js/bootstrap-select.min.js',
24
- )
25
- ) );
26
-
27
- wp_localize_script( 'ab-daterangepicker.js', 'BooklyL10n', array(
28
- 'today' => __( 'Today', 'bookly' ),
29
- 'yesterday' => __( 'Yesterday', 'bookly' ),
30
- 'last_7' => __( 'Last 7 Days', 'bookly' ),
31
- 'last_30' => __( 'Last 30 Days', 'bookly' ),
32
- 'this_month' => __( 'This Month', 'bookly' ),
33
- 'last_month' => __( 'Last Month', 'bookly' ),
34
- 'custom_range' => __( 'Custom Range', 'bookly' ),
35
- 'apply' => __( 'Apply', 'bookly' ),
36
- 'cancel' => __( 'Cancel', 'bookly' ),
37
- 'to' => __( 'To', 'bookly' ),
38
- 'from' => __( 'From', 'bookly' ),
39
- 'months' => array_values( $wp_locale->month ),
40
- 'days' => array_values( $wp_locale->weekday_abbrev ),
41
- 'startOfWeek' => (int) get_option( 'start_of_week' ),
42
- 'mjsDateFormat' => AB_DateTimeUtils::convertFormat( 'date', AB_DateTimeUtils::FORMAT_MOMENT_JS ),
43
- ));
44
-
45
- $this->collection = false;
46
-
47
- $this->types = array();
48
- $this->customers = array();
49
- $this->providers = array();
50
- $this->services = array();
51
-
52
- $this->render( 'index' );
53
- }
54
-
55
- /**
56
- * Sort payments.
57
- */
58
- public function executeSortPayments()
59
- {
60
- $this->executeFilterPayments();
61
- }
62
-
63
- /**
64
- * Filter payments.
65
- */
66
- public function executeFilterPayments()
67
- {
68
- $this->render( '_body', array( 'collection' => false ) );
69
- exit;
70
- }
71
-
72
-
73
- /**
74
- * Override parent method to add 'wp_ajax_ab_' prefix
75
- * so current 'execute*' methods look nicer.
76
- *
77
- * @param string $prefix
78
- */
79
- protected function registerWpActions( $prefix = '' )
80
- {
81
- parent::registerWpActions( 'wp_ajax_ab_' );
82
- }
83
-
84
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/payment/templates/_alert.php DELETED
@@ -1,4 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="alert alert-warning" >
3
- <?php _e('This function is disabled in the lite version of Bookly. If you find the plugin useful for your business please consider buying a licence for the full version. It costs just $46 and for this money you will get many useful functions, lifetime free update and excellent support! More information can be found here', 'bookly'); ?>: <a href="http://booking-wp-plugin.com" target="_blank">http://booking-wp-plugin.com</a>
4
- </div>
 
 
 
 
backend/modules/payment/templates/index.php DELETED
@@ -1,148 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- /**
3
- * @var array $customers
4
- * @var array $types
5
- * @var array $providers
6
- * @var array $services
7
- */
8
- ?>
9
-
10
- <div class="panel panel-default">
11
- <div class="panel-heading">
12
- <h3 class="panel-title"><?php _e( 'Payments', 'bookly' ) ?></h3>
13
- </div>
14
- <div class="panel-body">
15
- <div class=ab-nav-payment>
16
- <div class=row-fluid>
17
- <div id=reportrange class="ab-reportrange ab-inline-block">
18
- <i class="glyphicon glyphicon-calendar"></i>
19
- <span data-date="<?php echo date( 'Y-m-d', strtotime( '-30 day' ) ) ?> - <?php echo date( 'Y-m-d' ) ?>"><?php echo date_i18n( get_option( 'date_format' ), strtotime( '-30 day' ) ) ?> - <?php echo date_i18n( get_option( 'date_format' ) ) ?></span> <b style="margin-top: 8px;" class=caret></b>
20
- </div>
21
- <div class=ab-inline-block>
22
- <select id=ab-type-filter class=selectpicker>
23
- <option value="-1"><?php _e( 'All payment types', 'bookly' ) ?></option>
24
- <?php foreach ( $types as $type ): ?>
25
- <option value="<?php echo esc_attr( $type ) ?>">
26
- <?php
27
- switch( $type ) {
28
- case 'paypal':
29
- echo 'PayPal'; break;
30
- case 'authorizeNet':
31
- echo 'authorizeNet'; break;
32
- case 'stripe':
33
- echo 'Stripe'; break;
34
- default:
35
- _e( 'Local', 'bookly' ); break;
36
- }
37
- ?>
38
- </option>
39
- <?php endforeach ?>
40
- </select>
41
- <select id=ab-customer-filter class=selectpicker>
42
- <option value="-1"><?php _e( 'All customers', 'bookly' ) ?></option>
43
- <?php foreach ( $customers as $customer ): ?>
44
- <option><?php echo esc_html( $customer ) ?></option>
45
- <?php endforeach ?>
46
- </select>
47
- <select id=ab-provider-filter class=selectpicker>
48
- <option value="-1"><?php _e( 'All providers', 'bookly' ) ?></option>
49
- <?php foreach ( $providers as $provider ): ?>
50
- <option><?php echo esc_html( $provider ) ?></option>
51
- <?php endforeach ?>
52
- </select>
53
- <select id=ab-service-filter class=selectpicker>
54
- <option value="-1"><?php _e( 'All services', 'bookly' ) ?></option>
55
- <?php foreach ( $services as $service ): ?>
56
- <option><?php echo esc_html( $service ) ?></option>
57
- <?php endforeach ?>
58
- </select>
59
- <a id=ab-filter-submit href="#" class="btn btn-primary"><?php _e( 'Filter', 'bookly' ) ?></a>
60
- </div>
61
- </div>
62
- </div>
63
- <div id=ab-alert-div class=alert style="display: none"></div>
64
- <?php include '_alert.php' ?>
65
- <div style="display: none" class="loading-indicator">
66
- <span class="ab-loader"></span>
67
- </div>
68
- </div>
69
- </div>
70
- <div class="modal fade" id="lite_notice" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
71
- <div class="modal-dialog">
72
- <div class="modal-content">
73
- <div class="modal-header">
74
- <h4 class="modal-title"><?php _e('Notice', 'bookly') ?></h4>
75
- </div>
76
- <div class="modal-body">
77
- <?php _e('This function is disabled in the lite version of Bookly. If you find the plugin useful for your business please consider buying a licence for the full version. It costs just $46 and for this money you will get many useful functions, lifetime free update and excellent support! More information can be found here', 'bookly'); ?>: <a href="http://booking-wp-plugin.com" target="_blank">http://booking-wp-plugin.com</a>
78
- </div>
79
- <div class="modal-footer">
80
- <button type="button" class="btn btn-default" data-dismiss="modal"><?php _e('Close', 'bookly') ?></button>
81
- </div>
82
- </div><!-- /.modal-content -->
83
- </div><!-- /.modal-dialog -->
84
- </div><!-- /.modal -->
85
-
86
- <script type="text/javascript">
87
- jQuery(function($) {
88
- var data = {},
89
- $report_range = $('#reportrange span'),
90
- picker_ranges = {};
91
-
92
- picker_ranges[BooklyL10n.today] = [moment(), moment()];
93
- picker_ranges[BooklyL10n.yesterday] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')];
94
- picker_ranges[BooklyL10n.last_7] = [moment().subtract(7, 'days'), moment()];
95
- picker_ranges[BooklyL10n.last_30] = [moment().subtract(30, 'days'), moment()];
96
- picker_ranges[BooklyL10n.this_month] = [moment().startOf('month'), moment().endOf('month')];
97
- picker_ranges[BooklyL10n.last_month] = [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')];
98
-
99
- $('.selectpicker').selectpicker({style: 'btn-info', size: 5});
100
-
101
- function ajaxData(object) {
102
- data['customer'] = $('#ab-customer-filter').val();
103
- data['provider'] = $('#ab-provider-filter').val();
104
- data['service'] = $('#ab-service-filter').val();
105
- data['range'] = $report_range.data('date'); //text();
106
- data['type'] = $('#ab-type-filter').val();
107
- data['key'] = $('#search_customers').val();
108
-
109
-
110
- return data;
111
- }
112
-
113
- // sort order
114
- $('#ab_payments_list th a').on('click', function() {
115
- var data = { action:'ab_sort_payments', data: ajaxData(this) };
116
- $('.loading-indicator').show();
117
- $('#ab_payments_list tbody').load(ajaxurl, data, function() {$('.loading-indicator').hide();});
118
- });
119
-
120
- $('#reportrange').daterangepicker(
121
- {
122
- startDate: moment().subtract(30, 'days'), // by default selected is "Last 30 days"
123
- ranges: picker_ranges,
124
- locale: {
125
- applyLabel: BooklyL10n.apply,
126
- cancelLabel: BooklyL10n.cancel,
127
- fromLabel: BooklyL10n.from,
128
- toLabel: BooklyL10n.to,
129
- customRangeLabel: BooklyL10n.custom_range,
130
- daysOfWeek: BooklyL10n.days,
131
- monthNames: BooklyL10n.months,
132
- firstDay: parseInt(BooklyL10n.startOfWeek),
133
- format: BooklyL10n.mjsDateFormat
134
- }
135
- },
136
- function(start, end) {
137
- var format = 'YYYY-MM-DD';
138
- $report_range
139
- .data('date', start.format(format) + ' - ' + end.format(format))
140
- .html(start.format(BooklyL10n.mjsDateFormat) + ' - ' + end.format(BooklyL10n.mjsDateFormat));
141
- }
142
- );
143
-
144
- $('#ab-filter-submit').on('click', function() {
145
- $('#lite_notice').modal('show');
146
- });
147
- });
148
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/payments/Components.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Payments;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Components
8
+ * @package BooklyLite\Backend\Modules\Payments
9
+ */
10
+ class Components extends Lib\Base\Components
11
+ {
12
+ /**
13
+ * Render payment details dialog.
14
+ * @throws \Exception
15
+ */
16
+ public function renderPaymentDetailsDialog()
17
+ {
18
+ $this->enqueueStyles( array(
19
+ 'frontend' => array( 'css/ladda.min.css', ),
20
+ ) );
21
+
22
+ $this->enqueueScripts( array(
23
+ 'backend' => array( 'js/angular.min.js' => array( 'jquery' ), ),
24
+ 'frontend' => array(
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' );
32
+ }
33
+
34
+ }
backend/modules/payments/Controller.php ADDED
@@ -0,0 +1,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
+ /**
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
+ }
backend/modules/payments/resources/js/ng-payment_details_dialog.js ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ;(function() {
2
+
3
+ angular.module('paymentDetailsDialog', []).directive('paymentDetailsDialog', function() {
4
+ return {
5
+ restrict: 'A',
6
+ replace: true,
7
+ scope: {
8
+ callback: '&paymentDetailsDialog'
9
+ },
10
+ templateUrl: 'bookly-payment-details-dialog.tpl',
11
+ // The linking function will add behavior to the template.
12
+ link: function (scope, element, attrs) {
13
+ var $body = element.find('.modal-body'),
14
+ spinner = $body.html();
15
+
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) {
27
+ $body.html(response.data.html);
28
+ $body.find('#bookly-complete-payment').on('click',function () {
29
+ var ladda = Ladda.create(this);
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) {
37
+ if (response.success) {
38
+ element.trigger('show.bs.modal', [payment_id]);
39
+ if (scope.callback) {
40
+ scope.$apply(function ($scope) {
41
+ $scope.callback({
42
+ payment_id : payment_id,
43
+ payment_title : response.data.payment_title
44
+ });
45
+ });
46
+ }
47
+ // Reload DataTable.
48
+ var $table = jQuery(e.relatedTarget).closest('table.dataTable');
49
+ if ($table.length) {
50
+ $table.DataTable().ajax.reload();
51
+ }
52
+ }
53
+ }
54
+ });
55
+ });
56
+ }
57
+ }
58
+ });
59
+ })
60
+ .on('hidden.bs.modal', function () {
61
+ $body.html(spinner);
62
+ if ((jQuery("#bookly-appointment-dialog").data('bs.modal') || {isShown: false}).isShown) {
63
+ jQuery('body').addClass('modal-open');
64
+ }
65
+ });
66
+ }
67
+ }
68
+ });
69
+ })();
backend/modules/payments/resources/js/payments.js ADDED
@@ -0,0 +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
+ })();
backend/modules/payments/templates/_payment_details_dialog.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>
backend/modules/payments/templates/details.php ADDED
@@ -0,0 +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 ?>
backend/modules/payments/templates/index.php ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>
backend/modules/service/AB_ServiceController.php DELETED
@@ -1,282 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_ServiceController
5
- */
6
- class AB_ServiceController extends AB_Controller {
7
-
8
- const page_slug = 'ab-services';
9
- /**
10
- * Index page.
11
- */
12
- public function index()
13
- {
14
- $this->enqueueStyles( array(
15
- 'wp' => array(
16
- 'wp-color-picker',
17
- ),
18
- 'frontend' => array(
19
- 'css/ladda.min.css',
20
- ),
21
- 'backend' => array(
22
- 'css/bookly.main-backend.css',
23
- 'bootstrap/css/bootstrap.min.css',
24
- ),
25
- 'module' => array(
26
- 'css/service.css',
27
- )
28
- ) );
29
-
30
- $this->enqueueScripts( array(
31
- 'wp' => array(
32
- 'wp-color-picker',
33
- ),
34
- 'backend' => array(
35
- 'js/ab_popup.js' => array( 'jquery' ),
36
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
37
- ),
38
- 'module' => array(
39
- 'js/service.js' => array( 'jquery-ui-sortable', 'jquery' ),
40
- ),
41
- 'frontend' => array(
42
- 'js/spin.min.js' => array( 'jquery' ),
43
- 'js/ladda.min.js' => array( 'ab-spin.min.js', 'jquery' ),
44
- )
45
- ) );
46
-
47
- wp_localize_script( 'ab-service.js', 'BooklyL10n', array(
48
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
49
- 'no_staff_selected' => __( 'No staff selected', 'bookly' ),
50
- 'please_select_at_least_one_service' => __( 'Please select at least one service.', 'bookly' ),
51
- ) );
52
-
53
- $this->staff_collection = $this->getStaffCollection();
54
- $this->category_collection = $this->getCategoryCollection();
55
- $this->service_collection = $this->getServiceCollection();
56
- $this->render( 'index' );
57
- }
58
-
59
- /**
60
- *
61
- */
62
- public function executeCategoryServices()
63
- {
64
- $this->setDataForServiceList();
65
- $this->render( '_list' );
66
- exit;
67
- }
68
-
69
- /**
70
- *
71
- */
72
- public function executeCategoryForm()
73
- {
74
- $this->form = new AB_CategoryForm();
75
-
76
- if ( ! empty ( $_POST ) ) {
77
- $this->form->bind( $this->getPostParameters() );
78
- if ( $category = $this->form->save() ) {
79
- echo "<li class='ab-category-item' data-id='{$category->id}'>
80
- <span class='ab-handle'><i class='ab-inner-handle glyphicon glyphicon-move'></i></span>
81
- <span class='left displayed-value'>{$category->name}</span>
82
- <a href='#' class='left ab-hidden ab-edit'></a>
83
- <input class=value type=text name=name value='{$category->name}' style='display: none' />
84
- <a href='#' class='left ab-hidden ab-delete'></a></li>";
85
- // Register string for translate in WPML.
86
- do_action( 'wpml_register_single_string', 'bookly', 'category_' . $category->id, $category->name );
87
- exit;
88
- }
89
- }
90
- exit;
91
- }
92
-
93
- /**
94
- * Update category.
95
- */
96
- public function executeUpdateCategory()
97
- {
98
- $form = new AB_CategoryForm();
99
- $form->bind( $this->getPostParameters() );
100
- $category = $form->save();
101
- // Register string for translate in WPML.
102
- do_action( 'wpml_register_single_string', 'bookly', 'category_' . $category->id, $category->name );
103
- }
104
-
105
- /**
106
- * Update category position.
107
- */
108
- public function executeUpdateCategoryPosition()
109
- {
110
- $category_sorts = $this->getParameter( 'position' );
111
- foreach ( $category_sorts as $position => $category_id ) {
112
- $category_sort = new AB_Category();
113
- $category_sort->load( $category_id );
114
- $category_sort->set( 'position', $position );
115
- $category_sort->save();
116
- }
117
- }
118
-
119
- /**
120
- * Update services position.
121
- */
122
- public function executeUpdateServicesPosition()
123
- {
124
- $services_sorts = $this->getParameter( 'position' );
125
- foreach ( $services_sorts as $position => $service_ids ) {
126
- $services_sort = new AB_Service();
127
- $services_sort->load( $service_ids );
128
- $services_sort->set( 'position', $position );
129
- $services_sort->save();
130
- }
131
- }
132
-
133
- /**
134
- * Delete category.
135
- */
136
- public function executeDeleteCategory()
137
- {
138
- $category = new AB_Category();
139
- $category->set( 'id', $this->getParameter( 'id', 0 ) );
140
- $category->delete();
141
- }
142
-
143
- public function executeAddService()
144
- {
145
- $form = new AB_ServiceForm();
146
- $form->bind( $this->getPostParameters() );
147
- $form->getObject()->set( 'duration', get_option( 'ab_settings_time_slot_length' ) * 60 );
148
- $service = $form->save();
149
- $this->setDataForServiceList( $service->get( 'category_id' ) );
150
- wp_send_json_success( array( 'html' => $this->render( '_list', array(), false ), 'service_id' => $service->get( 'id' ) ) );
151
- }
152
-
153
- public function executeRemoveServices()
154
- {
155
- $service_ids = $this->getParameter( 'service_ids', array() );
156
- if ( is_array( $service_ids ) && ! empty ( $service_ids ) ) {
157
- AB_Service::query( 's' )->delete()->whereIn( 's.id', $service_ids )->execute();
158
- }
159
- }
160
-
161
- public function executeAssignStaff()
162
- {
163
- $service_id = $this->getParameter( 'service_id', 0 );
164
- $staff_ids = $this->getParameter( 'staff_ids', array() );
165
-
166
- if ( $service_id ) {
167
- AB_StaffService::query()->delete()->where( 'service_id', $service_id )->execute();
168
- $service = new AB_Service();
169
- if ( ! empty( $staff_ids ) && $service->load( $service_id ) ) {
170
- foreach ( $staff_ids as $staff_id ) {
171
- $staff_service = new AB_StaffService();
172
- $staff_service->set( 'staff_id', $staff_id );
173
- $staff_service->set( 'service_id', $service_id );
174
- $staff_service->set( 'price', $service->get( 'price' ) );
175
- $staff_service->save();
176
- }
177
- }
178
- wp_send_json_success( count( $staff_ids ) );
179
- }
180
- wp_send_json_error();
181
- }
182
-
183
- public function executeUpdateServiceValue()
184
- {
185
- /** @var $wpdb wpdb */
186
- global $wpdb;
187
-
188
- $form = new AB_ServiceForm();
189
- $form->bind( $this->getPostParameters() );
190
- $service = $form->save();
191
-
192
- if ( $this->getParameter( 'update_staff', false ) ) {
193
- if ( $this->getParameter( 'price' ) ) {
194
- $wpdb->update( AB_StaffService::getTableName(), array( 'price' => $this->getParameter( 'price' ) ), array( 'service_id' => $this->getParameter( 'id' ) ) );
195
- }
196
- if ( $this->getParameter( 'capacity' ) ) {
197
- $wpdb->update( AB_StaffService::getTableName(), array( 'capacity' => $this->getParameter( 'capacity' ) ), array( 'service_id' => $this->getParameter( 'id' ) ) );
198
- }
199
- }
200
- // Register string for translate in WPML.
201
- if ( $this->hasParameter( 'title' ) ) {
202
- do_action( 'wpml_register_single_string', 'bookly', 'service_' . $service->id, $service->title );
203
- }
204
-
205
- $staff_ids = $this->getParameter( 'staff_ids', array() );
206
- if ( $service->id ) {
207
- AB_StaffService::query()->delete()->where( 'service_id', $service->id )->execute();
208
- if ( ! empty( $staff_ids ) ) {
209
- foreach ( $staff_ids as $staff_id ) {
210
- $staff_service = new AB_StaffService();
211
- $staff_service->set( 'staff_id', $staff_id );
212
- $staff_service->set( 'service_id', $service->id );
213
- $staff_service->set( 'price', $service->get( 'price' ) );
214
- $staff_service->save();
215
- }
216
- }
217
- }
218
-
219
- wp_send_json_success( array( 'title' => $service->title, 'price' => AB_Utils::formatPrice( $service->price ), 'color' => $service->color, 'nice_duration' => AB_DateTimeUtils::secondsToInterval( $service->duration ) ) );
220
- }
221
-
222
- /**
223
- * @param int $category_id
224
- */
225
- private function setDataForServiceList( $category_id = 0 )
226
- {
227
- if ( ! $category_id ) {
228
- $category_id = $this->getParameter( 'category_id', 0 );
229
- }
230
-
231
- $this->service_collection = $this->getServiceCollection( $category_id );
232
- $this->staff_collection = $this->getStaffCollection();
233
- $this->category_collection = $this->getCategoryCollection();
234
- }
235
-
236
- /**
237
- * @return mixed
238
- */
239
- private function getCategoryCollection()
240
- {
241
- return AB_Category::query()->sortBy( 'position' )->fetchArray();
242
- }
243
-
244
- /**
245
- * @return mixed
246
- */
247
- private function getStaffCollection() {
248
-
249
- return AB_Staff::query()->fetchArray();
250
- }
251
-
252
- /**
253
- * @param int $id
254
- * @return mixed
255
- */
256
- private function getServiceCollection( $id = 0 )
257
- {
258
- $services = AB_Service::query( 's' )
259
- ->select( 's.*, COUNT(staff.id) AS total_staff, GROUP_CONCAT(DISTINCT staff.id) AS staff_ids' )
260
- ->leftJoin( 'AB_StaffService', 'ss', 'ss.service_id = s.id' )
261
- ->leftJoin( 'AB_Staff', 'staff', 'staff.id = ss.staff_id' )
262
- ->whereRaw( 's.category_id = %d OR !%d', array( $id, $id ) )
263
- ->groupBy( 's.id' )
264
- ->sortBy( 'ISNULL(s.position), s.position' );
265
-
266
- return $services->fetchArray();
267
- }
268
-
269
- // Protected methods.
270
-
271
- /**
272
- * Override parent method to add 'wp_ajax_ab_' prefix
273
- * so current 'execute*' methods look nicer.
274
- *
275
- * @param string $prefix
276
- */
277
- protected function registerWpActions( $prefix = '' )
278
- {
279
- parent::registerWpActions( 'wp_ajax_ab_' );
280
- }
281
-
282
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/service/forms/AB_CategoryForm.php DELETED
@@ -1,25 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_CategoryForm
5
- */
6
- class AB_CategoryForm extends AB_Form {
7
-
8
- /**
9
- * Constructor.
10
- */
11
- public function __construct()
12
- {
13
- parent::$entity_class = 'AB_Category';
14
- parent::__construct();
15
- }
16
-
17
- /**
18
- * Configure the form.
19
- */
20
- public function configure()
21
- {
22
- $this->setFields( array( 'name' ) );
23
- }
24
-
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/service/forms/AB_ServiceForm.php DELETED
@@ -1,47 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_ServiceForm
5
- */
6
- class AB_ServiceForm extends AB_Form
7
- {
8
- /**
9
- * Constructor.
10
- */
11
- public function __construct()
12
- {
13
- parent::$entity_class = 'AB_Service';
14
- parent::__construct();
15
- }
16
-
17
- public function configure()
18
- {
19
- $this->setFields( array( 'id', 'title', 'duration', 'price', 'category_id', 'color', 'capacity', 'padding_left', 'padding_right' ) );
20
- }
21
-
22
- /**
23
- * Bind values to form.
24
- *
25
- * @param array $post
26
- * @param array $files
27
- */
28
- public function bind( array $post, array $files = array() )
29
- {
30
- if ( array_key_exists( 'category_id', $post ) && !$post['category_id'] ) {
31
- $post['category_id'] = null;
32
- }
33
- parent::bind( $post, $files );
34
- }
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
- $this->data['capacity'] = 1;
43
-
44
- return parent::save();
45
- }
46
-
47
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/service/resources/css/service.css DELETED
@@ -1,144 +0,0 @@
1
- #ab_services_wrapper .table-responsive { overflow: visible!important; }
2
- @media screen and (max-width: 767px) {
3
- #ab_services_wrapper .table-responsive {
4
- overflow-y: visible!important;
5
- overflow-x: auto!important;
6
- }
7
- .ab-col-responsive {
8
- margin-bottom: 15px;
9
- }
10
- #services_list .panel.panel-default {
11
- margin-right: 0;
12
- }
13
- }
14
- .ab-category-item {
15
- padding: 10px;
16
- padding-left: 50px;
17
- vertical-align: middle;
18
- border-radius: 5px;
19
- cursor: pointer;
20
- }
21
- .ab-main-category-item {
22
- padding-left: 40px;
23
- font-size: 17px;
24
- background-image: url("../../../../resources/images/box-big.png");
25
- background-position: 5px 50%;
26
- background-repeat: no-repeat;
27
- }
28
- .ab-active {
29
- background-color: #ccc;
30
- }
31
-
32
- .wp-picker-holder { margin-top: 10px; left: -268px; z-index: 2; position: absolute; }
33
-
34
- #services_list .service-color-wrapper .wp-picker-open + .wp-picker-input-wrap {
35
- display: block;
36
- margin-left: 50px;
37
- margin-top: 20px;
38
- }
39
- .service-color-wrapper {
40
- height: 24px;
41
- width: 27px;
42
- }
43
-
44
- .wp-picker-container, .wp-picker-container:active {
45
- display: inline-block;
46
- outline: 0 none;
47
- position: absolute;
48
- }
49
-
50
- /** Services list **/
51
- #ab_services_wrapper .ab-right-content .ab-category-title { margin: 10px 0 15px 0; font-weight: normal; }
52
- #ab_services_wrapper .list-wrapper { max-width: 575px; }
53
- #ab_services_wrapper .list-wrapper .list-actions { overflow: hidden; margin-top: 10px; }
54
- #ab_services_wrapper .list-wrapper .list-actions .add-service { float: left }
55
- #ab_services_wrapper .list-wrapper .list-actions .delete { float: right }
56
-
57
- #services_list td.last,
58
- #services_list th.last { width: 16px; }
59
- #services_list td .btn-group { min-width: 85px; }
60
- #services_list td,
61
- #services_list th { vertical-align: middle; }
62
- #services_list .service-color-cell { width: 28px; }
63
- #services_list .service-color-wrapper .wp-color-result { padding-left: 25px; margin: 0; top: auto; }
64
- #services_list .service-color-wrapper .wp-color-result.wp-picker-open { top: auto }
65
- #services_list .service-color-wrapper .wp-color-result:after { top: auto; content: none; }
66
- #services_list .service-color-wrapper .wp-color-result.wp-picker-open:after { content: none }
67
- #services_list .service-color-wrapper .wp-picker-open + .wp-picker-input-wrap { display: block; visibility: hidden; margin-top: -25px; margin-left: 34px; }
68
- #services_list .service-color-wrapper .button { margin-left: 0 }
69
- #services_list .panel.panel-default { margin-right: 0; }
70
- #services_list .panel.panel-default.service .panel-heading { padding: 10px; overflow: hidden; }
71
- #services_list .panel.panel-default.service .panel-collapse { width: 100%!important; }
72
- #services_list .panel.panel-default.service .panel-heading .badge { margin: 0 5px; margin-top: -3px; }
73
- #services_list .ab-padding-before-after select { width: 46%!important; }
74
- #services_list a[data-toggle="collapse"] {
75
- outline: none;
76
- box-shadow: none;
77
- padding-right: 25px;
78
- background: url("../../../../resources/images/notifications-arrow-up.png") 100% 50% no-repeat;
79
- background-size: 17px 17px;
80
- }
81
- #services_list a[data-toggle="collapse"].collapsed {
82
- background: url("../../../../resources/images/notifications-arrow-down.png") 100% 50% no-repeat;
83
- background-size: 17px 17px;
84
- }
85
-
86
- .table tbody > tr td:first-child { vertical-align: middle; }
87
-
88
- #ab-services-list,
89
- #ab-services-list li { margin: 0; }
90
- #ab-services-list .table { margin: 0; }
91
- #ab-services-list .table .ab-handle { cursor: move; }
92
- #ab-services-list .ab-popover { margin: 29px 0 0 -20px; }
93
- #ab-services-list .service-color-wrapper { margin: 5px 0 0 -20px; }
94
-
95
- #new_category_popup .ab-popup { width: 187px }
96
- .staff-popup-wrapper .staff-row { white-space: nowrap; margin: 0; }
97
-
98
- #ab-category-item-list { padding: 0; margin: 20px 0; }
99
- #ab-category-item-list .ab-category-item {
100
- width: auto;
101
- height: auto;
102
- padding: 5px;
103
- margin-top: 5px;
104
- position: relative;
105
- white-space: nowrap;
106
- overflow: hidden;
107
- padding-left: 45px;
108
- background-image: url("../../../../resources/images/box-small.png");
109
- background-position: 22px 50%;
110
- background-repeat: no-repeat;
111
- }
112
- #ab-category-item-list .ab-category-item .ab-handle {
113
- display: block;
114
- width: 20px;
115
- position: absolute;
116
- top: 0;
117
- left: 0;
118
- bottom: 0;
119
- z-index: 999;
120
- text-align: center;
121
- padding: 5px 0;
122
- cursor: move;
123
- }
124
- #ab-category-item-list .ab-category-item > span { padding-right: 5px; }
125
- #ab-category-item-list .ab-category-item:hover span { text-decoration: underline; }
126
- #ab-category-item-list .ab-category-item .ab-edit,
127
- #ab-category-item-list .ab-category-item .ab-delete {
128
- display: inline-block;
129
- width: 21px;
130
- height: 16px;
131
- background: url("../../../../resources/images/edit.png") 5px 0 no-repeat;
132
- padding: 0 5px;
133
- visibility: hidden;
134
- }
135
- #ab-category-item-list .ab-category-item:hover .ab-edit,
136
- #ab-category-item-list .ab-category-item:hover .ab-delete { visibility: visible; }
137
- #ab-category-item-list .ab-category-item:hover .ab-delete { background: url("../../../../resources/images/delete.png") 5px 0 no-repeat; padding-left: 5px; }
138
- #ab-category-item-list .ab-category-item .ab-edit { padding-right: 0; }
139
-
140
- @media only screen and (max-width: 640px) {
141
- .wp-picker-container,
142
- .wp-picker-container:active { position: static; }
143
- }
144
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/service/resources/js/service.js DELETED
@@ -1,344 +0,0 @@
1
- jQuery(function($) {
2
- var $no_result = $('#ab_services_wrapper .no-result');
3
- // Remember user choice in the modal dialog.
4
- var update_staff_choice = null;
5
-
6
- // On new category form submit.
7
- $('#new-category-form').on('submit', function(event) {
8
- var data = $(this).serialize();
9
- $.post(ajaxurl, data, function(response) {
10
- $('.ab-category-item-list').append(response);
11
- $('#new_category_popup').ab_popup('close');
12
- // add created category to services
13
- $.each($('#services_list').find('select[name="category_id"]'), function(key, value) {
14
- var $new_category = $('.ab-category-item:last');
15
- $(value).append('<option value="' + $new_category.data('id') + '">'
16
- + $new_category.find('input').val() + ' </option>');
17
- });
18
- });
19
- return false;
20
- });
21
-
22
- // Preventing multiple creation of new category by pressing Enter-key
23
- $('input[value="ab_category_form"]').parent().find('input:first').one('keypress', function(e) {
24
- var code = (e.keyCode ? e.keyCode : e.which);
25
- if (code == 13) {
26
- $(this).trigger(e).blur();
27
- }
28
- });
29
-
30
- // Categories list delegated events.
31
- $('#ab-categories-list')
32
-
33
- // On category item click.
34
- .on('click', '.ab-category-item', function() {
35
- var $clicked = $(this);
36
- $.get(ajaxurl, {action:'ab_category_services', category_id: $clicked.data('id')}, function(response) {
37
- $('.ab-category-item').not($clicked).removeClass('ab-active');
38
- $('.ab-category-title').text($clicked.text());
39
- $clicked.addClass('ab-active');
40
- refreshList(response,0);
41
- });
42
- })
43
-
44
- // On edit category click.
45
- .on('click', '.ab-category-item .ab-edit', function(e) {
46
- // Keep category item click from being executed.
47
- e.stopPropagation();
48
- // Prevent navigating to '#'.
49
- e.preventDefault();
50
- // Hide edit button.
51
- $(this).hide()
52
- // Hide displayed category name and delete button.
53
- .siblings('.displayed-value, .ab-delete').hide().end()
54
- // Show input field.
55
- .nextAll('.value').show().focus();
56
- })
57
-
58
- // On blur of category edit input.
59
- .on('blur', '.ab-category-item input.value', function() {
60
- var $this = $(this),
61
- $item = $this.closest('.ab-category-item'),
62
- field = $this.attr('name'),
63
- value = $this.attr('value'),
64
- id = $item.data('id');
65
- if (value) {
66
- var data = { action: 'ab_update_category', id: id };
67
- data[field] = value;
68
- $.post(ajaxurl, data, function(response) {
69
- // Hide input field.
70
- $this.hide()
71
- // Show modified category name.
72
- .prevAll('.displayed-value').text(value).show().end()
73
- // Show edit and delete buttons.
74
- .siblings('.ab-edit, .ab-delete').show();
75
- // update edited category's name for services
76
- $.each($('#services_list').find('select[name="category_id"]'), function(k, v) {
77
- $(v).find('option:selected[value="' + id + '"]').text(value);
78
- });
79
- });
80
- }
81
- })
82
-
83
- // On delete category click.
84
- .on('click', '.ab-category-item .ab-delete', function(e) {
85
- // Keep category item click from being executed.
86
- e.stopPropagation();
87
- // Prevent navigating to '#'.
88
- e.preventDefault();
89
- // Ask user if he is sure.
90
- if (confirm(BooklyL10n.are_you_sure)) {
91
- var $item = $(this).closest('.ab-category-item');
92
- var data = { action: 'ab_delete_category', id: $item.data('id') };
93
- $.post(ajaxurl, data, function(response) {
94
- // Remove category item from Services
95
- $.each($('#services_list').find('select[name="category_id"]'), function(key, value) {
96
- $(value).find('option[value="' + $item.data('id') + '"]').remove();
97
- });
98
- // Remove category item from DOM.
99
- $item.remove();
100
- if ($item.is('.ab-active')) {
101
- location.reload(true);
102
- }
103
- });
104
- }
105
- });
106
-
107
- // Services list delegated events.
108
- $('#ab_services_wrapper')
109
- // On click on 'Add Service' button.
110
- .on('click', 'a.add-service', function(e) {
111
- e.preventDefault();
112
- var selected_category_id = $('#ab-categories-list .ab-active').data('id'),
113
- data = { action: 'ab_add_service' };
114
- if (selected_category_id) {
115
- data['category_id'] = selected_category_id;
116
- }
117
- $.post(ajaxurl, data, function(response) {
118
- refreshList(response.data.html,response.data.service_id);
119
- });
120
- })
121
- // On click on 'Delete' button.
122
- .on('click', 'a.delete', function(e){
123
- e.preventDefault();
124
-
125
- var for_delete = $('input:checkbox.service-checker:checked'),
126
- data = { action: 'ab_remove_services' },
127
- services = [],
128
- $panels = [];
129
-
130
- if (!for_delete.length) {
131
- alert(BooklyL10n.please_select_at_least_one_service);
132
- return false;
133
- }
134
-
135
- for_delete.each(function(){
136
- var panel = $(this).parents('.panel.service');
137
- $panels.push(panel);
138
- services.push(panel.data('service_id'));
139
- });
140
- data['service_ids[]'] = services;
141
- $.post(ajaxurl, data, function() {
142
- $.each($panels, function () {
143
- $(this).fadeOut(200, function () {
144
- $(this).remove();
145
- });
146
- });
147
- });
148
- })
149
-
150
- .on('change', 'input.all-staff, input.staff', function(){
151
- var $panel = $(this).parents('.panel.service');
152
- if ($(this).hasClass('all-staff')) {
153
- $panel.find('.staff').prop('checked', $(this).prop('checked'));
154
- } else {
155
- $panel.find('.all-staff').prop('checked', $panel.find('.staff:not(:checked)').length == 0);
156
- }
157
- updateStaffButton($panel);
158
- });
159
-
160
- // Modal window events.
161
- var $modal = $('#ab-staff-update');
162
- $modal
163
- .on('click', '.ab-yes', function() {
164
- $modal.modal('hide');
165
- if ( $('#ab-remember-my-choice').prop('checked') ) {
166
- update_staff_choice = true;
167
- }
168
- submitServiceFrom($modal.data('input'),true);
169
- })
170
- .on('click', '.ab-no', function() {
171
- if ( $('#ab-remember-my-choice').prop('checked') ) {
172
- update_staff_choice = false;
173
- }
174
- submitServiceFrom($modal.data('input'),false);
175
- });
176
-
177
- function refreshList(response,service_id) {
178
- var $list = $('#ab-services-list');
179
- $list.html(response);
180
- makeServicesSortable();
181
- doNotCloseDropDowns();
182
- initColorPicker($list.find('.service-color'));
183
- initPopovers();
184
- initServiceFormButtons();
185
-
186
- if (response.indexOf('panel') >= 0) {
187
- $no_result.hide();
188
- } else {
189
- $no_result.show();
190
- }
191
- $('#service_' + service_id).collapse('show');
192
- $('#service_' + service_id).find('input[name=title]').focus();
193
- }
194
-
195
- function initColorPicker($jquery_collection) {
196
- $jquery_collection.each(function(){
197
- $(this).data('last_color', $(this).val());
198
- });
199
- $jquery_collection.wpColorPicker();
200
- }
201
-
202
- function doNotCloseDropDowns() {
203
- $('#ab-services-list .dropdown-menu').on('click', function(e) {
204
- e.stopPropagation();
205
- });
206
- }
207
-
208
- function initPopovers() {
209
- // Popovers initialization.
210
- $('.ab-popover').popover({
211
- trigger : 'hover'
212
- });
213
- }
214
-
215
- function submitServiceFrom($form, update_staff) {
216
- $form.find('input[name=update_staff]').val( update_staff ? 1 : 0 );
217
- var ladda = Ladda.create( $form.find('button[type=submit]').get(0) );
218
- ladda.start();
219
- $.post(ajaxurl, $form.serialize(), function (response) {
220
- if (response.success) {
221
- var $panel = $form.parents('.panel.service'),
222
- $price = $form.find('[name=price]'),
223
- $capacity = $form.find('[name=capacity]');
224
- $panel.find('span.badge').css('background-color', response.data.color);
225
- $panel.find('.panel-title a').html(response.data.title);
226
- $panel.find('.panel-title .ab-right-nav small').html(response.data.nice_duration + '<div class="ab-inline-block" style="width: 75px;text-align: right">' + response.data.price + '</div>');
227
- $price.data('last_value', $price.val());
228
- $capacity.data('last_value', $capacity.val());
229
- $('.notice-dismiss').unbind('click.wp-dismiss-notice').on('click', function(){
230
- $(this).parents('.notice').fadeOut();
231
- });
232
- $('.notice-success').show();
233
- }
234
- }, 'json').always(function() {
235
- ladda.stop();
236
- } );
237
- }
238
-
239
- function updateStaffButton($panel) {
240
- var staff_checked = $panel.find('.staff:checked').length;
241
- if (staff_checked == 0) {
242
- $panel.find('.staff-count').text(BooklyL10n.no_staff_selected);
243
- } else if (staff_checked == 1) {
244
- $panel.find('.staff-count').text($panel.find('.staff:checked').data('staff_name'));
245
- } else {
246
- $panel.find('.staff-count').text(staff_checked + '/' + $panel.find('.staff').length);
247
- }
248
- }
249
-
250
- function initServiceFormButtons() {
251
- $('.ajax-service-send').on('click', function (e) {
252
- e.preventDefault();
253
- var $form = $(this).parents('form'),
254
- show_modal = false;
255
- if(update_staff_choice === null) {
256
- $('.ab-question', $form).each(function () {
257
- if ($(this).data('last_value') != $(this).val()) {
258
- show_modal = true;
259
- }
260
- });
261
- }
262
- if(show_modal){
263
- $modal.data('input', $form).modal('show');
264
- }else{
265
- submitServiceFrom($form, update_staff_choice);
266
- }
267
- });
268
- $('.js-reset').on('click', function (e) {
269
- $(this).parents('form').trigger('reset');
270
- var $color = $(this).parents('form').find('.service-color'),
271
- $panel = $(this).parents('.panel.service');
272
- $color.val($color.data('last_color')).trigger('change');
273
- updateStaffButton($panel);
274
- });
275
- $('.ab-question').each( function(){
276
- $(this).data('last_value', $(this).val());
277
- });
278
- }
279
-
280
-
281
- doNotCloseDropDowns();
282
- initColorPicker($('.service-color'));
283
- initPopovers();
284
- initServiceFormButtons();
285
-
286
- var $category = $('ul#ab-category-item-list');
287
- $category.sortable({
288
- axis : 'y',
289
- handle : '.ab-handle',
290
- update : function( event, ui ) {
291
- var data = [];
292
- $category.children('li').each(function() {
293
- var $this = $(this);
294
- var position = $this.data('id');
295
- data.push(position);
296
- });
297
- $.ajax({
298
- type : 'POST',
299
- url : ajaxurl,
300
- data : { action: 'ab_update_category_position', position: data }
301
- });
302
- }
303
- });
304
-
305
- function makeServicesSortable() {
306
- if ($('.ab-main-category-item').hasClass('ab-active')) {
307
- var $services = $('#services_list'),
308
- fixHelper = function(e, ui) {
309
- ui.children().each(function() {
310
- $(this).width($(this).width());
311
- });
312
- return ui;
313
- };
314
- $services.sortable({
315
- helper : fixHelper,
316
- axis : 'y',
317
- handle : '.ab-handle',
318
- update : function( event, ui ) {
319
- var data = [];
320
- $services.children('div').each(function() {
321
- data.push($(this).data('service_id'));
322
- });
323
- $.ajax({
324
- type : 'POST',
325
- url : ajaxurl,
326
- data : { action: 'ab_update_services_position', position: data }
327
- });
328
- }
329
- });
330
- } else {
331
- $('#services_list .ab-handle').hide();
332
- }
333
- }
334
- makeServicesSortable();
335
- $('.panel.service').each(function(){
336
- updateStaffButton($(this));
337
- });
338
- $('[name=capacity]').on('change',function(){
339
- if ($(this).val() > 1) {
340
- $('#lite_notice').modal('show');
341
- $(this).val(1) ;
342
- }
343
- });
344
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/service/templates/_list.php DELETED
@@ -1,177 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- $time_interval = get_option( 'ab_settings_time_slot_length' );
3
- ?>
4
- <?php AB_Utils::notice( __( 'Settings saved.', 'bookly' ), 'notice-success', false ) ?>
5
- <?php if ( ! empty( $service_collection ) ) : ?>
6
- <div class="panel-group" id="services_list" role="tablist" aria-multiselectable="true">
7
- <?php foreach ( $service_collection as $i => $service ) : ?>
8
- <?php $service_id = $service['id'];
9
- $assigned_staff_ids = $service['staff_ids'] ? explode( ',', $service['staff_ids'] ) : array();
10
- $all_staff_selected = count( $assigned_staff_ids ) == count( $staff_collection );
11
- ?>
12
- <div class="panel panel-default service" data-service_id="<?php echo $service_id ?>">
13
- <div class="panel-heading" role="tab" id="s_<?php echo esc_html( $service_id ) ?>">
14
- <h4 class="panel-title">
15
- <span class="ab-handle ab-move" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"><i class="ab-inner-handle glyphicon glyphicon-align-justify"></i></span>
16
- <span class="badge" style="background-color: <?php echo esc_attr( $service['color'] ) ?>">&nbsp;</span>
17
- <a role="button" class="collapsed" data-toggle="collapse" data-parent="#services_list" href="#service_<?php echo esc_html( $service_id ) ?>" aria-expanded="false" aria-controls="service_<?php echo esc_html( $service_id ) ?>">
18
- <?php echo esc_html( $service['title'] ) ?>
19
- </a>
20
- <div class="pull-right">
21
- <div class="ab-right-nav ab-inline-block">
22
- <small>
23
- <?php echo AB_DateTimeUtils::secondsToInterval( $service['duration'] ) ?>
24
- <div class="ab-inline-block" style="width: 75px;text-align: right">
25
- <?php echo AB_Utils::formatPrice( $service['price'] ) ?>
26
- </div>
27
- </small>
28
- </div>
29
-
30
- <input style="margin: 0 0 0 5px" type="checkbox" class="service-checker"/>
31
- </div>
32
- </h4>
33
- </div>
34
- <div id="service_<?php echo esc_html( $service_id ) ?>" class="panel-collapse collapse" role="tabpanel" aria-labelledby="s_<?php echo esc_html( $service_id ) ?>" style="height: 0px;">
35
- <div class="panel-body">
36
- <form method="post">
37
- <div class="form-group">
38
- <label for="title_<?php echo $service_id ?>"><?php _e( 'Title', 'bookly' ) ?></label>
39
- <div class="row">
40
- <div class="col-sm-11 col-xs-10">
41
- <input id="title_<?php echo $service_id ?>" class="form-control" type="text" name="title" value="<?php echo esc_attr( $service['title'] ) ?>" />
42
- </div>
43
- <div class="col-sm-1 col-xs-2">
44
- <div class="service-color-wrapper">
45
- <input type="hidden" class="service-color" name="color" value="<?php echo esc_attr( $service['color'] ) ?>" data-last_color='' />
46
- </div>
47
- </div>
48
- </div>
49
- </div>
50
- <div class="form-group">
51
- <div class="row">
52
- <div class="col-sm-5 col-xs-10 ab-col-responsive">
53
- <label for="duration_<?php echo $service_id ?>"><?php _e( 'Duration', 'bookly' ) ?></label>
54
- <select id="duration_<?php echo $service_id ?>" class="form-control" name="duration">
55
- <?php for ( $j = $time_interval; $j <= 720; $j += $time_interval ) : ?>
56
- <?php if ( $service['duration'] / 60 > $j - $time_interval && $service['duration'] / 60 < $j ): ?>
57
- <option value="<?php echo esc_attr( $service['duration'] ) ?>" selected>
58
- <?php echo AB_DateTimeUtils::secondsToInterval( $service['duration'] ) ?>
59
- </option>
60
- <?php endif ?>
61
- <option value="<?php echo $j * 60 ?>" <?php selected( $service['duration'], $j * 60 ) ?>>
62
- <?php echo AB_DateTimeUtils::secondsToInterval( $j * 60 ) ?>
63
- </option>
64
- <?php endfor ?>
65
- <option value="86400" <?php selected( $service['duration'], DAY_IN_SECONDS ) ?>>
66
- <?php _e( 'All day', 'bookly' ) ?>
67
- </option>
68
- </select>
69
- </div>
70
- <div class="col-sm-6 col-xs-10 ab-padding-before-after">
71
- <label for="padding_left_<?php echo $service_id ?>"><?php _e( 'Padding time (before and after)', 'bookly' ) ?></label>
72
- <div style="clear: both;"></div>
73
- <select id="padding_left_<?php echo $service_id ?>" class="form-control ab-auto-w pull-left" name="padding_left">
74
- <option value="0"><?php _e( 'OFF', 'bookly' ) ?></option>
75
- <?php for ( $j = $time_interval; $j <= 720; $j += $time_interval ) : ?>
76
- <?php if ( $service['duration'] / 60 > $j - $time_interval && $service['duration'] / 60 < $j ): ?>
77
- <option value="<?php echo esc_attr( $service['duration'] ) ?>" selected>
78
- <?php echo AB_DateTimeUtils::secondsToInterval( $service['duration'] ) ?>
79
- </option>
80
- <?php endif ?>
81
- <option value="<?php echo $j * 60 ?>" <?php selected( $service['padding_left'], $j * 60 ) ?>>
82
- <?php echo AB_DateTimeUtils::secondsToInterval( $j * 60 ) ?>
83
- </option>
84
- <?php endfor ?>
85
- </select>
86
- <select id="padding_right_<?php echo $service_id ?>" class="form-control ab-auto-w pull-right" name="padding_right">
87
- <option value="0"><?php _e( 'OFF', 'bookly' ) ?></option>
88
- <?php for ( $j = $time_interval; $j <= 720; $j += $time_interval ) : ?>
89
- <?php if ( $service['duration'] / 60 > $j - $time_interval && $service['duration'] / 60 < $j ): ?>
90
- <option value="<?php echo esc_attr( $service['duration'] ) ?>" selected>
91
- <?php echo AB_DateTimeUtils::secondsToInterval( $service['duration'] ) ?>
92
- </option>
93
- <?php endif ?>
94
- <option value="<?php echo $j * 60 ?>" <?php selected( $service['padding_right'], $j * 60 ) ?>>
95
- <?php echo AB_DateTimeUtils::secondsToInterval( $j * 60 ) ?>
96
- </option>
97
- <?php endfor ?>
98
- </select>
99
- </div>
100
- <div class="col-sm-1 col-xs-2">
101
- <?php AB_Utils::popover( __( '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' ) ) ?>
102
- </div>
103
- </div>
104
- </div>
105
- <div class="form-group">
106
- <div class="row">
107
- <div class="col-xs-5">
108
- <label for="price_<?php echo $service_id ?>"><?php _e( 'Price', 'bookly' ) ?></label>
109
- <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'] ) ?>"/>
110
- </div>
111
- <div class="col-sm-3 col-xs-5">
112
- <label for="capacity_<?php echo $service_id ?>"><?php _e( 'Capacity', 'bookly' ) ?></label>
113
- <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'] ) ?>"/>
114
- </div>
115
- <div class="col-xs-1">
116
- <?php AB_Utils::popover( __( 'The maximum number of customers allowed to book the service for the certain time period.', 'bookly' ) ) ?>
117
- </div>
118
- </div>
119
- </div>
120
- <div class="form-group">
121
- <div class="row">
122
- <div class="col-xs-5">
123
- <label for="category_<?php echo $service_id ?>"><?php _e( 'Category', 'bookly' ) ?></label>
124
- <select id="category_<?php echo $service_id ?>" class="form-control" name="category_id">
125
- <option value="0"></option>
126
- <?php foreach ( $category_collection as $category ) : ?>
127
- <option value="<?php echo $category['id'] ?>" <?php selected( $category['id'], $service['category_id'] ) ?>>
128
- <?php echo esc_html( $category['name'] ) ?>
129
- </option>
130
- <?php endforeach ?>
131
- </select>
132
- </div>
133
- <div class="col-xs-7">
134
- <label><?php _e( 'Providers', 'bookly' ) ?></label>
135
- <div style="clear: both"></div>
136
- <div class="btn-group">
137
- <button class="btn btn-info" data-toggle="dropdown"><i class="glyphicon glyphicon-user"></i> <span class=staff-count><?php echo $service['total_staff'] ?></span>
138
- </button>
139
- <button class="btn btn-info dropdown-toggle" data-toggle="dropdown">
140
- <span class="caret"></span>
141
- </button>
142
- <ul class="dropdown-menu staff-list">
143
- <li>
144
- <a href="javascript:void(0)">
145
- <input type="checkbox" id="service_<?php echo $service_id ?>_all_staff" class="all-staff" <?php checked( $all_staff_selected ) ?>"/>
146
- <label class="ab-inline" for="service_<?php echo $service_id ?>_all_staff"><?php _e( 'All staff', 'bookly' ) ?></label>
147
- </a>
148
- </li>
149
- <?php foreach ( $staff_collection as $i => $staff ) : ?>
150
- <li>
151
- <a href="javascript:void(0)" style="padding-left: 30px">
152
- <input type="checkbox" name="staff_ids[]" class="staff" value="<?php echo $staff['id'] ?>" <?php checked( in_array( $staff['id'], $assigned_staff_ids ) ) ?> data-staff_name="<?php echo esc_attr( $staff['full_name'] ) ?>" />
153
- <label class="ab-inline" for="service_<?php echo $service_id . '_staff_' . $i ?>">
154
- <?php echo esc_html( $staff['full_name'] ) ?>
155
- </label>
156
- </a>
157
- </li>
158
- <?php endforeach ?>
159
- </ul>
160
- </div>
161
- </div>
162
- </div>
163
- </div>
164
- <div class="form-group">
165
- <input type="hidden" name="action" value="ab_update_service_value">
166
- <input type="hidden" name="id" value="<?php echo esc_html( $service_id ) ?>">
167
- <input type="hidden" name="update_staff" value="0">
168
- <?php AB_Utils::submitButton( null, 'ajax-service-send' ) ?>
169
- <?php AB_Utils::resetButton( null, 'js-reset' ) ?>
170
- </div>
171
- </form>
172
- </div>
173
- </div>
174
- </div>
175
- <?php endforeach ?>
176
- </div>
177
- <?php endif ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/service/templates/index.php DELETED
@@ -1,105 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
-
3
- <div class="panel panel-default">
4
- <div class="panel-heading">
5
- <h3 class="panel-title"><?php _e( 'Services', 'bookly' ) ?></h3>
6
- </div>
7
- <div class="panel-body">
8
- <div class="ab-wrapper-container">
9
- <div class="row">
10
- <div class="ab-left-bar col-md-3 col-sm-3 col-xs-12 col-lg-3">
11
- <div id="ab-categories-list">
12
- <div class="ab-category-item ab-active ab-main-category-item" data-id=""><?php _e( 'All Services', 'bookly' ) ?></div>
13
- <ul id="ab-category-item-list" class="ab-category-item-list">
14
- <?php foreach ( $category_collection as $category ): ?>
15
- <li class="ab-category-item" data-id="<?php echo $category['id'] ?>">
16
- <span class="ab-handle" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>">
17
- <i class="ab-inner-handle glyphicon glyphicon-align-justify"></i>
18
- </span>
19
- <span class="left displayed-value"><?php echo esc_html( $category['name'] ) ?></span>
20
- <a href="#" class="left ab-hidden ab-edit"></a>
21
- <input class="form-control value ab-value" type="text" name="name" value="<?php echo esc_attr( $category['name'] ) ?>" style="display: none" />
22
- <a href="#" class="left ab-hidden ab-delete"></a>
23
- </li>
24
- <?php endforeach ?>
25
- </ul>
26
- </div>
27
- <input type="hidden" id="color" />
28
- <div id="new_category_popup" class="ab-popup-wrapper">
29
- <input class="btn btn-info ab-popup-trigger" type="submit" value="<?php _e( 'New Category', 'bookly' ) ?>" />
30
- <div class="ab-popup" style="display: none; margin-top: 10px;">
31
- <div class="ab-arrow"></div>
32
- <div class="ab-content">
33
- <form method="post" id="new-category-form">
34
- <table class="form-horizontal">
35
- <tr>
36
- <td>
37
- <input class="form-control ab-clear-text" style="width: 170px" type="text" name="name" />
38
- <input type="hidden" name="action" value="ab_category_form" />
39
- </td>
40
- </tr>
41
- <tr>
42
- <td>
43
- <?php AB_Utils::submitButton() ?>
44
- <a class="ab-popup-close" href="#"><?php _e( 'Cancel', 'bookly' ) ?></a>
45
- </td>
46
- </tr>
47
- </table>
48
- <a class="ab-popup-close ab-popup-close-icon" href="#"></a>
49
- </form>
50
- </div>
51
- </div>
52
- </div>
53
- </div>
54
- <div class="ab-right-content col-md-9 col-sm-9 col-xs-12 col-lg-9" id="ab_services_wrapper">
55
- <h2 class="ab-category-title"><?php _e( 'All Services', 'bookly' ) ?></h2>
56
- <div class="no-result"<?php if ( ! empty ( $service_collection ) ) : ?> style="display: none"<?php endif ?>><?php _e( 'No services found. Please add services.', 'bookly' ) ?></div>
57
- <div class="list-wrapper">
58
- <div id="ab-services-list">
59
- <?php include '_list.php' ?>
60
- </div>
61
- <div class="list-actions">
62
- <a class="add-service btn btn-info" href="#"><?php _e( 'Add Service', 'bookly' ) ?></a>
63
- <a class="delete btn btn-info" href="#"><?php _e( 'Delete', 'bookly' ) ?></a>
64
- </div>
65
- </div>
66
- </div>
67
- </div>
68
- <div id="ab-staff-update" class="modal fade">
69
- <div class="modal-dialog">
70
- <div class="modal-content">
71
- <div class="modal-header">
72
- <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
73
- <h4 class="modal-title"><?php _e( 'Update service setting', 'bookly' ) ?></h4>
74
- </div>
75
- <div class="modal-body" style="white-space: normal">
76
- <span class="help-block"><?php _e( 'You are about to change a service setting which is also configured separately for each staff member. Do you want to update it in staff settings too?', 'bookly' ) ?></span>
77
- <label>
78
- <input style="margin: 0" id="ab-remember-my-choice" type="checkbox" /> <?php _e( 'Remember my choice', 'bookly' ) ?>
79
- </label>
80
- </div>
81
- <div class="modal-footer">
82
- <button type="reset" class="btn btn-default ab-no" data-dismiss="modal" aria-hidden="true"><?php _e( 'No, update just here in services', 'bookly' ) ?></button>
83
- <button type="submit" class="btn btn-primary ab-yes"><?php _e( 'Yes', 'bookly' ) ?></button>
84
- </div>
85
- </div><!-- /.modal-content -->
86
- </div><!-- /.modal-dialog -->
87
- </div><!-- /.modal -->
88
- </div>
89
- </div>
90
- </div>
91
- <div class="modal fade" id="lite_notice" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
92
- <div class="modal-dialog">
93
- <div class="modal-content">
94
- <div class="modal-header">
95
- <h4 class="modal-title"><?php _e('Notice', 'bookly') ?></h4>
96
- </div>
97
- <div class="modal-body">
98
- <?php _e('This function is disabled in the lite version of Bookly. If you find the plugin useful for your business please consider buying a licence for the full version. It costs just $46 and for this money you will get many useful functions, lifetime free update and excellent support! More information can be found here', 'bookly'); ?>: <a href="http://booking-wp-plugin.com" target="_blank">http://booking-wp-plugin.com</a>
99
- </div>
100
- <div class="modal-footer">
101
- <button type="button" class="btn btn-default" data-dismiss="modal"><?php _e('Close', 'bookly') ?></button>
102
- </div>
103
- </div><!-- /.modal-content -->
104
- </div><!-- /.modal-dialog -->
105
- </div><!-- /.modal -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/services/Controller.php ADDED
@@ -0,0 +1,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 = '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
+ }
backend/modules/services/forms/Category.php ADDED
@@ -0,0 +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
+ }
backend/modules/services/forms/Service.php ADDED
@@ -0,0 +1,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
+ $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
+ }
backend/modules/services/resources/js/service.js ADDED
@@ -0,0 +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
+ });
backend/modules/services/templates/_category_item.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <li class="bookly-nav-item bookly-category-item" data-category-id="<?php echo $category['id'] ?>">
3
+ <div class="bookly-flexbox">
4
+ <div class="bookly-flex-cell bookly-vertical-middle" style="width: 1%">
5
+ <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>
6
+ </div>
7
+ <div class="bookly-flex-cell bookly-vertical-middle">
8
+ <span class="displayed-value"><?php echo esc_html( $category['name'] ) ?></span>
9
+ <input class="form-control input-lg" type="text" name="name" value="<?php echo esc_attr( $category['name'] ) ?>" style="display: none"/>
10
+ </div>
11
+ <div class="bookly-flex-cell bookly-vertical-middle" style="width: 1%">
12
+ <a href="#" class="glyphicon glyphicon-edit bookly-margin-horizontal-xs bookly-js-edit" title="<?php esc_attr_e( 'Edit', 'bookly' ) ?>"></a>
13
+ </div>
14
+ <div class="bookly-flex-cell bookly-vertical-middle" style="width: 1%">
15
+ <a href="#" class="glyphicon glyphicon-trash text-danger bookly-js-delete" title="<?php esc_attr_e( 'Delete', 'bookly' ) ?>"></a>
16
+ </div>
17
+ </div>
18
+ </li>
backend/modules/services/templates/_list.php ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>
311
+ <?php endif ?>
backend/modules/services/templates/index.php ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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( 'Services', 'bookly' ) ?>
7
+ </div>
8
+ </div>
9
+ <div class="row">
10
+ <div id="bookly-sidebar" class="col-sm-4">
11
+ <div id="bookly-categories-list" class="bookly-nav">
12
+ <div class="bookly-nav-item active bookly-category-item bookly-js-all-services">
13
+ <div class="bookly-padding-vertical-xs"><?php _e( 'All Services', 'bookly' ) ?></div>
14
+ </div>
15
+ <ul id="bookly-category-item-list">
16
+ <?php foreach ( $category_collection as $category ) include '_category_item.php'; ?>
17
+ </ul>
18
+ </div>
19
+
20
+ <div class="form-group">
21
+ <button id="bookly-new-category" type="button"
22
+ class="btn btn-xlg btn-block btn-success-outline">
23
+ <i class="dashicons dashicons-plus-alt"></i>
24
+ <?php _e( 'New Category', 'bookly' ) ?>
25
+ </button>
26
+ </div>
27
+
28
+ <form method="post" id="new-category-form" style="display: none">
29
+ <div class="form-group bookly-margin-bottom-md">
30
+ <div class="form-field form-required">
31
+ <label for="bookly-category-name"><?php _e( 'Name', 'bookly' ) ?></label>
32
+ <input class="form-control" id="bookly-category-name" type="text" name="name" />
33
+ <input type="hidden" name="action" value="ab_category_form" />
34
+ </div>
35
+ </div>
36
+
37
+ <hr />
38
+ <div class="text-right">
39
+ <button type="submit" class="btn btn-success">
40
+ <?php _e( 'Save', 'bookly' ) ?>
41
+ </button>
42
+ <button type="button" class="btn btn-default">
43
+ <?php _e( 'Cancel', 'bookly' ) ?>
44
+ </button>
45
+ </div>
46
+ </form>
47
+ </div>
48
+
49
+ <div id="bookly-services-wrapper" class="col-sm-8">
50
+ <div class="panel panel-default bookly-main">
51
+ <div class="panel-body">
52
+ <h4 class="bookly-block-head">
53
+ <span class="bookly-category-title"><?php _e( 'All Services', 'bookly' ) ?></span>
54
+ <button type="button" class="add-service ladda-button pull-right btn btn-lg btn-success" data-spinner-size="40" data-style="zoom-in">
55
+ <span class="ladda-label"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'Add Service', 'bookly' ) ?></span>
56
+ </button>
57
+ </h4>
58
+
59
+ <p class="bookly-margin-top-xlg no-result" <?php if ( ! empty ( $service_collection ) ) : ?>style="display: none;"<?php endif ?>>
60
+ <?php _e( 'No services found. Please add services.', 'bookly' ) ?>
61
+ </p>
62
+
63
+ <div class="bookly-margin-top-xlg" id="ab-services-list">
64
+ <?php include '_list.php' ?>
65
+ </div>
66
+ <div class="text-right">
67
+ <?php \BooklyLite\Lib\Utils\Common::deleteButton() ?>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </div>
74
+
75
+ <div id="ab-staff-update" class="modal fade" tabindex=-1 role="dialog">
76
+ <div class="modal-dialog">
77
+ <div class="modal-content">
78
+ <div class="modal-header">
79
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
80
+ <div class="modal-title h2"><?php _e( 'Update service setting', 'bookly' ) ?></div>
81
+ </div>
82
+ <div class="modal-body">
83
+ <p><?php _e( 'You are about to change a service setting which is also configured separately for each staff member. Do you want to update it in staff settings too?', 'bookly' ) ?></p>
84
+ <div class="checkbox">
85
+ <label>
86
+ <input id="ab-remember-my-choice" type="checkbox">
87
+ <?php _e( 'Remember my choice', 'bookly' ) ?>
88
+ </label>
89
+ </div>
90
+ </div>
91
+ <div class="modal-footer">
92
+ <button type="reset" class="btn btn-default ab-no" data-dismiss="modal" aria-hidden="true">
93
+ <?php _e( 'No, update just here in services', 'bookly' ) ?>
94
+ </button>
95
+ <button type="submit" class="btn btn-success ab-yes"><?php _e( 'Yes', 'bookly' ) ?></button>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ </div>
101
+
102
+ <div class="hidden">
103
+ <?php do_action( 'bookly_render_after_service_list', $service_collection ) ?>
104
+ </div>
backend/modules/settings/AB_SettingsController.php DELETED
@@ -1,197 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_SettingsController
5
- */
6
- class AB_SettingsController extends AB_Controller {
7
-
8
- const page_slug = 'ab-settings';
9
-
10
- public function index()
11
- {
12
- /** @var WP_Locale $wp_locale */
13
- global $wp_locale;
14
-
15
- $this->enqueueStyles( array(
16
- 'frontend' => array(
17
- 'css/ladda.min.css'
18
- ),
19
- 'backend' => array(
20
- 'css/bookly.main-backend.css',
21
- 'bootstrap/css/bootstrap.min.css',
22
- 'css/jCal.css',
23
- )
24
- ) );
25
-
26
- $this->enqueueScripts( array(
27
- 'backend' => array(
28
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
29
- 'js/jCal.js' => array( 'jquery' ),
30
- ),
31
- 'module' => array(
32
- 'js/settings.js' => array( 'jquery', 'ab-intlTelInput.min.js' ),
33
- ),
34
- 'frontend' => array(
35
- 'js/intlTelInput.min.js' => array( 'jquery' ),
36
- 'js/spin.min.js' => array( 'jquery' ),
37
- 'js/ladda.min.js' => array( 'jquery' ),
38
- )
39
- ) );
40
-
41
- wp_localize_script( 'ab-jCal.js', 'BooklyL10n', array(
42
- 'we_are_not_working' => __( 'We are not working on this day', 'bookly' ),
43
- 'repeat' => __( 'Repeat every year', 'bookly' ),
44
- 'months' => array_values( $wp_locale->month ),
45
- 'days' => array_values( $wp_locale->weekday_abbrev )
46
- ) );
47
- $this->message = '';
48
- // Save the settings.
49
- if ( ! empty ( $_POST ) ) {
50
- switch ( $this->getParameter( 'type' ) ) {
51
- case '_payments': // Payments form.
52
- update_option( 'ab_settings_pay_locally', (int)$this->getParameter( 'ab_settings_pay_locally' ) );
53
- break;
54
- case '_hours': // Business hours form.
55
- $this->form = new AB_BusinessHoursForm();
56
- break;
57
- case '_general': // General form.
58
- $ab_settings_time_slot_length = $this->getParameter( 'ab_settings_time_slot_length' );
59
- if ( in_array( $ab_settings_time_slot_length, array( 5, 10, 12, 15, 20, 30, 60, 90, 120, 180, 240, 360 ) ) ) {
60
- update_option( 'ab_settings_time_slot_length', $ab_settings_time_slot_length );
61
- }
62
- update_option( 'ab_settings_minimum_time_prior_booking', (int)$this->getParameter( 'ab_settings_minimum_time_prior_booking' ) );
63
- update_option( 'ab_settings_maximum_available_days_for_booking', (int)$this->getParameter( 'ab_settings_maximum_available_days_for_booking' ) );
64
- update_option( 'ab_settings_use_client_time_zone', (int)$this->getParameter( 'ab_settings_use_client_time_zone' ) );
65
- update_option( 'ab_settings_cancel_page_url', $this->getParameter( 'ab_settings_cancel_page_url' ) );
66
- update_option( 'ab_settings_final_step_url', $this->getParameter( 'ab_settings_final_step_url' ) );
67
- update_option( 'ab_settings_allow_staff_members_edit_profile', (int)$this->getParameter( 'ab_settings_allow_staff_members_edit_profile' ) );
68
- update_option( 'ab_settings_link_assets_method', $this->getParameter( 'ab_settings_link_assets_method' ) );
69
- $this->message = __( 'Settings saved.', 'bookly' );
70
- break;
71
- case '_google_calendar': // Google calendar form.
72
- $this->message = __( 'Settings saved.', 'bookly' );
73
- break;
74
- case '_holidays': // Holidays form.
75
- // Company form.
76
- break;
77
- case '_customers': // Customers form.
78
- update_option( 'ab_settings_create_account', (int)$this->getParameter( 'ab_settings_create_account' ) );
79
- update_option( 'ab_settings_phone_default_country', $this->getParameter( 'ab_settings_phone_default_country' ) );
80
- update_option( 'ab_sms_default_country_code', $this->getParameter( 'ab_sms_default_country_code' ) );
81
- $this->message = __( 'Settings saved.', 'bookly' );
82
- break;
83
- case '_woocommerce': // WooCommerce form.
84
- $this->message = __( 'Settings saved.', 'bookly' );
85
- break;
86
- case '_company': // Company form.
87
- $this->form = new AB_CompanyForm();
88
- break;
89
- }
90
- if ( in_array( $this->getParameter( 'type' ), array ( '_hours', '_company' ) ) ) {
91
- $this->form->bind( $this->getPostParameters(), $_FILES );
92
- $this->form->save();
93
- $this->message = __( 'Settings saved.', 'bookly' );
94
- }
95
- }
96
-
97
- // Get holidays.
98
- $this->holidays = $this->getHolidays();
99
- $this->candidates = $this->getCandidatesBooklyProduct();
100
-
101
- $this->render( 'index' );
102
- } // index
103
-
104
- /**
105
- * Ajax request for Holidays calendar
106
- */
107
- public function executeSettingsHoliday()
108
- {
109
- $id = $this->getParameter( 'id', false );
110
- $holiday = $this->getParameter( 'holiday' ) == 'true';
111
- $repeat = $this->getParameter( 'repeat' ) == 'true';
112
- $day = $this->getParameter( 'day', false );
113
-
114
- // update or delete the event
115
- if ( $id ) {
116
- if ( $holiday ) {
117
- $this->getWpdb()->update( AB_Holiday::getTableName(), array( 'repeat_event' => intval( $repeat ) ), array( 'id' => $id ), array( '%d' ) );
118
- $this->getWpdb()->update( AB_Holiday::getTableName(), array( 'repeat_event' => intval( $repeat ) ), array( 'parent_id' => $id ), array( '%d' ) );
119
- } else {
120
- AB_Holiday::query()->delete()->where( 'id', $id )->where( 'parent_id', $id, 'OR' )->execute();
121
- }
122
- // add the new event
123
- } elseif ( $holiday && $day ) {
124
- $holiday = new AB_Holiday( array( 'date' => $day, 'repeat_event' => intval( $repeat ) ) );
125
- $holiday->save();
126
- foreach ( AB_Staff::query()->fetchArray() as $employee ) {
127
- $staff_holiday = new AB_Holiday( array( 'date' => $day, 'repeat_event' => intval( $repeat ), 'staff_id' => $employee['id'], 'parent_id' => $holiday->get( 'id' ) ) );
128
- $staff_holiday->save();
129
- }
130
- }
131
-
132
- // and return refreshed events
133
- echo $this->getHolidays();
134
- exit;
135
- }
136
-
137
- /**
138
- * @return mixed|string|void
139
- */
140
- protected function getHolidays()
141
- {
142
- $collection = AB_Holiday::query()->where( 'staff_id', null )->fetchArray();
143
- $holidays = array();
144
- if ( count( $collection ) ) {
145
- foreach ( $collection as $holiday ) {
146
- $holidays[ $holiday['id'] ] = array(
147
- 'm' => intval( date( 'm', strtotime( $holiday['date'] ) ) ),
148
- 'd' => intval( date( 'd', strtotime( $holiday['date'] ) ) ),
149
- 'title' => $holiday['title'],
150
- );
151
- // if not repeated holiday, add the year
152
- if ( ! $holiday['repeat_event'] ) {
153
- $holidays[ $holiday['id'] ]['y'] = intval( date( 'Y', strtotime( $holiday['date'] ) ) );
154
- }
155
- }
156
- }
157
-
158
- return json_encode( $holidays );
159
- }
160
-
161
- protected function getCandidatesBooklyProduct()
162
- {
163
- $goods = array( array( 'id' => 0, 'name' => __( 'Select product', 'bookly' ) ) );
164
- $args = array(
165
- 'numberposts' => 0,
166
- 'post_type' => 'product',
167
- 'suppress_filters' => true
168
- );
169
- $collection = get_posts( $args );
170
- foreach ( $collection as $item ) {
171
- $goods[] = array( 'id' => $item->ID, 'name' => $item->post_title );
172
- }
173
- wp_reset_postdata();
174
-
175
- return $goods;
176
- }
177
-
178
- /**
179
- * Ajax request to dismiss admin notice for current user.
180
- */
181
- public function executeDismissAdminNotice()
182
- {
183
- update_user_meta( get_current_user_id(), 'ab_dismiss_admin_notice', 1 );
184
- }
185
-
186
- /**
187
- * Override parent method to add 'wp_ajax_ab_' prefix
188
- * so current 'execute*' methods look nicer.
189
- *
190
- * @param string $prefix
191
- */
192
- protected function registerWpActions( $prefix = '' )
193
- {
194
- parent::registerWpActions( 'wp_ajax_ab_' );
195
- }
196
-
197
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/Controller.php ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Settings;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Controller
8
+ * @package BooklyLite\Backend\Modules\Settings
9
+ */
10
+ class Controller extends Lib\Base\Controller
11
+ {
12
+ const page_slug = 'ab-settings';
13
+
14
+ public function index()
15
+ {
16
+ /** @var \WP_Locale $wp_locale */
17
+ global $wp_locale;
18
+
19
+ wp_enqueue_media();
20
+ $this->enqueueStyles( array(
21
+ 'frontend' => array( 'css/ladda.min.css' ),
22
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', )
23
+ ) );
24
+
25
+ $this->enqueueScripts( array(
26
+ 'backend' => array(
27
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
28
+ 'js/jCal.js' => array( 'jquery' ),
29
+ 'js/alert.js' => array( 'jquery' ),
30
+ ),
31
+ 'module' => array( 'js/settings.js' => array( 'jquery', 'ab-intlTelInput.min.js', 'jquery-ui-sortable' ) ),
32
+ 'frontend' => array(
33
+ 'js/intlTelInput.min.js' => array( 'jquery' ),
34
+ 'js/spin.min.js' => array( 'jquery' ),
35
+ 'js/ladda.min.js' => array( 'jquery' ),
36
+ )
37
+ ) );
38
+
39
+ $current_tab = $this->hasParameter( 'tab' ) ? $this->getParameter( 'tab' ) : 'general';
40
+ $alert = array( 'success' => array(), 'error' => array() );
41
+
42
+ // Save the settings.
43
+ if ( ! empty ( $_POST ) ) {
44
+ switch ( $this->getParameter( 'tab' ) ) {
45
+ case 'payments': // Payments form.
46
+ $form = new Forms\Payments();
47
+ break;
48
+ case 'business_hours': // Business hours form.
49
+ $form = new Forms\BusinessHours();
50
+ break;
51
+ case 'purchase_code': // Purchase Code form.
52
+ break;
53
+ case 'general': // General form.
54
+ $ab_settings_time_slot_length = $this->getParameter( 'ab_settings_time_slot_length' );
55
+ if ( in_array( $ab_settings_time_slot_length, array( 5, 10, 12, 15, 20, 30, 45, 60, 90, 120, 180, 240, 360 ) ) ) {
56
+ update_option( 'ab_settings_time_slot_length', $ab_settings_time_slot_length );
57
+ }
58
+ update_option( 'ab_settings_allow_staff_members_edit_profile', (int) $this->getParameter( 'ab_settings_allow_staff_members_edit_profile' ) );
59
+ update_option( 'ab_settings_approve_page_url', $this->getParameter( 'ab_settings_approve_page_url' ) );
60
+ update_option( 'ab_settings_cancel_denied_page_url', $this->getParameter( 'ab_settings_cancel_denied_page_url' ) );
61
+ update_option( 'ab_settings_cancel_page_url', $this->getParameter( 'ab_settings_cancel_page_url' ) );
62
+ update_option( 'ab_settings_default_appointment_status', $this->getParameter( 'ab_settings_default_appointment_status' ) );
63
+ update_option( 'ab_settings_final_step_url', '' );
64
+ update_option( 'ab_settings_link_assets_method', $this->getParameter( 'ab_settings_link_assets_method' ) );
65
+ update_option( 'ab_settings_maximum_available_days_for_booking', (int) $this->getParameter( 'ab_settings_maximum_available_days_for_booking' ) );
66
+ update_option( 'ab_settings_minimum_time_prior_booking', (int) $this->getParameter( 'ab_settings_minimum_time_prior_booking' ) );
67
+ update_option( 'ab_settings_minimum_time_prior_cancel', $this->getParameter( 'ab_settings_minimum_time_prior_cancel' ) );
68
+ update_option( 'ab_settings_use_client_time_zone', (int) $this->getParameter( 'ab_settings_use_client_time_zone' ) );
69
+ update_option( 'ab_lite_uninstall_remove_bookly_data', (int) $this->getParameter( 'ab_lite_uninstall_remove_bookly_data' ) );
70
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
71
+ break;
72
+ case 'google_calendar': // Google calendar form.
73
+ break;
74
+ case 'customers': // Customers form.
75
+ update_option( 'ab_settings_client_cancel_appointment_action', $this->getParameter( 'ab_settings_client_cancel_appointment_action' ) );
76
+ update_option( 'ab_settings_create_account', (int) $this->getParameter( 'ab_settings_create_account' ) );
77
+ update_option( 'ab_settings_phone_default_country', $this->getParameter( 'ab_settings_phone_default_country' ) );
78
+ update_option( 'ab_sms_default_country_code', $this->getParameter( 'ab_sms_default_country_code' ) );
79
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
80
+ break;
81
+ case 'woocommerce': // WooCommerce form.
82
+ break;
83
+ case 'cart': // Cart form.
84
+ update_option( 'ab_cart_show_columns', $this->getParameter( 'ab_cart_show_columns', array() ) );
85
+ update_option( 'ab_settings_cart_notifications_combined', $this->getParameter( 'ab_settings_cart_notifications_combined' ) );
86
+ update_option( 'ab_settings_step_cart_enabled', '0' );
87
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
88
+ break;
89
+ case 'company': // Company form.
90
+ update_option( 'ab_settings_company_address', $this->getParameter( 'ab_settings_company_address' ) );
91
+ update_option( 'ab_settings_company_logo_attachment_id', $this->getParameter( 'ab_settings_company_logo_attachment_id' ) );
92
+ update_option( 'ab_settings_company_name', $this->getParameter( 'ab_settings_company_name' ) );
93
+ update_option( 'ab_settings_company_phone', $this->getParameter( 'ab_settings_company_phone' ) );
94
+ update_option( 'ab_settings_company_website', $this->getParameter( 'ab_settings_company_website' ) );
95
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
96
+ break;
97
+ default:
98
+ // Let add-ons save their settings.
99
+ $alert = apply_filters( 'bookly_save_settings', $alert, $this->getParameter( 'tab' ), $this->getPostParameters() );
100
+ }
101
+
102
+ if ( in_array( $this->getParameter( 'tab' ), array ( 'payments', 'business_hours' ) ) ) {
103
+ $form->bind( $this->getPostParameters(), $_FILES );
104
+ $form->save();
105
+
106
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
107
+ }
108
+ }
109
+
110
+ $holidays = $this->getHolidays();
111
+ $candidates = $this->getCandidatesBooklyProduct();
112
+
113
+ // Check if WooCommerce cart exists.
114
+ if ( get_option( 'ab_woocommerce_enabled' ) && class_exists( 'WooCommerce', false ) ) {
115
+ $post = get_post( wc_get_page_id( 'cart' ) );
116
+ if ( $post === null || $post->post_status != 'publish' ) {
117
+ $alert['error'][] = sprintf(
118
+ __( 'WooCommerce cart is not set up. Follow the <a href="%s">link</a> to correct this problem.', 'bookly' ),
119
+ Lib\Utils\Common::escAdminUrl( 'wc-status', array( 'tab' => 'tools' ) )
120
+ );
121
+ }
122
+ }
123
+ $cart_columns = array(
124
+ 'service' => Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_service' ),
125
+ 'date' => __( 'Date', 'bookly' ),
126
+ 'time' => __( 'Time', 'bookly' ),
127
+ 'employee' => Lib\Utils\Common::getTranslatedOption( 'ab_appearance_text_label_employee' ),
128
+ 'price' => __( 'Price', 'bookly' ),
129
+ );
130
+
131
+ $cart_columns = apply_filters( 'bookly_settings_cart_columns', $cart_columns );
132
+
133
+ wp_localize_script( 'ab-jCal.js', 'BooklyL10n', array(
134
+ 'alert' => $alert,
135
+ 'close' => __( 'Close', 'bookly' ),
136
+ 'current_tab' => $current_tab,
137
+ 'days' => array_values( $wp_locale->weekday_abbrev ),
138
+ 'months' => array_values( $wp_locale->month ),
139
+ 'repeat' => __( 'Repeat every year', 'bookly' ),
140
+ 'we_are_not_working' => __( 'We are not working on this day', 'bookly' ),
141
+ '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>',
142
+ ) );
143
+
144
+ $this->render( 'index', compact( 'holidays', 'candidates', 'cart_columns' ) );
145
+ }
146
+
147
+ /**
148
+ * Ajax request for Holidays calendar
149
+ */
150
+ public function executeSettingsHoliday()
151
+ {
152
+ global $wpdb;
153
+
154
+ $id = $this->getParameter( 'id', false );
155
+ $day = $this->getParameter( 'day', false );
156
+ $holiday = $this->getParameter( 'holiday' ) == 'true';
157
+ $repeat = $this->getParameter( 'repeat' ) == 'true';
158
+
159
+ // update or delete the event
160
+ if ( $id ) {
161
+ if ( $holiday ) {
162
+ $wpdb->update( Lib\Entities\Holiday::getTableName(), array( 'repeat_event' => intval( $repeat ) ), array( 'id' => $id ), array( '%d' ) );
163
+ $wpdb->update( Lib\Entities\Holiday::getTableName(), array( 'repeat_event' => intval( $repeat ) ), array( 'parent_id' => $id ), array( '%d' ) );
164
+ } else {
165
+ Lib\Entities\Holiday::query()->delete()->where( 'id', $id )->where( 'parent_id', $id, 'OR' )->execute();
166
+ }
167
+ // add the new event
168
+ } elseif ( $holiday && $day ) {
169
+ $holiday = new Lib\Entities\Holiday( array( 'date' => $day, 'repeat_event' => intval( $repeat ) ) );
170
+ $holiday->save();
171
+ foreach ( Lib\Entities\Staff::query()->fetchArray() as $employee ) {
172
+ $staff_holiday = new Lib\Entities\Holiday( array( 'date' => $day, 'repeat_event' => intval( $repeat ), 'staff_id' => $employee['id'], 'parent_id' => $holiday->get( 'id' ) ) );
173
+ $staff_holiday->save();
174
+ }
175
+ }
176
+
177
+ // and return refreshed events
178
+ echo $this->getHolidays();
179
+ exit;
180
+ }
181
+
182
+ /**
183
+ * @return mixed|string|void
184
+ */
185
+ protected function getHolidays()
186
+ {
187
+ $collection = Lib\Entities\Holiday::query()->where( 'staff_id', null )->fetchArray();
188
+ $holidays = array();
189
+ if ( count( $collection ) ) {
190
+ foreach ( $collection as $holiday ) {
191
+ $holidays[ $holiday['id'] ] = array(
192
+ 'm' => intval( date( 'm', strtotime( $holiday['date'] ) ) ),
193
+ 'd' => intval( date( 'd', strtotime( $holiday['date'] ) ) ),
194
+ );
195
+ // If not repeated holiday, add the year
196
+ if ( ! $holiday['repeat_event'] ) {
197
+ $holidays[ $holiday['id'] ]['y'] = intval( date( 'Y', strtotime( $holiday['date'] ) ) );
198
+ }
199
+ }
200
+ }
201
+
202
+ return json_encode( $holidays );
203
+ }
204
+
205
+ protected function getCandidatesBooklyProduct()
206
+ {
207
+ $goods = array( array( 'id' => 0, 'name' => __( 'Select product', 'bookly' ) ) );
208
+ $args = array(
209
+ 'numberposts' => -1,
210
+ 'post_type' => 'product',
211
+ 'suppress_filters' => true
212
+ );
213
+ $collection = get_posts( $args );
214
+ foreach ( $collection as $item ) {
215
+ $goods[] = array( 'id' => $item->ID, 'name' => $item->post_title );
216
+ }
217
+ wp_reset_postdata();
218
+
219
+ return $goods;
220
+ }
221
+
222
+ /**
223
+ * Ajax request to dismiss admin notice for current user.
224
+ */
225
+ public function executeDismissAdminNotice()
226
+ {
227
+ update_user_meta( get_current_user_id(), $this->getParameter( 'prefix' ) . 'dismiss_admin_notice', 1 );
228
+ }
229
+
230
+ /**
231
+ * Override parent method to add 'wp_ajax_ab_' prefix
232
+ * so current 'execute*' methods look nicer.
233
+ *
234
+ * @param string $prefix
235
+ */
236
+ protected function registerWpActions( $prefix = '' )
237
+ {
238
+ parent::registerWpActions( 'wp_ajax_ab_' );
239
+ }
240
+
241
+ }
backend/modules/settings/forms/AB_BusinessHoursForm.php DELETED
@@ -1,66 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_BusinessHoursForm
5
- */
6
- class AB_BusinessHoursForm extends AB_Form {
7
-
8
- public function __construct()
9
- {
10
- $this->setFields(array(
11
- 'ab_settings_monday_start',
12
- 'ab_settings_monday_end',
13
- 'ab_settings_tuesday_start',
14
- 'ab_settings_tuesday_end',
15
- 'ab_settings_wednesday_start',
16
- 'ab_settings_wednesday_end',
17
- 'ab_settings_thursday_start',
18
- 'ab_settings_thursday_end',
19
- 'ab_settings_friday_start',
20
- 'ab_settings_friday_end',
21
- 'ab_settings_saturday_start',
22
- 'ab_settings_saturday_end',
23
- 'ab_settings_sunday_start',
24
- 'ab_settings_sunday_end',
25
- ));
26
- }
27
-
28
- public function save()
29
- {
30
- foreach ( $this->data as $field => $value ) {
31
- update_option( $field, $value );
32
- }
33
- }
34
-
35
- /**
36
- * @param string $field_name
37
- * @param bool $is_start
38
- * @return string
39
- */
40
- public function renderField( $field_name = 'ab_settings_monday', $is_start = true )
41
- {
42
- $ts_length = AB_Config::getTimeSlotLength();
43
- $time_output = AB_StaffScheduleItem::WORKING_START_TIME;
44
- $time_end = AB_StaffScheduleItem::WORKING_END_TIME;
45
- $option_name = $field_name . ( $is_start ? '_start' : '_end' );
46
- $class_name = $is_start ? 'select_start' : 'select_end';
47
- $selected_value = get_option( $option_name );
48
- $output = "<select class='form-control ab-auto-w {$class_name}' name={$option_name}>";
49
-
50
- if ( $is_start ) {
51
- $output .= "<option value=''>" . __( 'OFF', 'bookly' ) . "</option>";
52
- $time_end -= $ts_length;
53
- }
54
-
55
- while ( $time_output <= $time_end ) {
56
- $value = AB_DateTimeUtils::buildTimeString( $time_output, false );
57
- $op_name = AB_DateTimeUtils::formatTime( $time_output );
58
- $output .= "<option value='{$value}'".selected( $value, $selected_value, false ).">{$op_name}</option>";
59
- $time_output += $ts_length;
60
- }
61
-
62
- $output .= '</select>';
63
-
64
- return $output;
65
- }
66
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/forms/AB_CompanyForm.php DELETED
@@ -1,64 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- /**
4
- * Class AB_CompanyForm
5
- */
6
- class AB_CompanyForm extends AB_Form {
7
-
8
- public function __construct()
9
- {
10
- $this->setFields(array(
11
- 'ab_settings_company_name',
12
- 'ab_settings_company_logo',
13
- 'ab_settings_company_address',
14
- 'ab_settings_company_phone',
15
- 'ab_settings_company_website',
16
- 'ab_settings_company_logo_path',
17
- 'ab_settings_company_logo_url',
18
- ));
19
- }
20
-
21
- /**
22
- * @param array $post
23
- * @param array $files
24
- */
25
- public function bind( array $post, array $files = array() )
26
- {
27
- parent::bind( $post, $files );
28
-
29
- // Remove the old image.
30
- if ( isset( $post['ab_remove_logo'] ) && file_exists( get_option( 'ab_settings_company_logo_path' ) ) ) {
31
- unlink( get_option( 'ab_settings_company_logo_path' ) );
32
- update_option( 'ab_settings_company_logo_path', '' );
33
- update_option( 'ab_settings_company_logo_url', '' );
34
- }
35
-
36
- // And add new.
37
- if ( isset ( $files['ab_settings_company_logo'] ) && $files['ab_settings_company_logo']['tmp_name'] ) {
38
-
39
- if ( in_array( $files['ab_settings_company_logo']['type'], array( 'image/gif', 'image/jpeg', 'image/png' ) ) ) {
40
- $uploaded = wp_handle_upload( $files['ab_settings_company_logo'], array( 'test_form' => false ) );
41
- if ( $uploaded ) {
42
- $editor = wp_get_image_editor( $uploaded['file'] );
43
- $editor->resize( 200, 200 );
44
- $editor->save( $uploaded['file'] );
45
-
46
- $this->data['ab_settings_company_logo_path'] = $uploaded['file'];
47
- $this->data['ab_settings_company_logo_url'] = $uploaded['url'];
48
-
49
- // Remove old image.
50
- if ( file_exists( get_option( 'ab_settings_company_logo_path' ) ) ) {
51
- unlink( get_option( 'ab_settings_company_logo_path' ) );
52
- }
53
- }
54
- }
55
- }
56
- }
57
-
58
- public function save()
59
- {
60
- foreach ( $this->data as $field => $value ) {
61
- update_option( $field, $value );
62
- }
63
- }
64
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/forms/BusinessHours.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Settings\Forms;
3
+
4
+ use BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class BusinessHours
8
+ * @package BooklyLite\Backend\Modules\Settings\Forms
9
+ */
10
+ class BusinessHours extends Lib\Base\Form
11
+ {
12
+ public function __construct()
13
+ {
14
+ $this->setFields( array(
15
+ 'ab_settings_monday_start',
16
+ 'ab_settings_monday_end',
17
+ 'ab_settings_tuesday_start',
18
+ 'ab_settings_tuesday_end',
19
+ 'ab_settings_wednesday_start',
20
+ 'ab_settings_wednesday_end',
21
+ 'ab_settings_thursday_start',
22
+ 'ab_settings_thursday_end',
23
+ 'ab_settings_friday_start',
24
+ 'ab_settings_friday_end',
25
+ 'ab_settings_saturday_start',
26
+ 'ab_settings_saturday_end',
27
+ 'ab_settings_sunday_start',
28
+ 'ab_settings_sunday_end',
29
+ ) );
30
+ }
31
+
32
+ public function save()
33
+ {
34
+ foreach ( $this->data as $field => $value ) {
35
+ update_option( $field, $value );
36
+ }
37
+ }
38
+
39
+ /**
40
+ * @param string $field_name
41
+ * @param bool $is_start
42
+ * @return string
43
+ */
44
+ public function renderField( $field_name = 'ab_settings_monday', $is_start = true )
45
+ {
46
+ $ts_length = Lib\Config::getTimeSlotLength();
47
+ $time_output = Lib\Entities\StaffScheduleItem::WORKING_START_TIME;
48
+ $time_end = Lib\Entities\StaffScheduleItem::WORKING_END_TIME;
49
+ $option_name = $field_name . ( $is_start ? '_start' : '_end' );
50
+ $class_name = $is_start ? 'select_start' : 'select_end hide-on-non-working-day';
51
+ $selected_value = get_option( $option_name );
52
+ $selected_seconds = Lib\Utils\DateTime::timeToSeconds( $selected_value );
53
+ $output = "<select style='display:inline-block' class='form-control ab-auto-w {$class_name}' name={$option_name}>";
54
+
55
+ if ( $is_start ) {
56
+ $output .= '<option value="">' . __( 'OFF', 'bookly' ) . '</option>';
57
+ $time_end -= $ts_length;
58
+ }
59
+ $value_added = false;
60
+ while ( $time_output <= $time_end ) {
61
+ if ( $value_added === false ) {
62
+ if ( $selected_seconds == $time_output ) {
63
+ $value_added = true;
64
+ } elseif ( $selected_seconds < $time_output ) {
65
+ $output .= "<option value='{$selected_value}' selected='selected'>{$selected_value}</option>";
66
+ $value_added = true;
67
+ }
68
+ }
69
+
70
+ $value = Lib\Utils\DateTime::buildTimeString( $time_output, false );
71
+ $op_name = Lib\Utils\DateTime::formatTime( $time_output );
72
+ $output .= "<option value='{$value}'" . selected( $value, $selected_value, false ) . ">{$op_name}</option>";
73
+ $time_output += $ts_length;
74
+ }
75
+
76
+ $output .= '</select>';
77
+
78
+ return $output;
79
+ }
80
+
81
+ }
backend/modules/settings/forms/Payments.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace BooklyLite\Backend\Modules\Settings\Forms;
3
+
4
+ use \BooklyLite\Lib;
5
+
6
+ /**
7
+ * Class Payments
8
+ * @package BooklyLite\Backend\Modules\Settings
9
+ */
10
+ class Payments extends Lib\Base\Form
11
+ {
12
+ public function __construct()
13
+ {
14
+ $this->setFields( array(
15
+ 'ab_currency',
16
+ 'ab_settings_pay_locally',
17
+ ) );
18
+ }
19
+
20
+ public function save()
21
+ {
22
+ foreach ( $this->data as $field => $value ) {
23
+ update_option( $field, $value );
24
+ }
25
+ }
26
+
27
+ }
backend/modules/settings/resources/js/settings.js CHANGED
@@ -1,20 +1,36 @@
1
  jQuery(function ($) {
2
  var $form = $('#business-hours'),
3
- $all_tabs = $('#ab_settings_company, #ab_settings_payments, #ab_settings_hours, #ab_settings_holidays, #ab_settings_purchase_code, #ab_settings_google_calendar, #ab_settings_general, #ab_settings_woocommerce, #ab_settings_customers'),
4
- $all_forms = $('#company-form, #payments-form, #hours-form, #holidays-form, #purchase-code-form, #general-form, #google-calendar-form, #woocommerce-form, #customers-form'),
5
  $final_step_url = $('input[name=ab_settings_final_step_url]'),
6
- $final_step_url_mode = $('#ab_settings_final_step_url_mode');
 
7
 
8
- Ladda.bind( 'button[type=submit]' );
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  $('.select_start', $form).on('change', function () {
11
- var $row = $(this).parent(),
12
- $end_select = $('.select_end', $row),
13
  $start_select = $(this);
14
 
15
  if ($start_select.val()) {
16
- $end_select.show();
17
- $('span', $row).show();
18
 
19
  var start_time = $start_select.val();
20
 
@@ -25,7 +41,7 @@ jQuery(function ($) {
25
  // Hides end time options with value less than in the start time
26
  $('option', $end_select).each(function () {
27
  if ($(this).val() <= start_time) {
28
- $(this).wrap("<span>").parent().hide();
29
  }
30
  });
31
 
@@ -33,8 +49,7 @@ jQuery(function ($) {
33
  $('option:visible:first', $end_select).attr('selected', true);
34
  }
35
  } else { // OFF
36
- $end_select.hide();
37
- $('span', $row).hide();
38
  }
39
  }).each(function () {
40
  var $row = $(this).parent(),
@@ -65,102 +80,145 @@ jQuery(function ($) {
65
  $('.select_start', $form).trigger('change');
66
  });
67
 
68
- // Tabs Onclick Handlers.
69
- $all_tabs.on('click', function() {
70
- $('.ab-active').removeClass('ab-active');
71
- $(this).addClass('ab-active');
72
- });
73
- $('#ab_settings_company').on('click', function() {
74
- $all_forms.addClass('hidden');
75
- $('#company-form').removeClass('hidden');
76
- });
77
- $('#ab_settings_payments').on('click', function() {
78
- $all_forms.addClass('hidden');
79
- $('#payments-form').removeClass('hidden');
80
- });
81
- $('#ab_settings_hours').on('click', function() {
82
- $all_forms.addClass('hidden');
83
- $('#hours-form').removeClass('hidden');
84
- });
85
- $('#ab_settings_holidays').on('click', function() {
86
- $all_forms.addClass('hidden');
87
- $('#holidays-form').removeClass('hidden');
88
- });
89
- $('#ab_settings_purchase_code').on('click', function() {
90
- $all_forms.addClass('hidden');
91
- $('#purchase-code-form').removeClass('hidden');
92
- });
93
- $('#ab_settings_general').on('click', function() {
94
- $all_forms.addClass('hidden');
95
- $('#general-form').removeClass('hidden');
96
- });
97
- $('#ab_settings_woocommerce').on('click', function() {
98
- $all_forms.addClass('hidden');
99
- $('#woocommerce-form').removeClass('hidden');
100
- });
101
- $('#ab_settings_google_calendar').on('click', function() {
102
- $all_forms.addClass('hidden');
103
- $('#google-calendar-form').removeClass('hidden');
104
- });
105
-
106
  // Customers Tab
107
- var $customers_tab = $('#ab_settings_customers'),
108
- $default_country = $('#ab_settings_phone_default_country'),
109
  $default_country_code = $('#ab_sms_default_country_code');
110
 
111
- $customers_tab.on('click', function() {
112
- $all_forms.addClass('hidden');
113
- $('#customers-form').removeClass('hidden');
114
- }).on('click.fill', function() {
115
- $.each($.fn.intlTelInput.getCountryData(), function( index, value ) {
116
- $default_country.append('<option value="'+value.iso2 + '" data-code="' + value.dialCode + '">'+ value.name + ' +' + value.dialCode + '</option>' );
117
- });
118
- $(this).unbind('click.fill');
119
- $default_country.val( $default_country.data('country') );
120
  });
 
121
 
122
- $default_country.on('change', function() {
123
  $default_country_code.val($default_country.find('option:selected').data('code'));
124
  });
125
 
126
- if ($customers_tab.hasClass('ab-active')){
127
- $customers_tab.trigger('click.fill');
128
- }
129
-
130
  // Company Tab
131
- $('#ab-delete-logo').on('click', function() {
132
- $('#ab-show-logo').hide(300).append('<input type="hidden" id="ab-remove-logo" name="ab_remove_logo" value="1" />');
 
 
 
133
  });
134
- $('#ab_settings_company_logo').on('change', function() { $('#ab-remove-logo').remove(); });
135
 
136
- $('#ab-settings-company-reset').on('click', function() {
137
- $('#ab-remove-logo').remove();
138
- $('#ab-show-logo').show(300);
 
139
  });
140
 
141
- $('#ab_settings_google_client_id,#ab_settings_google_client_secret,#ab_settings_google_two_way_sync,#ab_settings_google_limit_events,#ab_settings_google_event_title,#ab_woocommerce,#ab_settings_coupons,#ab_paypal_type,#ab_authorizenet_type,#ab_stripe,#ab_2checkout').on('click', function(){
142
- $('#lite_notice').modal('show');
143
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
- $('.ab-popover').popover({
146
- trigger : 'hover'
 
 
147
  });
148
 
149
- $('.ab-popover-ext').popover({
150
- content: function() {
151
- return $('#' + $(this).data('ext_id')).html();
152
- },
153
- html: true
154
  });
155
 
156
  if ($final_step_url.val()) { $final_step_url_mode.val(1); }
157
- $final_step_url_mode.change(function(){
 
 
 
 
 
158
 
159
- if (this.value == 0){
160
- $final_step_url.hide().val('');
161
- }else{
162
- $final_step_url.show();
163
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  });
165
 
 
 
 
 
 
166
  });
1
  jQuery(function ($) {
2
  var $form = $('#business-hours'),
 
 
3
  $final_step_url = $('input[name=ab_settings_final_step_url]'),
4
+ $final_step_url_mode = $('#ab_settings_final_step_url_mode')
5
+ ;
6
 
7
+ booklyAlert(BooklyL10n.alert);
8
 
9
+ Ladda.bind('button[type=submit]', {timeout: 2000});
10
+
11
+ $('.bookly-limitation').on('click', function (e) {
12
+ e.preventDefault();
13
+ Ladda.stopAll();
14
+ booklyAlert({error: [BooklyL10n.limitations]});
15
+ $(this).prop('disabled', true);
16
+ });
17
+ $('#ab_settings_step_cart_enabled,#ab_woocommerce_enabled,#ab_settings_coupons').on('change', function (e) {
18
+ $(this).val('0');
19
+ booklyAlert({error: [BooklyL10n.limitations]});
20
+ $(this).find('option:gt(0)').prop('disabled', true);
21
+ });
22
+
23
+ $('#ab_settings_google_client_id,#ab_settings_google_client_secret,#ab_settings_google_two_way_sync,#ab_settings_google_limit_events,#ab_settings_google_event_title').on('focus', function () {
24
+ $(this).prop('disabled',true);
25
+ booklyAlert({error: [BooklyL10n.limitations]});
26
+ });
27
  $('.select_start', $form).on('change', function () {
28
+ var $flexbox = $(this).parents('.bookly-flexbox'),
29
+ $end_select = $('.select_end', $flexbox),
30
  $start_select = $(this);
31
 
32
  if ($start_select.val()) {
33
+ $flexbox.find('.hide-on-non-working-day').show();
 
34
 
35
  var start_time = $start_select.val();
36
 
41
  // Hides end time options with value less than in the start time
42
  $('option', $end_select).each(function () {
43
  if ($(this).val() <= start_time) {
44
+ $(this).wrap('<span>').parent().hide();
45
  }
46
  });
47
 
49
  $('option:visible:first', $end_select).attr('selected', true);
50
  }
51
  } else { // OFF
52
+ $flexbox.find('.hide-on-non-working-day').hide();
 
53
  }
54
  }).each(function () {
55
  var $row = $(this).parent(),
80
  $('.select_start', $form).trigger('change');
81
  });
82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  // Customers Tab
84
+ var $default_country = $('#ab_settings_phone_default_country'),
 
85
  $default_country_code = $('#ab_sms_default_country_code');
86
 
87
+ $.each($.fn.intlTelInput.getCountryData(), function (index, value) {
88
+ $default_country.append('<option value="' + value.iso2 + '" data-code="' + value.dialCode + '">' + value.name + ' +' + value.dialCode + '</option>');
 
 
 
 
 
 
 
89
  });
90
+ $default_country.val($default_country.data('country'));
91
 
92
+ $default_country.on('change', function () {
93
  $default_country_code.val($default_country.find('option:selected').data('code'));
94
  });
95
 
 
 
 
 
96
  // Company Tab
97
+ $('#ab-settings-company-reset').on('click', function () {
98
+ var $div = $('#bookly-js-logo .bookly-js-image'),
99
+ $input = $('[name=ab_settings_company_logo_attachment_id]');
100
+ $div.attr('style', $div.data('style'));
101
+ $input.val($input.data('default'));
102
  });
 
103
 
104
+ // Cart Tab
105
+ $('#ab_cart_show_columns').sortable({
106
+ axis : 'y',
107
+ handle : '.bookly-js-handle'
108
  });
109
 
110
+ // Payment Tab
111
+ $('#ab_paypal_type').change(function () {
112
+ if (this.value != 'disabled') {
113
+ $(this).val('disabled');
114
+ booklyAlert({error: [BooklyL10n.limitations]});
115
+ $(this).find('option:gt(0)').prop('disabled', true);
116
+ }
117
+ $('.ab-paypal-ec').toggle(this.value != 'disabled');
118
+ }).change();
119
+
120
+ $('#ab_authorizenet_type').change(function () {
121
+ if (this.value != 'disabled') {
122
+ $(this).val('disabled');
123
+ booklyAlert({error: [BooklyL10n.limitations]});
124
+ $(this).find('option:gt(0)').prop('disabled', true);
125
+ }
126
+ $('.authorizenet').toggle(this.value != 'disabled');
127
+ }).change();
128
+
129
+ $('#ab_stripe').change(function () {
130
+ if (this.value != 'disabled') {
131
+ $(this).val('disabled');
132
+ booklyAlert({error: [BooklyL10n.limitations]});
133
+ $(this).find('option:gt(0)').prop('disabled', true);
134
+ }
135
+ $('.ab-stripe').toggle(this.value == 1);
136
+ }).change();
137
+
138
+ $('#ab_2checkout').change(function () {
139
+ if (this.value != 'disabled') {
140
+ $(this).val('disabled');
141
+ booklyAlert({error: [BooklyL10n.limitations]});
142
+ $(this).find('option:gt(0)').prop('disabled', true);
143
+ }
144
+ $('.ab-2checkout').toggle(this.value != 'disabled');
145
+ }).change();
146
+
147
+ $('#ab_payulatam').change(function () {
148
+ if (this.value != 'disabled') {
149
+ $(this).val('disabled');
150
+ booklyAlert({error: [BooklyL10n.limitations]});
151
+ $(this).find('option:gt(0)').prop('disabled', true);
152
+ }
153
+ $('.ab-payulatam').toggle(this.value != 'disabled');
154
+ }).change();
155
+
156
+ $('#ab_payson').change(function () {
157
+ if (this.value != 'disabled') {
158
+ $(this).val('disabled');
159
+ booklyAlert({error: [BooklyL10n.limitations]});
160
+ $(this).find('option:gt(0)').prop('disabled', true);
161
+ }
162
+ $('.ab-payson').toggle(this.value != 'disabled');
163
+ }).change();
164
+
165
+ $('#ab_mollie').change(function () {
166
+ if (this.value != 'disabled') {
167
+ $(this).val('disabled');
168
+ booklyAlert({error: [BooklyL10n.limitations]});
169
+ $(this).find('option:gt(0)').prop('disabled', true);
170
+ }
171
+ $('.ab-mollie').toggle(this.value != 'disabled');
172
+ }).change();
173
 
174
+ $('#ab-payments-reset').on('click', function (event) {
175
+ setTimeout(function () {
176
+ $('#ab_paypal_type,#ab_authorizenet_type,#ab_stripe,#ab_2checkout,#ab_payulatam,#ab_payson,#ab_mollie').change();
177
+ }, 50);
178
  });
179
 
180
+ $('#ab-customer-reset').on('click', function (event) {
181
+ $default_country.val($default_country.data('country'));
 
 
 
182
  });
183
 
184
  if ($final_step_url.val()) { $final_step_url_mode.val(1); }
185
+ $final_step_url_mode.change(function () {
186
+ $(this).val(0);
187
+ booklyAlert({error: [BooklyL10n.limitations]});
188
+ $(this).find('option:gt(0)').prop('disabled', true);
189
+ $final_step_url.hide().val('');
190
+ });
191
 
192
+ $('li[data-target="#ab_settings_' + BooklyL10n.current_tab + '"]').tab('show');
193
+
194
+ $('#bookly-js-logo .bookly-pretty-indicator').on('click', function(){
195
+ var frame = wp.media({
196
+ library: {type: 'image'},
197
+ multiple: false
198
+ });
199
+ frame.on('select', function () {
200
+ var selection = frame.state().get('selection').toJSON(),
201
+ img_src
202
+ ;
203
+ if (selection.length) {
204
+ if (selection[0].sizes['thumbnail'] !== undefined) {
205
+ img_src = selection[0].sizes['thumbnail'].url;
206
+ } else {
207
+ img_src = selection[0].url;
208
+ }
209
+ $('[name=ab_settings_company_logo_attachment_id]').val(selection[0].id);
210
+ $('#bookly-js-logo .bookly-js-image').css({'background-image': 'url(' + img_src + ')', 'background-size': 'cover'});
211
+ $('#bookly-js-logo .bookly-thumb-delete').show();
212
+ $(this).hide();
213
+ }
214
+ });
215
+
216
+ frame.open();
217
  });
218
 
219
+ $('#bookly-js-logo .bookly-thumb-delete').on('click', function () {
220
+ var $thumb = $(this).parents('.bookly-js-image');
221
+ $thumb.attr('style', '');
222
+ $('[name=ab_settings_company_logo_attachment_id]').val('');
223
+ });
224
  });
backend/modules/settings/templates/_cartForm.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'cart' ) ) ?>"
3
+ class="ab-settings-form">
4
+ <div class="form-group">
5
+ <label for="ab_settings_step_cart_enabled"><?php _e( 'Cart', 'bookly' ) ?></label>
6
+ <p class="help-block"><?php _e( 'If cart is enabled then your clients will be able to book several appointments at once. Please note that WooCommerce integration must be disabled.', 'bookly' ) ?></p>
7
+ <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'ab_settings_step_cart_enabled' ) ?>
8
+ </div>
9
+ <div class="form-group">
10
+ <label for="ab_cart_show_columns"><?php _e('Columns', 'bookly') ?></label><br/>
11
+ <div class="ab-flags" id="ab_cart_show_columns">
12
+ <?php foreach ( (array) get_option( 'ab_cart_show_columns' ) as $column => $attr ) : ?>
13
+ <div class="bookly-flexbox"<?php if ( $column == 'deposit' && ! \BooklyLite\Lib\Utils\Common::isPluginActive( 'bookly-addon-deposit-payments/main.php' ) ): ?> style="display:none"<?php endif ?>>
14
+ <div class="bookly-flex-cell">
15
+ <i class="bookly-js-handle bookly-margin-right-sm bookly-icon bookly-icon-draghandle bookly-cursor-move" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
16
+ </div>
17
+ <div class="bookly-flex-cell" style="width: 100%">
18
+ <div class="checkbox">
19
+ <label>
20
+ <input type="hidden" name="ab_cart_show_columns[<?php echo $column ?>][show]" value="0">
21
+ <input type="checkbox"
22
+ name="ab_cart_show_columns[<?php echo $column ?>][show]"
23
+ value="1" <?php checked($attr['show'], true) ?>>
24
+ <?php echo $cart_columns[$column] ?>
25
+ </label>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ <?php endforeach ?>
30
+ </div>
31
+ </div>
32
+ <div class="form-group">
33
+ <label for="ab_settings_cart_notifications_combined"><?php _e( 'Combined notifications', 'bookly' ) ?></label>
34
+ <p class="help-block"><?php _e( 'If combined notifications are enabled then your clients will receive single notification for whole order instead of separate notification per each cart item. You will need to edit corresponding templates in Email and SMS Notifications.', 'bookly' ) ?></p>
35
+ <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'ab_settings_cart_notifications_combined' ) ?>
36
+ </div>
37
+
38
+ <div class="panel-footer">
39
+ <?php \BooklyLite\Lib\Utils\Common::submitButton() ?>
40
+ <?php \BooklyLite\Lib\Utils\Common::resetButton() ?>
41
+ </div>
42
+ </form>
backend/modules/settings/templates/_companyForm.php CHANGED
@@ -1,51 +1,58 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <form method="post" action="<?php echo esc_url( add_query_arg( 'type', '_company' ) ) ?>" enctype="multipart/form-data" class="ab-settings-form">
3
- <table class="form-horizontal">
4
- <tr>
5
- <td>
6
- <label for="ab_settings_company_name"><?php _e( 'Company name', 'bookly' ) ?></label>
7
- </td>
8
- <td><input id="ab_settings_company_name" class="form-control" type="text" name="ab_settings_company_name" value="<?php echo esc_attr( get_option( 'ab_settings_company_name' ) ) ?>" reset="<?php echo esc_attr( get_option( 'ab_settings_company_name' ) ) ?>"/></td>
9
- </tr>
10
- <tr>
11
- <td valign="top">
12
- <label for="ab_settings_company_logo"><?php _e( 'Company logo', 'bookly' ) ?></label>
13
- </td>
14
- <td>
15
- <?php if ( get_option( 'ab_settings_company_logo_url' ) ): ?>
16
- <div id="ab-show-logo">
17
- <img src="<?php echo esc_attr( get_option( 'ab_settings_company_logo_url' ) ) ?>" alt="<?php echo esc_attr( __( 'Company logo', 'bookly' ) ) ?>"/>
18
- <a id="ab-delete-logo" href="javascript:void(0)"><?php _e( 'Delete', 'bookly' ) ?></a>
19
- <br/>
 
 
 
 
 
 
 
 
 
 
 
20
  </div>
21
- <?php endif ?>
22
- <input name="ab_settings_company_logo" id="ab_settings_company_logo" type="file" />
23
- </td>
24
- </tr>
25
- <tr>
26
- <td valign="top">
27
- <label for="ab_settings_company_address"><?php _e( 'Address', 'bookly' ) ?></label>
28
- </td>
29
- <td><textarea id="ab_settings_company_address" class="form-control" rows="5" name="ab_settings_company_address"><?php echo esc_attr( get_option( 'ab_settings_company_address' ) ) ?></textarea></td>
30
- </tr>
31
- <tr>
32
- <td>
33
- <label for="ab_settings_company_phone"><?php _e( 'Phone', 'bookly' ) ?></label>
34
- </td>
35
- <td><input id="ab_settings_company_phone" class="form-control" type="text" name="ab_settings_company_phone" value="<?php echo esc_attr( get_option( 'ab_settings_company_phone' ) ) ?>" /></td>
36
- </tr>
37
- <tr>
38
- <td>
39
- <label for="ab_settings_company_website"><?php _e( 'Website', 'bookly' ) ?></label>
40
- </td>
41
- <td><input id="ab_settings_company_website" class="form-control" type="text" name="ab_settings_company_website" value="<?php echo esc_attr( get_option( 'ab_settings_company_website' ) ) ?>" /></td>
42
- </tr>
43
- <tr>
44
- <td></td>
45
- <td>
46
- <?php AB_Utils::submitButton() ?>
47
- <?php AB_Utils::resetButton( 'ab-settings-company-reset' ) ?>
48
- </td>
49
- </tr>
50
- </table>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <form method="post" action="<?php echo esc_url(add_query_arg('tab', 'company')) ?>" enctype="multipart/form-data"
3
+ class="ab-settings-form">
4
+ <div class="row">
5
+ <div class="col-xs-3 col-lg-2">
6
+ <div class="bookly-flexbox">
7
+ <div id="bookly-js-logo" class="bookly-thumb bookly-thumb-lg bookly-margin-right-lg">
8
+ <input type="hidden" name="ab_settings_company_logo_attachment_id" data-default="<?php echo esc_attr( get_option( 'ab_settings_company_logo_attachment_id' ) ) ?>"
9
+ value="<?php echo esc_attr( get_option( 'ab_settings_company_logo_attachment_id' ) ) ?>">
10
+ <div class="bookly-flex-cell">
11
+ <div class="form-group">
12
+ <?php $img = wp_get_attachment_image_src( get_option( 'ab_settings_company_logo_attachment_id' ), 'thumbnail' ) ?>
13
+ <div class="bookly-js-image bookly-thumb bookly-thumb-lg bookly-margin-right-lg"
14
+ data-style="<?php echo $img ? 'background-image: url(' . $img[0] . '); background-size: cover;' : '' ?>"
15
+ <?php echo $img ? 'style="background-image: url(' . $img[0] . '); background-size: cover;"' : '' ?>
16
+ >
17
+ <a class="dashicons dashicons-trash text-danger bookly-thumb-delete"
18
+ href="javascript:void(0)"
19
+ title="<?php _e( 'Delete', 'bookly' ) ?>"
20
+ <?php if ( ! $img ) : ?>style="display: none;"<?php endif ?>>
21
+ </a>
22
+ <div class="bookly-thumb-edit">
23
+ <div class="bookly-pretty">
24
+ <label class="bookly-pretty-indicator bookly-thumb-edit-btn">
25
+ <?php _e( 'Image', 'bookly' ) ?>
26
+ </label>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
  </div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ <div class="col-xs-9 col-lg-10">
36
+ <div class="bookly-flex-cell bookly-vertical-middle">
37
+ <?php \BooklyLite\Lib\Utils\Common::optionText( __( 'Company name', 'bookly' ), 'ab_settings_company_name' ) ?>
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="form-group">
43
+ <label for="ab_settings_company_address"><?php _e( 'Address', 'bookly' ) ?></label>
44
+ <textarea id="ab_settings_company_address" class="form-control" rows="5"
45
+ name="ab_settings_company_address"><?php echo esc_attr( get_option( 'ab_settings_company_address' ) ) ?></textarea>
46
+ </div>
47
+ <div class="form-group">
48
+ <?php \BooklyLite\Lib\Utils\Common::optionText( __( 'Phone', 'bookly' ), 'ab_settings_company_phone' ) ?>
49
+ </div>
50
+ <div class="form-group">
51
+ <?php \BooklyLite\Lib\Utils\Common::optionText( __( 'Website', 'bookly'