WordPress Online Booking and Scheduling Plugin – Bookly - Version 7.6.1

Version Description

  • Fixed disabling SMS and email notifications
Download this release

Release Info

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

Code changes from version 2.0.1 to 7.6.1

Files changed (119) hide show
  1. autoload.php +19 -0
  2. backend/AB_Backend.php +65 -183
  3. backend/modules/appearance/AB_AppearanceController.php +108 -37
  4. backend/modules/appearance/resources/css/appearance.css +25 -8
  5. backend/modules/appearance/resources/js/appearance.js +192 -85
  6. backend/modules/appearance/templates/_1_service.php +75 -133
  7. backend/modules/appearance/templates/_2_time.php +185 -101
  8. backend/modules/appearance/templates/_3_details.php +36 -42
  9. backend/modules/appearance/templates/_4_payment.php +38 -19
  10. backend/modules/appearance/templates/_5_done.php +3 -10
  11. backend/modules/appearance/templates/_card_payment.php +34 -0
  12. backend/modules/appearance/templates/_codes.php +16 -0
  13. backend/modules/appearance/templates/_progress_tracker.php +8 -7
  14. backend/modules/appearance/templates/index.php +84 -79
  15. backend/modules/appointments/AB_AppointmentsController.php +276 -0
  16. backend/modules/appointments/resources/js/ng-app.js +208 -0
  17. backend/modules/appointments/templates/index.php +79 -0
  18. backend/modules/calendar/AB_CalendarController.php +363 -549
  19. backend/modules/calendar/forms/AB_AppointmentForm.php +6 -7
  20. backend/modules/calendar/forms/AB_CustomerAppointmentForm.php +17 -0
  21. backend/modules/calendar/resources/css/calendar.css +68 -28
  22. backend/modules/calendar/resources/css/chosen.css +0 -437
  23. backend/modules/calendar/resources/css/fullcalendar.min.css +5 -0
  24. backend/modules/calendar/resources/css/jquery-ui-1.10.1.css +0 -1174
  25. backend/modules/calendar/resources/css/jquery.weekcalendar.css +0 -287
  26. backend/modules/calendar/resources/images/calendar1.png +0 -0
  27. backend/modules/calendar/resources/images/chosen-sprite.png +0 -0
  28. backend/modules/calendar/resources/images/information.png +0 -0
  29. backend/modules/calendar/resources/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  30. backend/modules/calendar/resources/images/user-white.png +0 -0
  31. backend/modules/calendar/resources/images/user.png +0 -0
  32. backend/modules/calendar/resources/js/calendar.js +350 -492
  33. backend/modules/calendar/resources/js/calendar_daypicker.js +0 -126
  34. backend/modules/calendar/resources/js/calendar_weekpicker.js +0 -168
  35. backend/modules/calendar/resources/js/chosen.jquery.js +0 -1229
  36. backend/modules/calendar/resources/js/fc-multistaff-view.js +160 -0
  37. backend/modules/calendar/resources/js/fullcalendar.min.js +8 -0
  38. backend/modules/calendar/resources/js/jquery.weekcalendar.js +0 -2951
  39. backend/modules/calendar/resources/js/ng-app.js +0 -379
  40. backend/modules/calendar/templates/_appointment_form.php +108 -0
  41. backend/modules/calendar/templates/_custom_fields_form.php +77 -0
  42. backend/modules/calendar/templates/appointment_form.php +0 -93
  43. backend/modules/calendar/templates/calendar.php +56 -121
  44. backend/modules/coupons/AB_CouponsController.php +43 -0
  45. backend/modules/coupons/resources/css/coupons.css +20 -0
  46. backend/modules/coupons/resources/js/coupons.js +5 -0
  47. backend/modules/coupons/templates/_list.php +4 -0
  48. backend/modules/coupons/templates/index.php +35 -0
  49. backend/modules/custom_fields/AB_CustomFieldsController.php +63 -0
  50. backend/modules/custom_fields/resources/css/custom_fields.css +86 -0
  51. backend/modules/custom_fields/resources/js/custom_fields.js +166 -0
  52. backend/modules/custom_fields/templates/index.php +134 -0
  53. backend/modules/customer/AB_CustomerController.php +161 -113
  54. backend/modules/customer/forms/AB_CustomerForm.php +9 -9
  55. backend/modules/customer/resources/css/customers.css +10 -0
  56. backend/modules/customer/resources/js/ng-app.js +344 -214
  57. backend/modules/customer/templates/_import.php +37 -0
  58. backend/modules/customer/templates/index.php +124 -94
  59. backend/modules/customer/templates/ng-new_customer_dialog.php +107 -46
  60. backend/modules/export/AB_ExportController.php +0 -123
  61. backend/modules/export/templates/index.php +0 -47
  62. backend/modules/notifications/AB_NotificationsController.php +56 -48
  63. backend/modules/notifications/forms/AB_NotificationsForm.php +166 -0
  64. backend/modules/notifications/resources/css/notifications.css +29 -0
  65. backend/modules/notifications/resources/js/notification.js +30 -0
  66. backend/modules/notifications/templates/_codes.php +24 -0
  67. backend/modules/notifications/templates/_codes_client_new_wp_user.php +12 -0
  68. backend/modules/notifications/templates/_codes_staff_agenda.php +4 -0
  69. backend/modules/notifications/templates/_tags_client_info.php +0 -48
  70. backend/modules/notifications/templates/_tags_evening_after.php +0 -40
  71. backend/modules/notifications/templates/_tags_evening_next_day.php +0 -40
  72. backend/modules/notifications/templates/_tags_event_next_day.php +0 -8
  73. backend/modules/notifications/templates/_tags_provider_info.php +0 -60
  74. backend/modules/notifications/templates/index.php +130 -115
  75. backend/modules/payment/AB_PaymentController.php +72 -47
  76. backend/modules/payment/templates/_alert.php +2 -2
  77. backend/modules/payment/templates/index.php +131 -63
  78. backend/modules/service/AB_ServiceController.php +188 -80
  79. backend/modules/service/forms/AB_CategoryForm.php +5 -5
  80. backend/modules/service/forms/AB_ServiceForm.php +18 -17
  81. backend/modules/service/resources/css/service.css +104 -13
  82. backend/modules/service/resources/js/service.js +311 -256
  83. backend/modules/service/templates/_list.php +177 -0
  84. backend/modules/service/templates/index.php +100 -55
  85. backend/modules/service/templates/list.php +0 -31
  86. backend/modules/service/templates/list_item.php +0 -96
  87. backend/modules/settings/AB_SettingsController.php +177 -133
  88. backend/modules/settings/forms/AB_BusinessHoursForm.php +28 -21
  89. backend/modules/settings/forms/AB_CompanyForm.php +26 -20
  90. backend/modules/settings/forms/AB_PaymentsForm.php +0 -19
  91. backend/modules/settings/resources/js/settings.js +66 -16
  92. backend/modules/settings/templates/_companyForm.php +24 -22
  93. backend/modules/settings/templates/_customers.php +48 -0
  94. backend/modules/settings/templates/_generalForm.php +87 -68
  95. backend/modules/settings/templates/_googleCalendarForm.php +110 -0
  96. backend/modules/settings/templates/_holidaysForm.php +10 -10
  97. backend/modules/settings/templates/_hoursForm.php +20 -20
  98. backend/modules/settings/templates/_paymentsForm.php +44 -48
  99. backend/modules/settings/templates/_purchaseCodeForm.php +0 -26
  100. backend/modules/settings/templates/_woocommerce.php +77 -0
  101. backend/modules/settings/templates/admin_notice.php +4 -3
  102. backend/modules/settings/templates/index.php +68 -30
  103. backend/modules/sms/AB_SmsController.php +46 -0
  104. backend/modules/sms/resources/css/flags.css +230 -0
  105. backend/modules/sms/resources/css/flags.png +0 -0
  106. backend/modules/sms/resources/css/sms.css +53 -0
  107. backend/modules/sms/resources/js/sms.js +6 -0
  108. backend/modules/sms/templates/_price.php +21 -0
  109. backend/modules/sms/templates/index.php +110 -0
  110. backend/modules/staff/AB_StaffController.php +335 -179
  111. backend/modules/staff/forms/AB_StaffMemberEditForm.php +40 -21
  112. backend/modules/staff/forms/AB_StaffMemberForm.php +30 -18
  113. backend/modules/staff/forms/AB_StaffMemberNewForm.php +3 -4
  114. backend/modules/staff/forms/AB_StaffScheduleForm.php +12 -18
  115. backend/modules/staff/forms/AB_StaffScheduleItemBreakForm.php +7 -8
  116. backend/modules/staff/forms/AB_StaffServicesForm.php +33 -44
  117. backend/modules/staff/forms/widget/AB_TimeChoiceWidget.php +43 -37
  118. backend/modules/staff/resources/css/staff.css +47 -18
  119. backend/modules/staff/resources/js/staff.js +59 -33
autoload.php ADDED
@@ -0,0 +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' );
backend/AB_Backend.php CHANGED
@@ -1,56 +1,39 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
-
5
- include 'modules/appearance/AB_AppearanceController.php';
6
- include 'modules/staff/AB_StaffController.php';
7
- include 'modules/service/AB_ServiceController.php';
8
- include 'modules/calendar/AB_CalendarController.php';
9
- include 'modules/payment/AB_PaymentController.php';
10
- include 'modules/notifications/AB_NotificationsController.php';
11
- include 'modules/settings/AB_SettingsController.php';
12
- include 'modules/customer/AB_CustomerController.php';
13
- include 'modules/tinymce/AB_TinyMCE_Plugin.php';
14
- include 'modules/export/AB_ExportController.php';
15
 
16
  class AB_Backend {
17
 
18
  public function __construct() {
19
  add_action( 'admin_menu', array( $this, 'addAdminMenu' ) );
20
- // Appearance
21
- $this->apearanceController = new AB_AppearanceController();
22
- // Staff members
23
- $this->staffController = new AB_StaffController();
24
- // Services
25
- $this->serviceController = new AB_ServiceController();
26
- // Calendar
27
- $this->calendarController = new AB_CalendarController();
28
- // Payments
29
- $this->paymentController = new AB_PaymentController();
30
- // Notifications
31
  $this->notificationsController = new AB_NotificationsController();
32
- // Settings
33
- $this->settingsController = new AB_SettingsController();
34
- // Customers
35
- $this->customerController = new AB_CustomerController();
36
- // Frontend booking ajax requests
37
- $this->bookingController = new AB_BookingController();
38
- // Export
39
- $this->exportController = new AB_ExportController();
 
 
 
40
 
41
- add_action( 'wp_loaded', array( $this, 'init' ) );
42
- add_action( 'admin_init', array( $this, 'addTinyMCEPlugin' ) );
43
- add_action( 'admin_notices', array( $this->settingsController, 'showAdminNotice' ) );
44
  }
45
 
46
  public function addTinyMCEPlugin() {
47
- /** @var WP_User $current_user */
48
- global $current_user;
49
  new AB_TinyMCE_Plugin();
50
  }
51
 
52
  public function init() {
53
- if ( !session_id() ) {
54
  @session_start();
55
  }
56
 
@@ -61,169 +44,68 @@ class AB_Backend {
61
  break;
62
  }
63
  }
64
-
65
- // for Appearance\Services\Settings all CSS and JS must be located directly in <HEAD>
66
- if ( isset( $_GET[ 'page' ] ) && $_GET[ 'page' ] == 'ab-system-appearance' ) { // Appearance
67
- // include StyleSheets
68
- //wp_enqueue_style( 'ab-reset', plugins_url( 'css/ab-reset.css', dirname(__FILE__). '../../frontend/resources/AB_Frontend.php' ) );
69
- wp_enqueue_style( 'ab-style', plugins_url( 'resources/css/ab_style.css', __FILE__ ) );
70
- wp_enqueue_style( 'ab-bootstrap', plugins_url( 'resources/bootstrap/css/bootstrap.min.css', __FILE__ ) );
71
- wp_enqueue_style( 'ab-bootstrap-editable', plugins_url( 'resources/bootstrap/css/bootstrap-editable.css', __FILE__ ) );
72
- wp_enqueue_style( 'ab-ladda-themeless', plugins_url( 'css/ladda-themeless.min.css',
73
- dirname(__FILE__). '../../frontend/resources/AB_Frontend.php' ) );
74
- wp_enqueue_style( 'ab-ladda-min', plugins_url( 'css/ladda.min.css',
75
- dirname(__FILE__). '../../frontend/resources/AB_Frontend.php' ) );
76
- wp_enqueue_style( 'ab-frontend-style', plugins_url( 'css/ab_frontend_style.css',
77
- dirname(__FILE__). '../../frontend/resources/AB_Frontend.php' ) );
78
- wp_enqueue_style( 'ab-columnizer', plugins_url( 'css/ab-columnizer.css',
79
- dirname(__FILE__). '../../frontend/resources/AB_Frontend.php' ) );
80
- wp_enqueue_style( 'ab-pickadate', plugins_url( 'css/pickadate.classic.css',
81
- dirname(__FILE__). '../../frontend/resources/AB_Frontend.php' ) );
82
- wp_enqueue_style( 'ab-columnizer', plugins_url( 'resources/css/ab-columnizer.css',
83
- dirname(__FILE__). '../../frontend/resources/AB_Frontend.php' ) );
84
- wp_enqueue_style( 'wp-color-picker' );
85
- wp_enqueue_style( 'ab-appearance', plugins_url( 'modules/appearance/resources/css/appearance.css', __FILE__ ) );
86
- // include JavaScript
87
- wp_enqueue_script( 'ab-bootstrap', plugins_url( 'resources/bootstrap/js/bootstrap.min.js',
88
- __FILE__ ), array( 'jquery' ) );
89
- wp_enqueue_script( 'ab-bootstrap-editable', plugins_url( 'resources/bootstrap/js/bootstrap-editable.min.js',
90
- __FILE__ ), array( 'jquery' ) );
91
- wp_enqueue_script( 'ab-appearance',
92
- plugins_url( 'modules/appearance/resources/js/appearance.js', __FILE__ ),
93
- array( 'jquery' )
94
- );
95
- wp_enqueue_script( 'ab-pickadate', plugins_url( 'js/pickadate.legacy.min.js',
96
- dirname(__FILE__). '../../frontend/resources/AB_Frontend.php' ), array( 'jquery' ) );
97
- wp_localize_script( 'ab-pickadate', 'BooklyL10n', array(
98
- 'today' => __( 'Today', 'ab' ),
99
- 'month' => array(
100
- 'January' => __( 'January', 'ab' ),
101
- 'February' => __( 'February', 'ab' ),
102
- 'March' => __( 'March', 'ab' ),
103
- 'April' => __( 'April', 'ab' ),
104
- 'May' => __( 'May', 'ab' ),
105
- 'June' => __( 'June', 'ab' ),
106
- 'July' => __( 'July', 'ab' ),
107
- 'August' => __( 'August', 'ab' ),
108
- 'September' => __( 'September', 'ab' ),
109
- 'October' => __( 'October', 'ab' ),
110
- 'November' => __( 'November', 'ab' ),
111
- 'December' => __( 'December', 'ab' )
112
- ),
113
- 'day' => array(
114
- 'Sun' => __( 'Sun', 'ab' ),
115
- 'Mon' => __( 'Mon', 'ab' ),
116
- 'Tue' => __( 'Tue', 'ab' ),
117
- 'Wed' => __( 'Wed', 'ab' ),
118
- 'Thu' => __( 'Thu', 'ab' ),
119
- 'Fri' => __( 'Fri', 'ab' ),
120
- 'Sat' => __( 'Sat', 'ab' )
121
- )
122
- ) );
123
- wp_enqueue_script( 'wp-color-picker' );
124
- } elseif ( isset( $_GET[ 'page' ] ) && $_GET[ 'page' ] == 'ab-system-services' ) { // Services
125
- // include StyleSheets
126
- wp_enqueue_style( 'wp-color-picker' );
127
- wp_enqueue_style( 'ab-style', plugins_url( 'resources/css/ab_style.css', __FILE__ ) );
128
- wp_enqueue_style( 'ab-service', plugins_url( 'modules/service/resources/css/service.css', __FILE__ ) );
129
- wp_enqueue_style( 'ab-bootstrap', plugins_url( 'resources/bootstrap/css/bootstrap.min.css', __FILE__ ) );
130
- // include JavaScript
131
- wp_enqueue_script( 'wp-color-picker' );
132
- wp_enqueue_script( 'ab-popup', plugins_url( 'resources/js/ab_popup.js', __FILE__ ), array( 'jquery' ) );
133
- wp_enqueue_script( 'ab-bootstrap', plugins_url( 'resources/bootstrap/js/bootstrap.min.js', __FILE__ ), array( 'jquery' ) );
134
- wp_enqueue_script( 'ab-service', plugins_url( 'modules/service/resources/js/service.js', __FILE__ ), array( 'jquery' ) );
135
- wp_localize_script( 'ab-service', 'BooklyL10n', array(
136
- 'are_you_sure' => __( 'Are you sure?', 'ab' ),
137
- 'please_select_at_least_one_service' => __( 'Please select at least one service.', 'ab'),
138
- ) );
139
- } elseif ( isset( $_GET[ 'page' ] ) && $_GET[ 'page' ] == 'ab-system-settings' ) { // Settings
140
- // include StyleSheets
141
- wp_enqueue_style( 'ab-style', plugins_url( 'resources/css/ab_style.css', __FILE__ ) );
142
- wp_enqueue_style( 'ab-bootstrap', plugins_url( 'resources/bootstrap/css/bootstrap.min.css', __FILE__ ) );
143
- wp_enqueue_style( 'ab-jCal', plugins_url( 'resources/css/jCal.css', __FILE__ ) );
144
- // include JavaScript
145
- wp_enqueue_script( 'ab-bootstrap', plugins_url( 'resources/bootstrap/js/bootstrap.min.js', __FILE__ ), array( 'jquery' ) );
146
- wp_enqueue_script( 'ab-settings', plugins_url( 'modules/settings/resources/js/settings.js', __FILE__ ), array( 'jquery' ) );
147
- wp_enqueue_script( 'ab-jCal', plugins_url( 'resources/js/jCal.js', __FILE__ ), array( 'jquery' ) );
148
- wp_localize_script( 'ab-jCal', 'BooklyL10n', array(
149
- 'we_are_not_working' => __( 'We are not working on this day', 'ab' ),
150
- 'repeat' => __( 'Repeat every year', 'ab' ),
151
- 'month' => array(
152
- 'January' => __( 'January', 'ab' ),
153
- 'February' => __( 'February', 'ab' ),
154
- 'March' => __( 'March', 'ab' ),
155
- 'April' => __( 'April', 'ab' ),
156
- 'May' => __( 'May', 'ab' ),
157
- 'June' => __( 'June', 'ab' ),
158
- 'July' => __( 'July', 'ab' ),
159
- 'August' => __( 'August', 'ab' ),
160
- 'September' => __( 'September', 'ab' ),
161
- 'October' => __( 'October', 'ab' ),
162
- 'November' => __( 'November', 'ab' ),
163
- 'December' => __( 'December', 'ab' )
164
- ),
165
- 'day' => array(
166
- 'Mon' => __( 'Mon', 'ab' ),
167
- 'Tue' => __( 'Tue', 'ab' ),
168
- 'Wed' => __( 'Wed', 'ab' ),
169
- 'Thu' => __( 'Thu', 'ab' ),
170
- 'Fri' => __( 'Fri', 'ab' ),
171
- 'Sat' => __( 'Sat', 'ab' ),
172
- 'Sun' => __( 'Sun', 'ab' )
173
- )
174
- ) );
175
- }
176
  }
177
 
178
  public function addAdminMenu() {
179
- /** @var wpdb $wpdb */
180
- global $wpdb;
181
  /** @var WP_User $current_user */
182
  global $current_user;
183
 
184
- // translated submenu pages
185
- $calendar = __( 'Calendar', 'ab' );
186
- $staff_members = __( 'Staff members', 'ab' );
187
- $services = __( 'Services', 'ab' );
188
- $customers = __( 'Customers', 'ab' );
189
- $notifications = __( 'Notifications', 'ab' );
190
- $payments = __( 'Payments', 'ab' );
191
- $appearance = __( 'Appearance', 'ab' );
192
- $settings = __( 'Settings', 'ab' );
193
- $export = __( 'Export', 'ab' );
 
 
 
194
 
195
- if ( in_array( 'administrator', $current_user->roles )
196
- || $wpdb->get_var( $wpdb->prepare(
197
- 'SELECT COUNT(id) AS numb FROM ab_staff WHERE wp_user_id = %d', $current_user->ID
198
- ) ) ) {
199
  if ( function_exists( 'add_options_page' ) ) {
200
- $dynamic_position = '80.0000001' . mt_rand( 1, 1000 ); // position always is under `Settings`
201
- add_menu_page( 'Bookly', 'Bookly', 'read', 'ab-system',
202
- array( $this->staffController, 'renderStaffMembers'),
203
  plugins_url('resources/images/menu.png', __FILE__), $dynamic_position );
204
- add_submenu_page( 'ab-system', $calendar, $calendar, 'read', 'ab-system-calendar',
205
- array( $this->calendarController, 'renderCalendar' ) );
206
- add_submenu_page( 'ab-system', $staff_members, $staff_members, 'manage_options', 'ab-system-staff',
207
- array( $this->staffController, 'renderStaffMembers' ) );
208
- add_submenu_page( 'ab-system', $services, $services, 'manage_options', 'ab-system-services',
 
 
 
 
 
 
 
 
 
209
  array( $this->serviceController, 'index' ) );
210
- add_submenu_page( 'ab-system', $customers, $customers, 'manage_options', 'ab-system-customers',
211
  array( $this->customerController, 'index' ) );
212
- add_submenu_page( 'ab-system', $notifications, $notifications, 'manage_options', 'ab-system-notifications',
213
  array( $this->notificationsController, 'index' ) );
214
- add_submenu_page( 'ab-system', $payments, $payments, 'manage_options', 'ab-system-payments',
 
 
215
  array( $this->paymentController, 'index' ) );
216
- add_submenu_page( 'ab-system', $appearance, $appearance, 'manage_options', 'ab-system-appearance',
217
  array( $this->apearanceController, 'index' ) );
218
- add_submenu_page( 'ab-system', $settings, $settings, 'manage_options', 'ab-system-settings',
 
 
 
 
219
  array( $this->settingsController, 'index' ) );
220
- add_submenu_page( 'ab-system', $export, $export, 'manage_options', 'ab-system-export',
221
- array( $this->exportController, 'index' ) );
222
 
223
  global $submenu;
 
224
  unset( $submenu[ 'ab-system' ][ 0 ] );
225
  }
226
  }
227
  }
228
 
229
- }
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
 
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/modules/appearance/AB_AppearanceController.php CHANGED
@@ -1,6 +1,4 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
 
5
  /**
6
  * Class AB_AppearanceController
@@ -10,8 +8,66 @@ class AB_AppearanceController extends AB_Controller {
10
  /**
11
  * Default Action
12
  */
13
- public function index() {
14
- // initialize steps (tabs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  $this->steps = array(
16
  1 => get_option( 'ab_appearance_text_step_service' ),
17
  2 => get_option( 'ab_appearance_text_step_time' ),
@@ -20,62 +76,77 @@ class AB_AppearanceController extends AB_Controller {
20
  5 => get_option( 'ab_appearance_text_step_done' )
21
  );
22
 
23
- // render general layout
24
  $this->render( 'index' );
25
  } // index
26
 
27
  /**
28
  * Update options
29
  */
30
- public function executeUpdateAppearanceOptions() {
31
- if ( count( $this->getPost() ) ) {
 
32
  $get_option = $this->getParameter( 'options' );
33
  $options = array(
34
  // Info text.
35
- 'ab_appearance_text_info_first_step' => $get_option[ 'text_info_first_step' ],
36
- 'ab_appearance_text_info_second_step' => $get_option[ 'text_info_second_step' ],
37
- 'ab_appearance_text_info_third_step' => $get_option[ 'text_info_third_step' ],
38
- 'ab_appearance_text_info_fourth_step' => $get_option[ 'text_info_fourth_step' ],
39
- 'ab_appearance_text_info_fifth_step' => $get_option[ 'text_info_fifth_step' ],
 
 
40
  // Color.
41
- 'ab_appearance_color' => $get_option[ 'color' ],
42
  // Step, label and option texts.
43
- 'ab_appearance_text_step_service' => $get_option[ 'text_step_service' ],
44
- 'ab_appearance_text_step_time' => $get_option[ 'text_step_time' ],
45
- 'ab_appearance_text_step_details' => $get_option[ 'text_step_details' ],
46
- 'ab_appearance_text_step_payment' => $get_option[ 'text_step_payment' ],
47
- 'ab_appearance_text_step_done' => $get_option[ 'text_step_done' ],
48
- 'ab_appearance_text_label_category' => $get_option[ 'text_label_category' ],
49
- 'ab_appearance_text_label_service' => $get_option[ 'text_label_service' ],
50
- 'ab_appearance_text_label_employee' => $get_option[ 'text_label_employee' ],
51
- 'ab_appearance_text_label_select_date' => $get_option[ 'text_label_select_date' ],
52
- 'ab_appearance_text_label_start_from' => $get_option[ 'text_label_start_from' ],
53
- 'ab_appearance_text_label_finish_by' => $get_option[ 'text_label_finish_by' ],
54
- 'ab_appearance_text_label_name' => $get_option[ 'text_label_name' ],
55
- 'ab_appearance_text_label_phone' => $get_option[ 'text_label_phone' ],
56
- 'ab_appearance_text_label_email' => $get_option[ 'text_label_email' ],
57
- 'ab_appearance_text_label_notes' => $get_option[ 'text_label_notes' ],
58
- 'ab_appearance_text_option_service' => $get_option[ 'text_option_service' ],
59
- 'ab_appearance_text_option_category' => $get_option[ 'text_option_category' ],
60
- 'ab_appearance_text_option_employee' => $get_option[ 'text_option_employee' ],
 
 
61
  // Checkboxes.
62
- 'ab_appearance_show_progress_tracker' => $get_option[ 'progress_tracker' ],
 
 
 
63
  );
64
 
65
  // Save options.
66
  foreach ( $options as $option_name => $option_value ) {
67
  update_option( $option_name, $option_value );
 
 
 
 
68
  }
69
  }
70
  exit;
71
- } // executeUpdateAppearanceOptions
72
 
73
  /**
74
  * Override parent method to add 'wp_ajax_ab_' prefix
75
  * so current 'execute*' methods look nicer.
 
 
76
  */
77
- protected function registerWpActions( $prefix = '' ) {
 
78
  parent::registerWpActions( 'wp_ajax_ab_' );
79
- } // registerWpActions
80
 
81
- } // AB_AppearanceController
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
2
 
3
  /**
4
  * Class AB_AppearanceController
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' ),
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/resources/css/appearance.css CHANGED
@@ -1,5 +1,5 @@
1
  /* Backend-Appearance */
2
-
3
  div.controls {
4
  float: right;
5
  margin-right: 15px;
@@ -11,15 +11,32 @@ span.spinner {
11
  margin-right: 20px;
12
  }
13
 
14
- div.ab-second-step-buttons div.ab-first-step-buttons div.ab-third-step-buttons div.ab-fourth-step-buttons div.ab-fifth-step-buttons {
15
- float: left;
16
- margin-left: 395px;
 
 
 
17
  }
18
 
19
- div.ab-second-step-buttons input.ab-back-forth-button {
20
- /* styles */
 
 
 
 
 
 
21
  }
22
 
23
- div.ab-second-step-options {
24
- /* styles */
 
 
 
 
 
 
 
 
25
  }
1
  /* Backend-Appearance */
2
+ #ab-appearance { max-width: 960px; }
3
  div.controls {
4
  float: right;
5
  margin-right: 15px;
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,12 +1,16 @@
1
  jQuery(function($) {
2
- var // Progress Tracker
3
  $progress_tracker_option = $('input#ab-progress-tracker-checkbox'),
4
- // Tabs
 
 
 
 
5
  $tabs = $('div.tabbable').find('.nav-tabs'),
6
  $tab_content = $('div.tab-content'),
7
  // Buttons.
8
- $update_button = $('#update_button'),
9
- $reset_button = $('#reset_button'),
10
  // Texts.
11
  $text_step_service = $('#ab-text-step-service'),
12
  $text_step_time = $('#ab-text-step-time'),
@@ -18,6 +22,7 @@ jQuery(function($) {
18
  $text_option_service = $('#ab-text-option-service'),
19
  $text_option_employee = $('#ab-text-option-employee'),
20
  $text_label_service = $('#ab-text-label-service'),
 
21
  $text_label_employee = $('#ab-text-label-employee'),
22
  $text_label_select_date = $('#ab-text-label-select_date'),
23
  $text_label_start_from = $('#ab-text-label-start_from'),
@@ -25,22 +30,40 @@ jQuery(function($) {
25
  $text_label_name = $('#ab-text-label-name'),
26
  $text_label_phone = $('#ab-text-label-phone'),
27
  $text_label_email = $('#ab-text-label-email'),
28
- $text_label_notes = $('#ab-text-label-notes'),
29
  $text_info_service = $('#ab-text-info-first'),
30
  $text_info_time = $('#ab-text-info-second'),
31
  $text_info_details = $('#ab-text-info-third'),
 
32
  $text_info_payment = $('#ab-text-info-fourth'),
33
  $text_info_done = $('#ab-text-info-fifth'),
 
34
  $color_picker = $('.wp-color-picker'),
35
- $ab_editable = $('.ab_editable');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  // menu fix for WP 3.8.1
38
  $('#toplevel_page_ab-system > ul').css('margin-left', '0px');
39
 
40
- // Tabs
41
  $tabs.find('.ab-step-tabs').on('click', function() {
42
  var $step_id = $(this).data('step-id');
43
- // hide all other tab content and show only current
44
  $tab_content.children('div[data-step-id!="' + $step_id + '"]').removeClass('active').hide();
45
  $tab_content.children('div[data-step-id="' + $step_id + '"]').addClass('active').show();
46
  }).filter('li:first').trigger('click');
@@ -53,21 +76,19 @@ jQuery(function($) {
53
  $('.ab-mobile-step_1 label').css('color', $color_picker.wpColorPicker('color'));
54
  $('.ab-next-step, .ab-mobile-next-step').css('background', $color_picker.wpColorPicker('color'));
55
  $('.ab-week-days label').css('background-color', $color_picker.wpColorPicker('color'));
56
- $('.pickadate__calendar').attr('style', 'background: ' + color_important);
57
- $('.pickadate__header').attr('style', 'border-bottom: ' + '1px solid ' + color_important);
58
- // $('.pickadate__nav--next, .pickadate__nav--prev').attr('style', 'border-left: 6px solid ' + color_important);
59
- // $('.pickadate__nav--next:before').attr('style', 'border-left: 6px solid ' + color_important);
60
- // $('.pickadate__nav--prev:before').attr('style', 'border-right: 6px solid ' + color_important);
61
- // $('.pickadate__day:hover').attr('style', 'color: ' + color_important);
62
- // $('.pickadate__day--selected:hover').attr('style', '');
63
- $('.pickadate__day--selected').attr('style', 'color: ' + color_important);
64
- $('.pickadate__button--clear').attr('style', 'color: ' + color_important);
65
- $('.pickadate__button--today').attr('style', 'color: ' + color_important);
66
  $('.ab-columnizer .ab-available-day').css({
67
  'background': $color_picker.wpColorPicker('color'),
68
  'border-color': $color_picker.wpColorPicker('color')
69
  });
70
- $('.ab-time-no-resize').css('background-color', $color_picker.wpColorPicker('color'));
71
  $('.ab-columnizer .ab-available-hour').off().hover(
72
  function() { // mouse-on
73
  $(this).css({
@@ -85,7 +106,7 @@ jQuery(function($) {
85
  function() { // mouse-out
86
  $(this).css({
87
  'color': '#333333',
88
- 'border': '1px solid ' + '#cccccc'
89
  });
90
  $(this).find('.ab-hour-icon').css({
91
  'border-color': '#333333',
@@ -96,66 +117,112 @@ jQuery(function($) {
96
  });
97
  }
98
  );
99
- $('div.ab-details-list > label.ab-formLabel').css('color', $color_picker.wpColorPicker('color'));
100
  $('.ab-to-second-step, .ab-to-fourth-step, .ab-to-third-step, .ab-final-step')
101
  .css('background', $color_picker.wpColorPicker('color'));
102
  };
103
  $color_picker.wpColorPicker({
104
  change : function() {
105
  applyColor();
 
 
 
 
106
  }
107
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
- $('.ab-requested-date-from').pickadate({
110
- dateMin: true,
111
- clear: false,
112
- onRender: function() {
113
- applyColor();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  }
 
 
115
  });
 
 
116
 
117
  // Update options.
118
- $update_button.on('click', function() {
 
119
  var data = {
120
  action: 'ab_update_appearance_options',
121
  options: {
122
  // Color.
123
- 'color' : $color_picker.wpColorPicker('color'),
124
  // Info text.
125
- 'text_info_first_step' : $.trim($text_info_service.text()),
126
- 'text_info_second_step' : $.trim($text_info_time.text()),
127
- 'text_info_third_step' : $.trim($text_info_details.text()),
128
- 'text_info_fourth_step' : $.trim($text_info_payment.text()),
129
- 'text_info_fifth_step' : $.trim($text_info_done.text()),
 
 
130
  // Step and label texts.
131
- 'text_step_service' : $.trim($text_step_service.text()),
132
- 'text_step_time' : $.trim($text_step_time.text()),
133
- 'text_step_details' : $.trim($text_step_details.text()),
134
- 'text_step_payment' : $.trim($text_step_payment.text()),
135
- 'text_step_done' : $.trim($text_step_done.text()),
136
- 'text_label_category' : $.trim($text_label_category.text()),
137
- 'text_label_service' : $.trim($text_label_service.text()),
138
- 'text_label_employee' : $.trim($text_label_employee.text()),
139
- 'text_label_select_date' : $.trim($text_label_select_date.text()),
140
- 'text_label_start_from' : $.trim($text_label_start_from.text()),
141
- 'text_label_finish_by' : $.trim($text_label_finish_by.text()),
142
- 'text_label_name' : $.trim($text_label_name.text()),
143
- 'text_label_phone' : $.trim($text_label_phone.text()),
144
- 'text_label_email' : $.trim($text_label_email.text()),
145
- 'text_label_notes' : $.trim($text_label_notes.text()),
146
- 'text_option_category' : $.trim($text_option_category.text()),
147
- 'text_option_service' : $.trim($text_option_service.text()),
148
- 'text_option_employee' : $.trim($text_option_employee.text()),
 
 
149
  // Checkboxes.
150
- 'progress_tracker' : Number($('#ab-progress-tracker-checkbox').is(':checked'))
 
 
 
151
  } // options
152
  }; // data
153
 
154
  // update data and show spinner while updating
155
- $('#update_spinner').show();
 
156
  $.post(ajaxurl, data, function (response) {
157
- $('#update_spinner').hide();
158
- $('.alert').show();
159
  });
160
  });
161
 
@@ -199,6 +266,50 @@ jQuery(function($) {
199
  $(this).is(':checked') ? $('div.ab-progress-tracker').show() : $('div.ab-progress-tracker').hide();
200
  }).trigger('change');
201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  // Clickable week-days.
203
  $('.ab-week-day').on('change', function () {
204
  var self = $(this);
@@ -209,32 +320,11 @@ jQuery(function($) {
209
  }
210
  });
211
 
212
- /**
213
- * Helper functions.
214
- */
215
- function nl2br(str, is_xhtml) {
216
- var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>';
217
- return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2');
218
- }
219
- function escapeHtml(string) {
220
- var entityMap = {
221
- "&": "&amp;",
222
- "<": "&lt;",
223
- ">": "&gt;",
224
- '"': '&quot;',
225
- "'": '&#39;',
226
- "/": '&#x2F;'
227
- };
228
- return String(string).replace(/[&<>"'\/]/g, function (s) {
229
- return entityMap[s];
230
- });
231
- }
232
-
233
  var multiple = function (options) {
234
  this.init('multiple', options, multiple.defaults);
235
  };
236
 
237
- //inherit from Abstract input
238
  $.fn.editableutils.inherit(multiple, $.fn.editabletypes.abstractinput);
239
 
240
  $.extend(multiple.prototype, {
@@ -274,8 +364,8 @@ jQuery(function($) {
274
  });
275
 
276
  multiple.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
277
- tpl: '<div class="editable-multiple"><label><input type="text" name="label" class="input-medium"></label></div>'+
278
- '<div style="margin-top:5px;" class="editable-multiple"><label><input type="text" name="option" class="input-medium"><input type="hidden" name="id_option"></label></div>',
279
 
280
  inputclass: ''
281
  });
@@ -286,26 +376,26 @@ jQuery(function($) {
286
  value: {
287
  label: $text_label_category.text(),
288
  option: $text_option_category.text(),
289
- id_option: $text_label_category.data('link-class')
290
  }
291
  });
292
  $text_label_service.editable({
293
  value: {
294
  label: $text_label_service.text(),
295
  option: $text_option_service.text(),
296
- id_option: $text_label_service.data('link-class')
297
  }
298
  });
299
  $text_label_employee.editable({
300
  value: {
301
  label: $text_label_employee.text(),
302
  option: $text_option_employee.text(),
303
- id_option: $text_label_employee.data('link-class')
304
  }
305
  });
306
 
307
- $('#ab-text-info-first').add('#ab-text-info-second').add('#ab-text-info-third').add('#ab-text-info-fourth').add('#ab-text-info-fifth').editable({placement: 'right', emptytext: ''});
308
- $ab_editable.editable({emptytext: ''});
309
 
310
  $.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>';
311
 
@@ -317,4 +407,21 @@ jQuery(function($) {
317
  $("span[data-link-class='" + $(e.target).data('link-class') + "']").editable('setValue', params.newValue);
318
  $("span." + $(e.target).data('link-class')).text(params.newValue);
319
  });
320
- }); // jQuery
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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'),
22
  $text_option_service = $('#ab-text-option-service'),
23
  $text_option_employee = $('#ab-text-option-employee'),
24
  $text_label_service = $('#ab-text-label-service'),
25
+ $text_label_number_of_persons = $('#ab-text-label-number-of-persons'),
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'),
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');
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);
80
+ $('.picker__header').attr('style', 'border-bottom: ' + '1px solid ' + color_important);
81
+ $('.picker__day').mouseenter(function(){
82
+ $(this).attr('style', 'color: ' + color_important);
83
+ }).mouseleave(function(){ $(this).attr('style', $(this).hasClass('picker__day--selected') ? 'color: ' + color_important : '') });
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({
106
  function() { // mouse-out
107
  $(this).css({
108
  'color': '#333333',
109
+ 'border': '1px solid #cccccc'
110
  });
111
  $(this).find('.ab-hour-icon').css({
112
  'border-color': '#333333',
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({
135
+ formatSubmit : 'yyyy-mm-dd',
136
+ format : BooklyL10n.date_format,
137
+ min : true,
138
+ clear : false,
139
+ close : false,
140
+ today : BooklyL10n.today,
141
+ weekdaysShort : BooklyL10n.days,
142
+ monthsFull : BooklyL10n.months,
143
+ labelMonthNext : BooklyL10n.nextMonth,
144
+ labelMonthPrev : BooklyL10n.prevMonth,
145
+ onRender : applyColor,
146
+ firstDay : BooklyL10n.start_of_week == 1
147
+ });
148
 
149
+ $second_step_calendar.pickadate({
150
+ formatSubmit : 'yyyy-mm-dd',
151
+ format : BooklyL10n.date_format,
152
+ min : true,
153
+ weekdaysShort : BooklyL10n.days,
154
+ monthsFull : BooklyL10n.months,
155
+ labelMonthNext : BooklyL10n.nextMonth,
156
+ labelMonthPrev : BooklyL10n.prevMonth,
157
+ close : false,
158
+ clear : false,
159
+ today : false,
160
+ closeOnSelect : false,
161
+ onRender : applyColor,
162
+ firstDay : BooklyL10n.start_of_week == 1,
163
+ klass : {
164
+ picker: 'picker picker--opened picker--focused'
165
+ },
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'));
174
 
175
  // Update options.
176
+ $save_button.on('click', function(e) {
177
+ e.preventDefault();
178
  var data = {
179
  action: 'ab_update_appearance_options',
180
  options: {
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
 
220
  // update data and show spinner while updating
221
+ var ladda = Ladda.create(this);
222
+ ladda.start();
223
  $.post(ajaxurl, data, function (response) {
224
+ ladda.stop();
225
+ $('.notice-success').show();
226
  });
227
  });
228
 
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.
302
+ $day_one_column_option.change(function() {
303
+ if (this.checked) {
304
+ day_one_column.show();
305
+ day_columns.hide();
306
+ } else {
307
+ day_one_column.hide();
308
+ day_columns.show();
309
+ }
310
+ clickTwoStep();
311
+ });
312
+
313
  // Clickable week-days.
314
  $('.ab-week-day').on('change', function () {
315
  var self = $(this);
320
  }
321
  });
322
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  var multiple = function (options) {
324
  this.init('multiple', options, multiple.defaults);
325
  };
326
 
327
+ // Inherit from Abstract input.
328
  $.fn.editableutils.inherit(multiple, $.fn.editabletypes.abstractinput);
329
 
330
  $.extend(multiple.prototype, {
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
  });
376
  value: {
377
  label: $text_label_category.text(),
378
  option: $text_option_category.text(),
379
+ id_option: $text_label_category.data('option-id')
380
  }
381
  });
382
  $text_label_service.editable({
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
 
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
backend/modules/appearance/templates/_1_service.php CHANGED
@@ -1,21 +1,23 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
-
3
- <div class="ab-booking-form" style="overflow: hidden">
 
 
4
 
5
  <!-- Progress Tracker-->
6
  <?php $step = 1; include '_progress_tracker.php'; ?>
7
 
8
- <div class="ab-wrapper-content">
9
- <div style="margin-bottom: 15px!important;" class="ab-row-fluid">
10
- <span data-inputclass="input-xxlarge" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_first_step' ) ); ?>" data-link-class="ab-text-info-first" class="ab-text-info-first-preview ab-bold ab_editable" id="ab-text-info-first" data-rows="7" data-type="textarea" data-pk="1"><?php echo esc_html( get_option( 'ab_appearance_text_info_first_step' ) ) ?></span>
11
  </div>
12
  <div class=ab-service-form>
13
- <div class="ab-mobile-step_1 ab-row-fluid">
14
- <div class="ab-category-list ab-left">
15
- <label data-default="<?php echo get_option( 'ab_appearance_text_label_category' ); ?>" data-link-class="ab-text-option-category" class="ab-category-title text_category_label" id="ab-text-label-category" data-type="multiple" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_label_category' )) ?></label>
16
- <div class="ab-select-wrap">
17
- <select class="select-list ab-select-mobile ab-select-category" style="width: 100%">
18
- <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_attr( get_option( 'ab_appearance_text_option_category' ) ); ?></option>
19
  <option value="1">Cosmetic Dentistry</option>
20
  <option value="2">Invisalign</option>
21
  <option value="3">Orthodontics</option>
@@ -23,11 +25,11 @@
23
  </select>
24
  </div>
25
  </div>
26
- <div class="ab-category-list ab-category-list-center ab-left">
27
- <label data-default="<?php echo get_option( 'ab_appearance_text_label_service' ); ?>" data-link-class="ab-text-option-service" class="ab-category-title text_service_label" id="ab-text-label-service" data-type="multiple" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_label_service' )) ?></label>
28
- <div class="ab-select-wrap">
29
- <select class="select-list ab-select-mobile ab-select-service" style="width: 100%">
30
- <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_attr( get_option( 'ab_appearance_text_option_service' ) ); ?></option>
31
  <option value="1">Crown and Bridge</option>
32
  <option value="2">Teeth Whitening</option>
33
  <option value="3">Veneers</option>
@@ -39,11 +41,11 @@
39
  </select>
40
  </div>
41
  </div>
42
- <div class="ab-category-list ab-left">
43
- <label data-default="<?php echo get_option( 'ab_appearance_text_label_employee' ); ?>" data-link-class="ab-text-option-employee" class="ab-category-title text_employee_label" id="ab-text-label-employee" data-type="multiple" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_label_employee' )) ?></label>
44
- <div class="ab-select-wrap">
45
- <select class="select-list ab-select-mobile ab-select-employee" style="width: 100%">
46
- <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_attr( get_option( 'ab_appearance_text_option_employee' ) ); ?></option>
47
  <option value="1">Nick Knight</option>
48
  <option value="2">Jane Howard</option>
49
  <option value="3">Ashley Stamp</option>
@@ -57,139 +59,79 @@
57
  </select>
58
  </div>
59
  </div>
60
- <button class="ab-right ab-mobile-next-step ladda-button orange zoom-in" onclick="return false">
61
- <span><?php _e( 'Next', 'ab' ) ?></span>
 
 
 
 
 
 
 
 
 
 
 
62
  </button>
63
  </div>
64
  <div class="ab-mobile-step_2">
65
  <div class="ab-row-fluid">
66
- <div class="ab-left ab-available-date">
67
- <label data-default="<?php echo get_option( 'ab_appearance_text_label_select_date' ); ?>" data-link-class="text_select_date_label" class="text_select_date_label ab_editable" id="ab-text-label-select_date" data-type="text" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_label_select_date' )) ?></label>
68
- <div class="ab-input-wrap">
69
- <span class="ab-requested-date-wrap">
70
- <input style="height: 30px!important;" class="ab-requested-date-from select-list" type="text" value="29 November, 2013">
71
  </span>
72
  </div>
73
  </div>
74
- <div class="ab-left ab-available-days">
75
  <ul class="ab-week-days">
76
- <li>
77
- <div class="ab-bold"><?php _e('Sun', 'ab' ) ?></div>
78
- <!-- #11055: all days are checked by default -->
79
- <label class="active">
80
- <input class="ab-week-day ab-week-day-1" value="1" checked="checked" type="checkbox">
81
- </label>
82
- </li>
83
- <li>
84
- <div class="ab-bold"><?php _e( 'Mon', 'ab' ) ?></div>
85
- <!-- #11055: all days are checked by default -->
86
- <label class="active">
87
- <input class="ab-week-day ab-week-day-2" value="2" checked="checked" type="checkbox">
88
- </label>
89
- </li>
90
- <li>
91
- <div class="ab-bold"><?php _e( 'Tue', 'ab' ) ?></div>
92
- <!-- #11055: all days are checked by default -->
93
- <label class="active">
94
- <input class="ab-week-day ab-week-day-3" value="3" checked="checked" type="checkbox">
95
- </label>
96
- </li>
97
- <li>
98
- <div class="ab-bold"><?php _e( 'Wed', 'ab' ) ?></div>
99
- <!-- #11055: all days are checked by default -->
100
- <label class="active">
101
- <input class="ab-week-day ab-week-day-4" value="4" checked="checked" type="checkbox">
102
- </label>
103
- </li>
104
- <li>
105
- <div class="ab-bold"><?php _e( 'Thu', 'ab' ) ?></div>
106
- <!-- #11055: all days are checked by default -->
107
- <label class="active">
108
- <input class="ab-week-day ab-week-day-5" value="5" checked="checked" type="checkbox">
109
- </label>
110
- </li>
111
- <li>
112
- <div class="ab-bold"><?php _e( 'Fri', 'ab' ) ?></div>
113
- <!-- #11055: all days are checked by default -->
114
- <label class="active">
115
- <input class="ab-week-day ab-week-day-6" value="6" checked="checked" type="checkbox">
116
- </label>
117
- </li>
118
- <li>
119
- <div class="ab-bold"><?php _e( 'Sat', 'ab' ) ?></div>
120
- <!-- #11055: all days are checked by default -->
121
- <label class="active">
122
- <input class="ab-week-day ab-week-day-7" value="7" checked="checked" type="checkbox">
123
- </label>
124
- </li>
125
  </ul>
126
  </div>
127
- <div class="ab-left ab-time-range">
128
- <div class="ab-left ab-time-from">
129
- <label data-default="<?php echo get_option( 'ab_appearance_text_label_start_from' ); ?>" data-link-class="text_start_from_label" class="text_start_from_label ab_editable" id="ab-text-label-start_from" data-type="text" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_label_start_from' )) ?></label>
130
- <div class="ab-select-wrap">
131
- <select class="select-list ab-requested-time-from" style="width: auto">
132
- <option value="00:00">12:00 am</option>
133
- <option value="01:00">1:00 am</option>
134
- <option value="02:00">2:00 am</option>
135
- <option value="03:00">3:00 am</option>
136
- <option value="04:00">4:00 am</option>
137
- <option value="05:00">5:00 am</option>
138
- <option value="06:00">6:00 am</option>
139
- <option value="07:00">7:00 am</option>
140
- <option value="08:00" selected="selected">8:00 am</option>
141
- <option value="09:00">9:00 am</option>
142
- <option value="10:00">10:00 am</option>
143
- <option value="11:00">11:00 am</option>
144
- <option value="12:00">12:00 pm</option>
145
- <option value="13:00">1:00 pm</option>
146
- <option value="14:00">2:00 pm</option>
147
- <option value="15:00">3:00 pm</option>
148
- <option value="16:00">4:00 pm</option>
149
- <option value="17:00">5:00 pm</option>
150
- <option value="18:00">6:00 pm</option>
151
- <option value="19:00">7:00 pm</option>
152
- <option value="20:00">8:00 pm</option>
153
- <option value="21:00">9:00 pm</option>
154
- <option value="22:00">10:00 pm</option>
155
- <option value="23:00">11:00 pm</option>
156
  </select>
157
  </div>
158
  </div>
159
- <div class="ab-left ab-time-to">
160
- <label data-default="<?php echo get_option( 'ab_appearance_text_label_finish_by' ); ?>" data-link-class="text_finish_by_label" class="text_finish_by_label ab_editable" id="ab-text-label-finish_by" data-type="text" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_label_finish_by' )) ?></label>
161
- <div class="ab-select-wrap">
162
- <select class="select-list ab-requested-time-to" style="width: auto">
163
- <option value="09:00">9:00 am</option>
164
- <option value="10:00">10:00 am</option>
165
- <option value="11:00">11:00 am</option>
166
- <option value="12:00">12:00 pm</option>
167
- <option value="13:00">1:00 pm</option>
168
- <option value="14:00">2:00 pm</option>
169
- <option value="15:00">3:00 pm</option>
170
- <option value="16:00">4:00 pm</option>
171
- <option value="17:00">5:00 pm</option>
172
- <option value="18:00">6:00 pm</option>
173
- <option value="19:00">7:00 pm</option>
174
- <option value="20:00">8:00 pm</option>
175
- <option value="21:00">9:00 pm</option>
176
- <option value="22:00">10:00 pm</option>
177
- <option value="23:00">11:00 pm</option>
178
- <option value="23:59">12:00 am</option>
179
  </select>
180
  </div>
181
  </div>
182
  </div>
183
  </div>
184
  <div class="ab-row-fluid ab-nav-steps last-row ab-clear">
185
- <button class="ab-right ab-mobile-prev-step ladda-button orange zoom-in">
186
- <span><?php _e( 'Back', 'ab' ) ?></span>
187
  </button>
188
- <button class="ab-right ab-next-step ladda-button orange zoom-in">
189
- <span><?php _e( 'Next', 'ab' ) ?></span>
190
  </button>
191
  </div>
192
  </div>
193
  </div>
194
  </div>
195
- </div>
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
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>
23
  <option value="3">Orthodontics</option>
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>
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>
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>
backend/modules/appearance/templates/_2_time.php CHANGED
@@ -1,108 +1,192 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <!-- ab-booking-info-second-preview -->
3
- <div id="ab-booking-form" class="ab-booking-form" style="overflow: hidden">
4
- <!-- Progress Tracker-->
5
- <?php $step = 2; include '_progress_tracker.php'; ?>
6
 
7
- <div style="margin-bottom: 15px!important;" class="ab-row-fluid">
8
- <span data-inputclass="input-xxlarge" data-notes="<?php _e( '<b>[[SERVICE_NAME]]</b> - name of service, <b>[[STAFF_NAME]]</b> - name of staff', 'ab' ); ?>" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_second_step' ) ) ?>" data-link-class="ab-text-info-second" class="ab-text-info-second-preview ab-row-fluid ab_editable" id="ab-text-info-second" data-type="textarea" data-pk="1"><?php echo esc_html( get_option( 'ab_appearance_text_info_second_step' ) ) ?></span>
9
- </div>
10
- <!-- timeslots -->
11
- <div class="ab-columnizer-wrap" style="height: 400px;">
12
- <div class="ab-columnizer">
13
- <div class="ab-time-screen">
14
- <div class="ab-column">
15
- <button class="ab-available-day ab-first-child" value="">Wed, Jul 31</button>
16
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>1:00 pm</button>
17
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>1:15 pm</button>
18
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>1:30 pm</button>
19
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>1:45 pm</button>
20
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>2:00 pm</button>
21
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>2:15 pm</button>
22
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>2:30 pm</button>
23
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>2:45 pm</button>
24
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>3:00 pm</button>
25
- </div>
26
- <div class="ab-column">
27
- <button class="ab-available-hour ab-first-child"><i class="ab-hour-icon"><span></span></i>3:15 pm</button>
28
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>3:30 pm</button>
29
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>3:45 pm</button>
30
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>4:00 pm</button>
31
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>4:15 pm</button>
32
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>4:30 pm</button>
33
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>4:45 pm</button>
34
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>5:00 pm</button>
35
- <button class="ab-available-day ab-first-child" value="">Thu, Aug 01</button>
36
- <button class="ab-available-hour ab-last-child"><i class="ab-hour-icon"><span></span></i>10:00 am</button>
37
- </div>
38
- <div class="ab-column">
39
- <button class="ab-available-hour ab-first-child"><i class="ab-hour-icon"><span></span></i>10:15 am</button>
40
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>10:30 am</button>
41
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>10:45 am</button>
42
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>11:00 am</button>
43
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>11:15 am</button>
44
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>11:30 am</button>
45
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>11:45 am</button>
46
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>12:00 pm</button>
47
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>12:15 pm</button>
48
- <button class="ab-available-hour ab-last-child""><i class="ab-hour-icon"><span></span></i>12:30 pm</button>
49
- </div>
50
- <div class="ab-column">
51
- <button class="ab-available-hour ab-first-child"><i class="ab-hour-icon"><span></span></i>12:45 pm</button>
52
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>1:00 pm</button>
53
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>1:15 pm</button>
54
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>3:30 pm</button>
55
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>3:45 pm</button>
56
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>4:00 pm</button>
57
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>4:15 pm</button>
58
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>4:30 pm</button>
59
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>4:45 pm</button>
60
- <button class="ab-available-hour ab-last-child"><i class="ab-hour-icon"><span></span></i>5:00 pm</button>
61
- </div>
62
- <div class="ab-column">
63
- <button class="ab-available-day ab-first-child" value="">Fri, Aug 02</button>
64
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>1:00 pm</button>
65
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>1:15 pm</button>
66
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>1:30 pm</button>
67
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>1:45 pm</button>
68
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>2:00 pm</button>
69
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>2:15 pm</button>
70
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>2:30 pm</button>
71
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>2:45 pm</button>
72
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>3:00 pm</button>
73
- </div>
74
- <div class="ab-column">
75
- <button class="ab-available-hour ab-first-child"><i class="ab-hour-icon"><span></span></i>3:15 pm</button>
76
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>3:30 pm</button>
77
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>3:45 pm</button>
78
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>4:00 pm</button>
79
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>4:15 pm</button>
80
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>4:30 pm</button>
81
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>4:45 pm</button>
82
- <button class=ab-available-hour><i class="ab-hour-icon"><span></span></i>5:00 pm</button>
83
- <button class="ab-available-day ab-first-child" value="">Sat, Aug 03</button>
84
- <button class="ab-available-hour ab-last-child"><i class="ab-hour-icon"><span></span></i>10:00 am</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  </div>
86
- <div class="ab-column">
87
- <button class="ab-available-hour ab-first-child"><i class="ab-hour-icon"><span></span></i>10:15 am</button>
88
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>10:30 am</button>
89
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>10:45 am</button>
90
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>11:00 am</button>
91
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>11:15 am</button>
92
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>11:30 am</button>
93
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>11:45 am</button>
94
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>12:00 pm</button>
95
- <button class="ab-available-hour"><i class="ab-hour-icon"><span></span></i>12:15 pm</button>
96
- <button class="ab-available-hour ab-last-child""><i class="ab-hour-icon"><span></span></i>12:30 pm</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  </div>
98
  </div>
99
  </div>
100
- </div>
101
- <div class="ab-time-buttons ab-row-fluid ab-nav-steps last-row ab-clear">
102
- <a href="javascript:void(0)" class="ab-time-no-resize ab-time-next ab-right"></a>
103
- <a href="javascript:void(0)" class="ab-time-no-resize ab-time-prev ab-right"></a>
104
- <button class="ab-time-no-resize ab-left ab-to-first-step ladda-button orange zoom-in">
105
- <span><?php _e( 'Back', 'ab' ) ?></span>
106
- </button>
107
- </div>
 
 
 
108
  </div>
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 CHANGED
@@ -1,48 +1,42 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <!-- ab-booking-info-third-preview -->
3
- <div class="ab-booking-form" style="overflow: hidden">
4
- <!-- Progress Tracker-->
5
- <?php $step = 3; include '_progress_tracker.php'; ?>
6
 
7
- <div style="margin-bottom: 15px!important;" class="ab-row-fluid">
8
- <span data-inputclass="input-xxlarge" data-notes = "<?php _e( '<b>[[STAFF_NAME]]</b> - name of staff, <b>[[SERVICE_NAME]]</b> - name of service,', 'ab' );?> <br> <?php _e( '<b>[[SERVICE_TIME]]</b> - time of service, <b>[[SERVICE_DATE]]</b> - date of service', 'ab' );?> <br> <?php _e( '<b>[[SERVICE_PRICE]]</b> - price of service.', 'ab' ); ?>" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_third_step' ) ) ?>" data-link-class="ab-text-info-third" class="ab-text-info-third-preview ab-row-fluid ab_editable" id="ab-text-info-third" data-type="textarea" data-pk="1"><?php echo esc_html( get_option( 'ab_appearance_text_info_third_step' ) ) ?></span>
9
- </div>
10
- <form class="ab-your-details-form ab-row-fluid">
11
- <div class="ab-details-list ab-left">
12
- <label data-default="<?php echo get_option( 'ab_appearance_text_label_name' ); ?>" data-link-class="text_name_label" class="ab-formLabel text_name_label ab_editable" id="ab-text-label-name" data-type="text" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_label_name' )) ?></label>
13
- <div class="ab-details-wrap">
14
- <input class="ab-full-name" type="text" value="" maxlength="60">
15
- </div>
16
- <div class="ab-full-name-error ab-bold"></div>
17
  </div>
18
- <div class="ab-details-list ab-left">
19
- <label data-default="<?php echo get_option( 'ab_appearance_text_label_phone' ); ?>" data-link-class="text_phone_label" class="ab-formLabel text_phone_label ab_editable" id="ab-text-label-phone" data-type="text" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_label_phone' )) ?></label>
20
- <div class="ab-details-wrap">
21
- <input class="ab-user-phone" maxlength="30" type="text" value="">
22
- </div>
23
- <div class="ab-user-phone-error ab-bold"></div>
24
  </div>
25
- <div class="ab-details-list ab-left">
26
- <label data-default="<?php echo get_option( 'ab_appearance_text_label_email' ); ?>" data-link-class="text_email_label" class="ab-formLabel text_email_label ab_editable" id="ab-text-label-email" data-type="text" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_label_email' )) ?></label>
27
- <div class="ab-details-wrap" style="margin-right: 0">
28
- <input class="ab-user-email" maxlength="40" type="text" value="">
29
- </div>
30
- <div class="ab-user-email-error ab-bold"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  </div>
32
- <div class="ab-clear"></div>
33
- <div class="ab-details-list ab-textarea">
34
- <label data-default="<?php echo get_option( 'ab_appearance_text_label_notes' ); ?>" data-link-class="text_notes_label" class="ab-formLabel text_notes_label ab_editable" id="ab-text-label-notes" data-type="text" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_label_notes' )) ?></label>
35
- <div style="margin: 5px 2px 0 0">
36
- <textarea rows="6" class="ab-user-notes"></textarea>
37
- </div>
38
- </div>
39
- </form>
40
- <div class="ab-row-fluid last-row ab-nav-steps ab-clear">
41
- <button class="ab-left ab-to-second-step ladda-button orange zoom-in" style="margin-right: 10px;">
42
- <span><?php _e( 'Back', 'ab' ) ?></span>
43
- </button>
44
- <button class="ab-right ab-to-fourth-step ladda-button orange zoom-in">
45
- <span><?php _e( 'Next', 'ab' ) ?></span>
46
- </button>
47
- </div>
48
  </div>
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/_4_payment.php CHANGED
@@ -1,41 +1,60 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <!-- ab-booking-info-third-preview -->
3
- <div class="ab-booking-form" style="overflow: hidden">
4
  <!-- Progress Tracker-->
5
  <?php $step = 4; include '_progress_tracker.php'; ?>
6
- <div style="margin-bottom: 15px!important;" class="ab-row-fluid">
7
- <span data-inputclass="input-xxlarge" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_fourth_step' ) ) ?>" data-link-class="ab-text-info-fourth" class="ab-text-info-fourth-preview ab-row-fluid ab_editable" id="ab-text-info-fourth" data-type="textarea" data-pk="1"><?php echo esc_html( get_option( 'ab_appearance_text_info_fourth_step' ) ) ?></span>
8
- </div>
9
  <!-- payment -->
10
  <div class="ab-payment">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  <!-- label -->
12
  <div class="ab-row-fluid">
13
  <label>
14
  <input type="radio" name="payment" class="ab-local-payment" checked="checked" value="local"/>
15
- <?php _e( 'I will pay locally', 'ab' ) ?>
 
 
 
 
 
 
 
16
  </label>
17
  </div>
18
  <div class="ab-row-fluid">
19
  <label>
20
- <input type="radio" name="payment" class="ab-paypal-payment" value="paypal"/>
21
- <?php _e( 'I will pay now with PayPal', 'ab' ) ?>
 
22
  </label>
 
 
 
23
  </div>
 
24
  <!-- buttons -->
25
  <div class="ab-local-pay-button ab-row-fluid ab-nav-steps last-row">
26
- <button class="ab-left ab-to-third-step ladda-button orange zoom-in" style="margin-right: 10px;">
27
- <span><?php _e( 'Back', 'ab' ) ?></span>
28
  </button>
29
- <button class="ab-right ab-final-step ladda-button orange zoom-in">
30
- <span><?php _e( 'Next', 'ab' ) ?></span>
31
  </button>
32
  </div>
33
  </div>
34
- </div>
35
-
36
- <!-- fourth step options -->
37
- <div class="ab-fourth-step-options">
38
- <!-- booking-info -->
39
- <div class="ab-booking-details">
40
- </div>
41
  </div>
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_done.php CHANGED
@@ -1,15 +1,8 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <!-- ab-booking-info-third-preview -->
3
- <div class="ab-booking-form" style="overflow: hidden">
4
  <!-- Progress Tracker-->
5
  <?php $step = 5; include '_progress_tracker.php'; ?>
6
- <div style="margin-bottom: 15px!important;" class="ab-row-fluid">
7
- <span data-inputclass="input-xxlarge" data-link-class="ab-text-info-fifth" class="ab-text-info-fifth-preview ab_editable" data-default="<?php echo esc_attr( get_option( 'ab_appearance_text_info_fifth_step' ) ) ?>" id="ab-text-info-fifth" data-type="textarea" data-pk="1"><?php echo nl2br( esc_html( get_option( 'ab_appearance_text_info_fifth_step' ) ) ) ?></span>
8
- </div>
9
- </div>
10
-
11
- <!-- fifth step options -->
12
- <div class="ab-fifth-step-options">
13
- <div class="ab-booking-details">
14
  </div>
15
  </div>
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/_card_payment.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>
backend/modules/appearance/templates/_codes.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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' ) ?>
backend/modules/appearance/templates/_progress_tracker.php CHANGED
@@ -1,23 +1,24 @@
 
1
  <div class="ab-progress-tracker">
2
  <ul class="ab-progress-bar nav-3">
3
- <li class="ab-step-tabs first active">
4
- <a href="javascript:void(0)">1. <span data-default="<?php echo 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" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_step_service' )) ?></span></a>
5
  <div class="step"></div>
6
  </li>
7
  <li class="ab-step-tabs<?php if ($step >= 2): ?> active<?php endif ?>">
8
- <a href="javascript:void(0)">2. <span data-default="<?php echo 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" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_step_time' )) ?></span></a>
9
  <div class="step"></div>
10
  </li>
11
  <li class="ab-step-tabs<?php if ($step >= 3): ?> active<?php endif ?>">
12
- <a href="javascript:void(0)">3. <span data-default="<?php echo 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" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_step_details' )) ?></span></a>
13
  <div class="step"></div>
14
  </li>
15
  <li class="ab-step-tabs<?php if ($step >= 4): ?> active<?php endif ?>">
16
- <a href="javascript:void(0)">4. <span data-default="<?php echo 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" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_step_payment' )) ?></span></a>
17
  <div class="step"></div>
18
  </li>
19
- <li class="ab-step-tabs last<?php if ($step >= 5): ?> active<?php endif ?>">
20
- <a href="javascript:void(0)">5. <span data-default="<?php echo 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" data-pk="1"><?php echo esc_html(get_option( 'ab_appearance_text_step_done' )) ?></span></a>
21
  <div class="step"></div>
22
  </li>
23
  </ul>
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>
backend/modules/appearance/templates/index.php CHANGED
@@ -1,85 +1,90 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
-
3
- <div class="ab-title">
4
- <div class="alert" style="font-size: 14px; display: none;">
5
- <button type="button" class="close" onclick="jQuery('.alert').hide()">&times;</button>
6
- <?php _e( 'Settings saved.', 'ab' ); ?>
7
  </div>
8
- <?php _e( 'Appearance', 'ab' ) ?>
9
- </div>
10
-
11
- <input type=text class="wp-color-picker appearance-color-picker" name=color
12
- value="<?php echo get_option( 'ab_appearance_color' ) ?>"
13
- data-selected="<?php echo get_option( 'ab_appearance_color' ) ?>" />
14
-
15
- <div style="max-width: 960px;">
16
- <form method=post id=common_settings style="margin-right: 15px">
17
- <legend id=main_form>
18
- <div>
19
- <input id=ab-progress-tracker-checkbox name=ab-progress-tracker-checkbox <?php if (get_option( 'ab_appearance_show_progress_tracker' )): ?>checked=checked<?php endif ?> type=checkbox />
20
- <label style="display: inline" for="progress_tracker">
21
- <b><?php _e( 'Show form progress tracker', 'ab' ) ?></b>
22
- </label>
23
- </div>
24
- </legend>
25
- </form>
26
- <!-- Tabs -->
27
- <div class=tabbable style="margin-top: 20px;">
28
- <ul class="nav nav-tabs ab-nav-tabs">
29
- <?php foreach ( $steps as $step_id => $step_name ): ?>
30
- <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 ?>">
31
- <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>
32
- </li>
33
- <?php endforeach ?>
34
- </ul>
35
- <!-- Tabs-Content -->
36
- <div class=tab-content>
37
- <?php foreach ( $steps as $step_id => $step_name ) : ?>
38
- <div class="tab-pane-<?php echo $step_id ?><?php if ( $step_id == 1 ): ?> active<?php endif ?>" data-step-id="<?php echo $step_id ?>">
39
- <?php
40
- // Render unique data per step
41
- switch ( $step_id ) {
42
- // Service
43
- case 1:
44
- include '_1_service.php';
45
- break;
46
- // Time
47
- case 2:
48
- include '_2_time.php';
49
- break;
50
- // Details
51
- case 3:
52
- include '_3_details.php';
53
- break;
54
- // Payment
55
- case 4:
56
- include '_4_payment.php';
57
- break;
58
- // Done
59
- case 5:
60
- include '_5_done.php';
61
- break;
62
- }
63
- ?>
64
  </div>
65
- <?php endforeach ?>
66
- </div>
67
- <div style="float:right;margin-right:20px;">
68
- <p><?php _e('Click on the underlined text to edit.', 'ab') ?></p>
69
- </div>
70
- <div class="clear"></div>
71
- <!-- controls -->
72
- <div class=controls>
73
- <!-- spinner -->
74
- <span id="update_spinner" class="spinner"></span>
75
- <!-- update button -->
76
- <button id="update_button" class="btn btn-info ab-update-button ab-appearance-update">
77
- <?php _e( 'Update', 'ab' ) ?>
78
- </button>
79
- <!-- reset button -->
80
- <button id="reset_button" class="ab-reset-form ab-appearance-reset" type="reset">
81
- <?php _e( 'Reset', 'ab' ) ?>
82
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  </div>
84
  </div>
 
 
 
 
85
  </div>
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>
backend/modules/appointments/AB_AppointmentsController.php ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/resources/js/ng-app.js ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/index.php ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>
backend/modules/calendar/AB_CalendarController.php CHANGED
@@ -1,516 +1,428 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
-
5
- $path_to_entities = dirname(__FILE__) . '/../../../lib/entities/';
6
-
7
- include 'forms/AB_AppointmentForm.php';
8
- include $path_to_entities . 'AB_ScheduleItem.php';
9
 
10
  /**
11
  * Class AB_CalendarController
12
- *
13
- * @property $collection
14
- * @property $staff_services
15
- * @property $startDate
16
- * @property $period_start
17
- * @property $period_end
18
- * @property $customers
19
- * @property $staff_id
20
- * @property $service_id
21
- * @property $customer_id
22
- * @property $staff_collection
23
- * @property $date_interval_not_available
24
- * @property $date_interval_warning
25
- * @property $notes
26
  */
27
- class AB_CalendarController extends AB_Controller {
28
-
29
- public function renderCalendar() {
30
- wp_enqueue_style( 'ab-jquery-ui-css', plugins_url( 'resources/css/jquery-ui-1.10.1.css', __FILE__ ) );
31
- wp_enqueue_style( 'ab-weekcalendar', plugins_url( 'resources/css/jquery.weekcalendar.css', __FILE__ ) );
32
- wp_enqueue_style( 'ab-calendar', plugins_url( 'resources/css/calendar.css', __FILE__ ) );
33
- wp_enqueue_style( 'ab-chosen', plugins_url( 'resources/css/chosen.css', __FILE__ ) );
34
-
35
- wp_enqueue_style( 'ab-style', plugins_url( 'resources/css/ab_style.css', dirname(__FILE__).'/../../AB_Backend.php' ) );
36
- wp_enqueue_style( 'ab-bootstrap', plugins_url( 'resources/bootstrap/css/bootstrap.min.css', dirname( dirname( __FILE__ ) ) ) );
37
- wp_enqueue_script( 'ab-bootstrap', plugins_url( 'resources/bootstrap/js/bootstrap.min.js', dirname( dirname( __FILE__ ) ) ), array( 'jquery' ) );
38
- wp_enqueue_script( 'ab-date', plugins_url( 'resources/js/date.js', dirname(__FILE__).'/../../AB_Backend.php' ), array( 'jquery' ) );
39
- wp_enqueue_script( 'ab-chosen', plugins_url( 'resources/js/chosen.jquery.js', __FILE__ ) );
40
-
41
- wp_enqueue_script(
42
- 'ab-weekcalendar',
43
- plugins_url( 'resources/js/jquery.weekcalendar.js', __FILE__ ),
44
- array(
45
- 'jquery',
46
- 'jquery-ui-widget',
47
- 'jquery-ui-dialog',
48
- 'jquery-ui-button',
49
- 'jquery-ui-draggable',
50
- 'jquery-ui-droppable',
51
- 'jquery-ui-resizable',
52
- 'jquery-ui-datepicker'
53
- )
54
- );
55
- wp_enqueue_script( 'ab-calendar_daypicker', plugins_url( 'resources/js/calendar_daypicker.js', __FILE__ ), array( 'jquery' ) );
56
- wp_enqueue_script( 'ab-calendar_weekpicker', plugins_url( 'resources/js/calendar_weekpicker.js', __FILE__ ), array( 'jquery' ) );
57
- wp_enqueue_script( 'ab-calendar', plugins_url( 'resources/js/calendar.js', __FILE__ ), array( 'jquery', 'ab-calendar_daypicker', 'ab-calendar_weekpicker' ) );
58
- wp_enqueue_script( 'ab-angularjs', plugins_url( 'resources/js/angular-1.0.6.min.js', dirname( dirname( __FILE__ ) ) ) );
59
- wp_enqueue_script( 'ab-angularui', plugins_url( 'resources/js/angular-ui-0.4.0.min.js', dirname( dirname( __FILE__ ) ) ) );
60
- wp_enqueue_script( 'ab-ng-app', plugins_url( 'resources/js/ng-app.js', __FILE__ ), array( 'jquery' ) );
61
- wp_enqueue_script( 'ab-ng-new_customer_dialog', plugins_url( 'resources/js/ng-new_customer_dialog.js', dirname( dirname(__FILE__) ) ), array( 'jquery', 'ab-angularjs' ) );
62
- wp_localize_script( 'ab-ng-app', 'BooklyL10n', array(
63
- 'new_appointment' => __( 'New appointment', 'ab' ),
64
- 'edit_appointment' => __( 'Edit appointment', 'ab' ),
65
- 'are_you_sure' => __( 'Are you sure?', 'ab' ),
66
- 'phone' => __( 'Phone', 'ab' ),
67
- 'email' => __( 'Email', 'ab' ),
68
- 'timeslotsPerHour' => 60 / get_option('ab_settings_time_slot_length'),
69
- 'shortMonths' => array(
70
- __( 'Jan', 'ab' ),
71
- __( 'Feb', 'ab' ),
72
- __( 'Mar', 'ab' ),
73
- __( 'Apr', 'ab' ),
74
- __( 'May', 'ab' ),
75
- __( 'Jun', 'ab' ),
76
- __( 'Jul', 'ab' ),
77
- __( 'Aug', 'ab' ),
78
- __( 'Sep', 'ab' ),
79
- __( 'Oct', 'ab' ),
80
- __( 'Nov', 'ab' ),
81
- __( 'Dec', 'ab' ),
82
  ),
83
- 'longMonths' => array(
84
- __( 'January', 'ab' ),
85
- __( 'February', 'ab' ),
86
- __( 'March', 'ab' ),
87
- __( 'April', 'ab' ),
88
- __( 'May', 'ab' ),
89
- __( 'June', 'ab' ),
90
- __( 'July', 'ab' ),
91
- __( 'August', 'ab' ),
92
- __( 'September', 'ab' ),
93
- __( 'October', 'ab' ),
94
- __( 'November', 'ab' ),
95
- __( 'December', 'ab' )
96
  ),
97
- 'shortDays' => array(
98
- __( 'Sun', 'ab' ),
99
- __( 'Mon', 'ab' ),
100
- __( 'Tue', 'ab' ),
101
- __( 'Wed', 'ab' ),
102
- __( 'Thu', 'ab' ),
103
- __( 'Fri', 'ab' ),
104
- __( 'Sat', 'ab' )
105
  ),
106
- 'longDays' => array(
107
- __( 'Sunday', 'ab' ),
108
- __( 'Monday', 'ab' ),
109
- __( 'Tuesday', 'ab' ),
110
- __( 'Wednesday', 'ab' ),
111
- __( 'Thursday', 'ab' ),
112
- __( 'Friday', 'ab' ),
113
- __( 'Saturday', 'ab' ),
 
 
 
114
  ),
115
- 'PM' => __( 'PM', 'ab' ),
116
- 'AM' => __( 'AM', 'ab' ),
117
- 'Week' => __( 'Week', 'ab' ) . ': ',
118
- 'dateFormat' => $this->dateFormatTojQueryUIDatePickerFormat(),
119
- ));
120
-
121
- $user_id = get_current_user_id();
122
- if ( is_super_admin( $user_id ) ) {
123
- $this->collection = $this->getWpdb()->get_results( "SELECT * FROM ab_staff" );
124
- } else {
125
- $this->collection = $this->getWpdb()->get_results( $this->getWpdb()->prepare( "SELECT * FROM ab_staff s WHERE s.wp_user_id = %d", array($user_id) ) );
126
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
  $this->render( 'calendar' );
129
  }
 
130
  /**
131
- * Get data for WeekCalendar in `week` mode.
132
  *
133
  * @return json
134
  */
135
- public function executeWeekStaffAppointments() {
136
- $result = array( 'events' => array(), 'freebusys' => array() );
137
- $staff_id = $this->getParameter( 'staff_id' );
138
- if ( $staff_id ) {
139
- $staff = new AB_Staff();
140
- $staff->load( $staff_id );
141
-
142
- $start_date = $this->_post['start_date'];
143
- $end_date = $this->_post['end_date'];
144
-
145
- $staff_appointments = $staff->getAppointments( $start_date, $end_date );
146
- foreach ( $staff_appointments as $appointment ) {
147
- $result['events'][] = $this->getAppointment( $appointment );
148
- }
149
-
150
- $wpdb = $this->getWpdb();
151
- $schedule = $wpdb->get_results( $wpdb->prepare(
152
- 'SELECT
153
- ssi.*,
154
- si.id - 1 AS "day_index",
155
- si.name AS "day_name"
156
- FROM `ab_staff_schedule_item` ssi
157
- LEFT JOIN `ab_schedule_item` si ON ssi.schedule_item_id = si.id
158
- WHERE ssi.staff_id = %d',
159
- $staff_id
160
- ) );
161
-
162
- $holidays = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM ab_holiday WHERE staff_id = %d OR staff_id IS NULL', $staff_id ) );
163
-
164
- if ( ! empty( $schedule ) ) {
165
- $wp_week_start_day = get_option( 'start_of_week', 1 );
166
- $schedule_start_day = $schedule[0]->id - 1;
167
-
168
- // if wp week start day is higher than our
169
- // cut the list into 2 parts (before and after wp wp week start day)
170
- // move the second part of the list above the first one
171
- if ( $wp_week_start_day > $schedule_start_day ) {
172
- $schedule_start = array_slice( $schedule, 0, $wp_week_start_day );
173
- $schedule_end = array_slice( $schedule, $wp_week_start_day );
174
- $schedule = $schedule_end;
175
-
176
- foreach ( $schedule_start as $schedule_item ) {
177
- $schedule[] = $schedule_item;
178
- }
179
- }
180
-
181
- $active_schedule_items_ids = array();
182
 
183
- foreach ( $schedule as $item ) {
184
- // if start time is NULL we consider that the day is "OFF"
185
- if ( null !== $item->start_time ) {
186
- if ($item->day_name == 'Sunday' && $wp_week_start_day == 0){
187
- $date = date( 'Y-m-d', strtotime( $item->day_name . ' last week', strtotime( $start_date ) ) );
188
- }else{
189
- $date = date( 'Y-m-d', strtotime( $item->day_name . ' this week', strtotime( $start_date ) ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  }
191
- $startDate = new DateTime( $date . ' ' . $item->start_time );
192
- $endDate = new DateTime( $date . ' ' . $item->end_time );
193
- // Skip holidays
194
- foreach ( $holidays as $holiday ) {
195
- $holidayDate = new DateTime($holiday->holiday);
196
- if ( $holiday->repeat_event ) {
197
- if ($holidayDate->format('m-d') == $startDate->format('m-d')) {
198
- continue 2;
199
- }
200
- } else {
201
- if ($holidayDate->format('Y-m-d') == $startDate->format('Y-m-d')) {
202
- continue 2;
203
- }
204
- }
205
  }
206
 
207
- // get available day parts
208
- $result['freebusys'][] = $this->getFreeBusy( $startDate, $endDate, true );
209
- $active_schedule_items_ids[] = $item->id;
210
  }
211
- }
212
 
213
- if ( empty( $active_schedule_items_ids ) ) {
214
- $active_schedule_items_ids = array( 0 );
215
- }
 
 
 
 
 
216
 
217
- $schedule_breaks = $wpdb->get_results(
218
- 'SELECT
219
- sib.*,
220
- si.id - 1 AS "day_index",
221
- si.name AS "day_name"
222
- FROM `ab_schedule_item_break` sib
223
- LEFT JOIN `ab_staff_schedule_item` ssi ON sib.staff_schedule_item_id = ssi.id
224
- LEFT JOIN `ab_schedule_item` si ON ssi.schedule_item_id = si.id
225
- WHERE sib.staff_schedule_item_id IN (' . implode( ', ', $active_schedule_items_ids ) . ')'
226
- );
227
 
228
- foreach ( $schedule_breaks as $break_item ) {
229
- $date = date( 'Y-m-d', strtotime( $break_item->day_name . ' this week', strtotime( $start_date ) ) );
230
- $startDate = new DateTime( $date . ' ' . $break_item->start_time );
231
- $endDate = new DateTime( $date . ' ' . $break_item->end_time );
232
-
233
- // get breaks
234
- $result['freebusys'][] = $this->getFreeBusy( $startDate, $endDate, false );
235
- }
236
- }
237
- }
238
- echo json_encode( $result );
239
- exit;
240
- }
241
-
242
- /**
243
- * Get data for WeekCalendar in `day` mode.
244
- *
245
- * @return json
246
- */
247
- public function executeDayStaffAppointments() {
248
- $result = array( 'events' => array(), 'freebusys' => array() );
249
- $staff_ids = $this->getParameter( 'staff_id' );
250
- if (is_array($staff_ids)) {
251
- $wpdb = $this->getWpdb();
252
-
253
- $start_date = $this->_post['start_date'];
254
-
255
- $appointments = $wpdb->get_results( sprintf(
256
- 'SELECT
257
- a.id,
258
- a.start_date,
259
- a.end_date,
260
- a.notes,
261
- a.customer_id AS "customer_id",
262
- s.title,
263
- s.color,
264
- staff.id AS "staff_id",
265
- staff.full_name AS "staff_fullname"
266
- FROM ab_appointment a
267
- LEFT JOIN ab_service s ON a.service_id = s.id
268
- LEFT JOIN ab_staff staff ON a.staff_id = staff.id
269
- WHERE DATE(a.start_date) = DATE("%s") AND staff_id IN (%s)
270
- GROUP BY a.id',
271
- mysql_real_escape_string($start_date),
272
- implode(',', array_merge(array(0), array_map('intval', $staff_ids)))
273
- ) );
274
-
275
- foreach ( $appointments as $appointment ) {
276
- $result['events'][] = $this->getAppointment( $appointment, $appointment->staff_id, $day_view = true );
277
  }
278
 
279
- $schedule = $wpdb->get_results(
280
- 'SELECT
281
- ssi.*,
282
- s.id AS "staff_id"
283
- FROM `ab_staff_schedule_item` ssi
284
- LEFT JOIN `ab_schedule_item` si ON ssi.schedule_item_id = si.id
285
- LEFT JOIN `ab_staff` s ON ssi.staff_id = s.id
286
- WHERE si.name = DATE_FORMAT(DATE("' . $start_date . '"), "%W")
287
- AND ssi.start_time IS NOT NULL'
288
- );
289
-
290
- $active_schedule_items_ids = array();
291
-
292
- foreach ( $schedule as $item ) {
293
- $startDate = new DateTime(date( 'Y-m-d', strtotime( $start_date ) ) . ' ' . $item->start_time);
294
- $endDate = new DateTime(date( 'Y-m-d', strtotime( $start_date ) ) . ' ' . $item->end_time);
295
-
296
- $holidays = $wpdb->get_results($wpdb->prepare(
297
- 'SELECT * FROM ab_holiday WHERE staff_id = %d and ((`repeat_event` = 0 and DATE_FORMAT( `holiday` , "%%Y-%%m-%%d" ) = %s) or (`repeat_event` = 1 and DATE_FORMAT( `holiday` , "%%Y-%%m" ) = %s))',
298
- array($item->staff_id, $startDate->format('Y-m-d'), $startDate->format('m-d')))
299
- );
300
- if (!$holidays){
301
- $result['freebusys'][] = $this->getFreeBusy( $startDate, $endDate, true, $item->staff_id );
302
- $active_schedule_items_ids[] = $item->id;
303
- }
304
- }
305
-
306
- if ( empty($active_schedule_items_ids) ) {
307
- $active_schedule_items_ids = array( 0 );
308
- }
309
-
310
- $schedule_breaks = $wpdb->get_results(
311
- 'SELECT
312
- sib.*,
313
- s.id AS "staff_id"
314
- FROM `ab_schedule_item_break` sib
315
- LEFT JOIN `ab_staff_schedule_item` ssi ON sib.staff_schedule_item_id = ssi.id
316
- LEFT JOIN `ab_schedule_item` si ON ssi.schedule_item_id = si.id
317
- LEFT JOIN `ab_staff` s ON ssi.staff_id = s.id
318
- WHERE sib.staff_schedule_item_id IN (' . implode( ', ', $active_schedule_items_ids ) . ')'
319
- );
320
-
321
- foreach ( $schedule_breaks as $break_item ) {
322
- $startDate = new DateTime(date( 'Y-m-d', strtotime( $start_date ) ) . ' ' . $break_item->start_time);
323
- $endDate = new DateTime(date( 'Y-m-d', strtotime( $start_date ) ) . ' ' . $break_item->end_time);
324
-
325
- $result['freebusys'][] = $this->getFreeBusy( $startDate, $endDate, false, $break_item->staff_id );
326
  }
327
  }
328
- echo json_encode( $result );
329
- exit;
330
  }
331
 
332
  /**
333
  * Get data needed for appointment form initialisation.
334
  */
335
- public function executeGetDataForAppointmentForm() {
336
- $wpdb = $this->getWpdb();
337
- $user_id = get_current_user_id();
338
- $result = array(
339
  'staff' => array(),
340
  'customers' => array(),
341
- 'time' => array(),
 
342
  'time_interval' => get_option( 'ab_settings_time_slot_length' ) * 60
343
  );
344
 
345
  // Staff list.
346
- if ( is_super_admin( $user_id ) ) {
347
- $staff = $wpdb->get_results( 'SELECT `id`, `full_name` FROM `ab_staff`', ARRAY_A );
348
- } else {
349
- $staff = $wpdb->get_results( $wpdb->prepare(
350
- 'SELECT `id`, `full_name` FROM `ab_staff` WHERE `wp_user_id` = %d',
351
- array( $user_id )
352
- ), ARRAY_A );
353
- }
354
- foreach ( $staff as $st ) {
355
- $services = $wpdb->get_results( $wpdb->prepare(
356
- 'SELECT
357
- `service`.`id`,
358
- `service`.`title`,
359
- `service`.`duration`
360
- FROM `ab_service` `service`
361
- LEFT JOIN `ab_staff_service` `ss` ON `ss`.`service_id` = `service`.`id`
362
- LEFT JOIN `ab_staff` `staff` ON `ss`.`staff_id` = `staff`.`id`
363
- WHERE `staff`.`id` = %d',
364
- $st[ 'id' ]
365
- ), ARRAY_A );
366
- array_walk($services, create_function('&$a', '$a[\'title\'] = sprintf(\'%s (%s)\', $a[\'title\'], AB_Service::durationToString($a[\'duration\']));'));
367
- $result[ 'staff' ][] = array(
368
- 'id' => $st[ 'id' ],
369
- 'full_name' => $st[ 'full_name' ],
370
  'services' => $services
371
  );
372
  }
373
 
374
  // Customers list.
375
- $customers = $this->getWpdb()->get_results(
376
- 'SELECT * FROM `ab_customer` WHERE name <> "" OR phone <> "" OR email <> "" ORDER BY name',
377
- ARRAY_A
378
- );
379
- $customer = new AB_Customer();
380
- foreach ($customers as $customer_data) {
381
- $customer->setData( $customer_data );
382
-
383
- $name = $customer->get('name');
384
- if ($customer->get('email') && $customer->get('phone')){
385
- $name .= ' (' . $customer->get('email') . ', ' . $customer->get('phone') . ')';
386
- }elseif($customer->get('email')){
387
- $name .= ' (' . $customer->get('email') . ')';
388
- }elseif($customer->get('phone')){
389
- $name .= ' (' . $customer->get('phone') . ')';
390
  }
391
 
392
  $result[ 'customers' ][] = array(
393
- 'id' => $customer->get('id'),
394
- 'name' => $name,
 
 
395
  );
396
  }
397
 
398
  // Time list.
399
- $tf = get_option( 'time_format' );
400
- $ts_length = get_option( 'ab_settings_time_slot_length' );
401
- $time_start = new AB_DateTime( AB_StaffScheduleItem::WORKING_START_TIME, new DateTimeZone( 'UTC' ) );
402
- $time_end = new AB_DateTime( AB_StaffScheduleItem::WORKING_END_TIME, new DateTimeZone( 'UTC' ) );
403
 
404
  // Run the loop.
405
- while ( $time_start->format( 'U' ) <= $time_end->format( 'U' ) ) {
406
- $result[ 'time' ][ ] = array(
407
- 'value' => $time_start->format( 'H:i' ),
408
- 'title' => date_i18n( $tf, $time_start->format( 'U' ) )
409
  );
410
- $time_start->modify( '+' . $ts_length . ' min' );
 
 
 
 
411
  }
412
 
413
- echo json_encode( $result );
414
- exit (0);
415
  }
416
 
417
  /**
418
- * Get appointment data when editing the appointment.
419
  */
420
- public function executeGetDataForAppointment() {
421
- $response = array( 'status' => 'error', 'data' => array() );
 
422
 
423
  $appointment = new AB_Appointment();
424
- if ( $appointment->load( $this->_post['id'] ) ) {
425
- $response[ 'status' ] = 'ok';
426
- $response[ 'data' ][ 'service_id' ] = $appointment->get( 'service_id' );
427
- $response[ 'data' ][ 'customer_id' ] = $appointment->get( 'customer_id' );
428
- $response[ 'data' ][ 'notes' ] = $appointment->get( 'notes' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
  }
430
-
431
- echo json_encode( $response );
432
- exit ( 0 );
433
  }
434
 
435
  /**
436
  * Save appointment form (for both create and edit).
437
  */
438
- public function executeSaveAppointmentForm() {
439
- global $wpdb;
440
-
441
- $response = array( 'status' => 'error' );
442
-
443
- $start_date = $this->_post['start_date'];
444
- $end_date = $this->_post['end_date'];
445
- $staff_id = $this->_post['staff_id'];
446
- $service_id = $this->_post['service_id'] ? $this->_post['service_id'] : null;
447
- $customer_id = $this->_post['customer_id'] ? $this->_post['customer_id'] : null;
448
- $appointment_id = $this->_post['id'] ? $this->_post['id'] : 0;
449
- $notes = $this->_post['notes'] ? $this->_post['notes'] : '';
 
 
 
 
450
 
 
 
 
 
 
 
 
451
  if ( !$this->dateIntervalIsAvailableForAppointment( $start_date, $end_date, $staff_id, $appointment_id ) ) {
452
- $response[ 'errors' ] = array( 'date_interval_not_available' => true );
453
- } else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  $appointment = new AB_Appointment();
455
  if ( $appointment_id ) {
456
- // edit
457
  $appointment->load( $appointment_id );
458
  }
459
- $appointment->set( 'start_date', $start_date );
460
- $appointment->set( 'end_date', $end_date );
461
- $appointment->set( 'staff_id', $staff_id );
462
- $appointment->set( 'service_id', $service_id );
463
- $appointment->set( 'customer_id', $customer_id );
464
- $appointment->set( 'notes', $notes );
465
 
466
  if ( $appointment->save() !== false ) {
 
 
 
 
 
 
467
  $startDate = new DateTime( $appointment->get( 'start_date' ) );
468
  $endDate = new DateTime( $appointment->get( 'end_date' ) );
469
- $employee = new AB_Staff();
470
- $employee->load( $staff_id );
471
- $service = new AB_Service();
472
- $service->load( $service_id );
473
- $response[ 'status' ] = 'ok';
474
- $desc = array();
475
- $customer = $this->getCustomer( $this->getParameter( 'customer_id' ) );
476
-
477
- foreach ( array( 'name', 'phone', 'email' ) as $data_entry ) {
478
- $entry_value = $customer->get( $data_entry );
479
- if ( $entry_value ) {
480
- $desc[] = '<div class="wc-employee">' . esc_html( $entry_value ) . '</div>';
 
 
 
 
 
 
 
 
481
  }
482
- }
483
 
484
- if ( $appointment->get( 'notes' ) ) {
485
- $desc[] = '<div class="wc-notes">' . $appointment->notes . '</div>';
486
  }
487
 
488
- $response[ 'data' ] = array(
489
- 'id' => (int)$appointment->get( 'id' ),
490
- 'start' => $startDate->format( 'm/d/Y H:i' ),
491
- 'end' => $endDate->format( 'm/d/Y H:i' ),
492
- 'desc' => implode('', $desc),
493
- 'title' => $service->get( 'title' ) ? $service->get( 'title' ) : __( 'Untitled', 'ab' ),
494
- 'color' => $service->get( 'color' ),
495
- 'userId' => (int)$appointment->get( 'staff_id' ),
496
- );
497
 
498
- $staff = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM ab_staff WHERE id = %d', $staff_id ) );
 
 
 
 
 
 
 
 
 
499
  } else {
500
- $response[ 'errors' ] = array( 'unknown' => true );
501
  }
502
  }
503
 
504
- echo json_encode( $response );
505
- exit ( 0 );
506
  }
507
 
508
- public function executeCheckAppointmentDateSelection() {
509
- $start_date = $this->getParameter('start_date');
510
- $end_date = $this->getParameter('end_date');
511
- $staff_id = $this->getParameter('staff_id');
512
- $service_id = $this->getParameter('service_id');
513
- $appointment_id = $this->getParameter('appointment_id');
 
514
  $timestamp_diff = strtotime( $end_date ) - strtotime( $start_date );
515
 
516
  $result = array(
@@ -528,147 +440,49 @@ class AB_CalendarController extends AB_Controller {
528
 
529
  $duration = $service->get( 'duration' );
530
 
531
- // service duration interval is not equal to
532
- $result['date_interval_warning'] = ($timestamp_diff != $duration);
533
  }
534
 
535
- echo json_encode( $result );
536
- exit;
537
  }
538
 
539
- public function executeDeleteAppointment() {
 
540
  $appointment = new AB_Appointment();
541
- $appointment->load( $this->_post['appointment_id'] );
542
  $appointment->delete();
543
  exit;
544
  }
545
 
546
- private function dateIntervalIsAvailableForAppointment( $start_date, $end_date, $staff_id, $appointment_id ) {
547
- return ! is_object( $this->getWpdb()->get_row( $this->getWpdb()->prepare(
548
- 'SELECT * FROM `ab_appointment`
549
- WHERE (
550
- start_date > %s AND start_date < %s
551
- OR (end_date > %s AND end_date < %s)
552
- OR (start_date < %s AND end_date > %s)
553
- OR (start_date = %s OR end_date = %s)
554
- )
555
- AND staff_id = %d
556
- AND id <> %d',
557
- $start_date,
558
- $end_date,
559
- $start_date,
560
- $end_date,
561
- $start_date,
562
- $end_date,
563
- $start_date,
564
- $end_date,
565
- $staff_id,
566
- $appointment_id
567
- ) ) );
568
- }
569
-
570
  /**
571
- * @param $id
572
- *
573
- * @return AB_Customer
574
- */
575
- public function getCustomer( $id ) {
576
- $customer = new AB_Customer();
577
- $customer_data = $this->getWpdb()->get_row( $this->getWpdb()->prepare(
578
- 'SELECT * FROM `ab_customer` WHERE id = %d', $id
579
- ) );
580
- // populate customer with data
581
- if ( $customer_data ) {
582
- $customer->setData( $customer_data );
583
- }
584
-
585
- return $customer;
586
- }
587
-
588
- /**
589
- * Get appointment data
590
- *
591
- * @param stdClass $appointment
592
- * @param null $user_id
593
- * @param bool $day_view
594
- *
595
- * @return array
596
  */
597
- private function getAppointment( stdClass $appointment, $user_id = null, $day_view = false ) {
598
- $startDate = new DateTime( $appointment->start_date );
599
- $endDate = new DateTime( $appointment->end_date );
600
- $customer = $this->getCustomer( $appointment->customer_id );
601
- $desc = array();
602
-
603
- foreach ( array( 'name', 'phone', 'email' ) as $data_entry ) {
604
- $entry_value = $customer->get( $data_entry );
605
- if ( $entry_value ) {
606
- $desc[] = '<div class="wc-employee">' . esc_html( $entry_value ) . '</div>';
607
- }
608
- }
609
-
610
- if ($appointment->notes) {
611
- $desc[] = '<div class="wc-notes">' . nl2br( esc_html( $appointment->notes ) ) . '</div>';
612
- }
613
-
614
- $appointment_data = array(
615
- 'id' => $appointment->id,
616
- 'start' => $startDate->format( 'm/d/Y H:i' ),
617
- 'end' => $endDate->format( 'm/d/Y H:i' ),
618
- 'title' => $appointment->title ? esc_html( $appointment->title ) : __( 'Untitled', 'ab' ),
619
- 'desc' => implode('', $desc),
620
- 'color' => $appointment->color,
621
- );
622
- // if needed to be rendered for a specific user
623
- // pass the the user id
624
- if ( null !== $user_id ) {
625
- $appointment_data['userId'] = $user_id;
626
- }
627
- return $appointment_data;
628
  }
629
 
630
  /**
631
- * Get free busy data
632
- *
633
- * @param DateTime $startDate
634
- * @param DateTime $endDate
635
- * @param $free
636
- * @param null $user_id
637
- *
638
- * @return array
639
- */
640
- private function getFreeBusy( DateTime $startDate, DateTime $endDate, $free, $user_id = null ) {
641
- $freebusy_data = array(
642
- 'start' => $startDate->format( 'm/d/Y H:i' ),
643
- 'end' => $endDate->format( 'm/d/Y H:i' ),
644
- 'free' => $free
645
- );
646
- // if needed to be rendered for a specific user
647
- // pass the the user id
648
- if ( null !== $user_id ) {
649
- $freebusy_data['userId'] = $user_id;
650
- }
651
- return $freebusy_data;
652
- }
653
-
654
- private function dateFormatTojQueryUIDatePickerFormat() {
655
- $chars = array(
656
- // Day
657
- 'd' => 'dd', 'j' => 'd', 'l' => 'DD', 'D' => 'D',
658
- // Month
659
- 'm' => 'mm', 'n' => 'm', 'F' => 'MM', 'M' => 'M',
660
- // Year
661
- 'Y' => 'yy', 'y' => 'y',
662
- );
663
-
664
- return strtr((string)get_option('date_format'), $chars);
665
- }
666
-
667
- /**
668
  * Override parent method to add 'wp_ajax_ab_' prefix
669
  * so current 'execute*' methods look nicer.
 
 
670
  */
671
- protected function registerWpActions( $prefix = '' ) {
 
672
  parent::registerWpActions( 'wp_ajax_ab_' );
673
  }
 
674
  }
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(
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/forms/AB_AppointmentForm.php CHANGED
@@ -1,8 +1,4 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
-
5
- include dirname(__FILE__) . '/../../../../lib/entities/AB_Appointment.php';
6
 
7
  /**
8
  * Class AB_AppointmentForm
@@ -12,12 +8,15 @@ class AB_AppointmentForm extends AB_Form {
12
  /**
13
  * Constructor.
14
  */
15
- public function __construct() {
 
16
  parent::$entity_class = 'AB_Appointment';
17
  parent::__construct();
18
  }
19
 
20
- public function configure() {
 
21
  //$this->setFields( array() );
22
  }
 
23
  }
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
 
 
2
 
3
  /**
4
  * Class AB_AppointmentForm
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 ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 CHANGED
@@ -1,4 +1,3 @@
1
-
2
  /* calendar nav */
3
  .ab-calendar-day, .ab-calendar-week, .ab-calendar-today, .btn-list, .btn-dropdown {
4
  display: inline-block;
@@ -33,9 +32,7 @@
33
  .ab-calendar-week { border-bottom-left-radius: 0px; border-top-left-radius: 0px; margin-left: -3px; margin-top: 0; border-left: 0; }
34
  .ab-calendar-today { margin: 0 5px; border-radius: 3px; }
35
  .ab-nav-calendar { height: 30px; }
36
- .ab-nav-calendar .ab-button-active {
37
- background: #1f6a8c;
38
- }
39
  .ab-nav-calendar .ab-popup-wrapper { margin-left: 10px; }
40
  .ab-popup-table td { white-space: nowrap; padding: 5px; border: 0!important; }
41
  .ab-popup-table td > p { margin: 0; }
@@ -68,15 +65,14 @@
68
  .ab-icon-button.ab-services {
69
  background: url("../images/box-small.png") 2px 2px no-repeat;
70
  }
71
- .ab-icon-button.ab-staff {
72
- background: url("../images/user-white.png") 0 2px no-repeat;
73
- }
74
 
75
  /** week picker */
76
- .ab-week-picker-wrapper { width: auto; position: relative; margin-right: 15px; }
77
- .ab-week-picker-wrapper .ab-week-picker { clear: both; left: -1px; position: absolute; top: 30px; z-index: 1; display: none; }
 
78
  .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; }
79
- #appendedPrependedInput { width: 340px; background-color: #FFFFFF; cursor: default; }
80
 
81
  /** appointment dialog */
82
  .ab-appointment-popup .ui-widget-header {
@@ -89,7 +85,6 @@
89
  line-height: 26px!important;
90
  padding: 9px 15px!important;
91
  }
92
- .ab-appointment-popup .ui-widget-header .ui-state-hover { background: none!important; border: none!important; }
93
 
94
  .ab-appointment-popup .loading-indicator img { display: block; margin: 20px auto; }
95
  .ab-appointment-popup .dialog-button-wrapper { }
@@ -103,9 +98,17 @@
103
 
104
  /** calendars */
105
  #day_calendar_wrapper { margin-top: 55px; }
106
-
107
- #date_interval_warning_msg { color: #21759B }
108
- #date_interval_not_available_msg { color: red }
 
 
 
 
 
 
 
 
109
 
110
  /** wp elements */
111
  #update-nag, .update-nag, #wpfooter { display: none; }
@@ -144,32 +147,69 @@
144
  -moz-border-top-right-radius: 10px;
145
  -o-border-top-right-radius: 10px;
146
  }
147
- .wc-cal-event {
 
148
  background-color: #DDDDDD;
149
  line-height: 16px!important;
 
150
  }
151
- .wc-cal-event .wc-time {
152
  background-color : #DDDDDD;
153
  border-left-color : #DDDDDD;
154
  border-right-color : #DDDDDD;
155
  border-top-color : #DDDDDD;
156
  border-bottom-color : #ABABAB;
 
157
  }
158
 
159
- .wc-information {
160
- background: url("../images/information.png") no-repeat scroll 1px 2px transparent;
161
- float: right;
162
- height: 16px;
163
- width: 16px;
164
- z-index: 20;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  }
 
 
166
 
167
- .wc-service-name, .wc-employee {
168
- padding: 3px 0;
 
169
  }
170
 
171
- .right-margin { margin-right: 10px; }
172
- .pagination { margin: 0!important; }
173
 
174
- .nav-tabs > .active > a { background: #1f6a8c!important; color: white!important; border-left: 0!important; }
175
- .ab-date-calendar { padding-left: 25px!important; background: url("../images/calendar.png") 5px 8px no-repeat; }
 
1
  /* calendar nav */
2
  .ab-calendar-day, .ab-calendar-week, .ab-calendar-today, .btn-list, .btn-dropdown {
3
  display: inline-block;
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; }
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 {
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 { }
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; }
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/chosen.css DELETED
@@ -1,437 +0,0 @@
1
- /*!
2
- Chosen, a Select Box Enhancer for jQuery and Prototype
3
- by Patrick Filler for Harvest, http://getharvest.com
4
-
5
- Version 1.2.0
6
- Full source at https://github.com/harvesthq/chosen
7
- Copyright (c) 2011-2014 Harvest http://getharvest.com
8
-
9
- MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
10
- This file is generated by `grunt build`, do not edit it by hand.
11
- */
12
-
13
- /* @group Base */
14
- .chosen-container {
15
- position: relative;
16
- display: inline-block;
17
- vertical-align: middle;
18
- font-size: 13px;
19
- zoom: 1;
20
- *display: inline;
21
- -webkit-user-select: none;
22
- -moz-user-select: none;
23
- user-select: none;
24
- }
25
- .chosen-container * {
26
- -webkit-box-sizing: border-box;
27
- -moz-box-sizing: border-box;
28
- box-sizing: border-box;
29
- }
30
- .chosen-container .chosen-drop {
31
- position: absolute;
32
- top: 100%;
33
- left: -9999px;
34
- z-index: 1010;
35
- width: 100%;
36
- border: 1px solid #aaa;
37
- border-top: 0;
38
- background: #fff;
39
- box-shadow: 0 4px 5px rgba(0, 0, 0, 0.15);
40
- }
41
- .chosen-container.chosen-with-drop .chosen-drop {
42
- left: 0;
43
- }
44
- .chosen-container a {
45
- cursor: pointer;
46
- }
47
-
48
- /* @end */
49
- /* @group Single Chosen */
50
- .chosen-container-single .chosen-single {
51
- position: relative;
52
- display: block;
53
- overflow: hidden;
54
- padding: 0 0 0 8px;
55
- height: 25px;
56
- border: 1px solid #aaa;
57
- border-radius: 5px;
58
- background-color: #fff;
59
- background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #ffffff), color-stop(50%, #f6f6f6), color-stop(52%, #eeeeee), color-stop(100%, #f4f4f4));
60
- background: -webkit-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
61
- background: -moz-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
62
- background: -o-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
63
- background: linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
64
- background-clip: padding-box;
65
- box-shadow: 0 0 3px white inset, 0 1px 1px rgba(0, 0, 0, 0.1);
66
- color: #444;
67
- text-decoration: none;
68
- white-space: nowrap;
69
- line-height: 24px;
70
- }
71
- .chosen-container-single .chosen-default {
72
- color: #999;
73
- }
74
- .chosen-container-single .chosen-single span {
75
- display: block;
76
- overflow: hidden;
77
- margin-right: 26px;
78
- text-overflow: ellipsis;
79
- white-space: nowrap;
80
- }
81
- .chosen-container-single .chosen-single-with-deselect span {
82
- margin-right: 38px;
83
- }
84
- .chosen-container-single .chosen-single abbr {
85
- position: absolute;
86
- top: 6px;
87
- right: 26px;
88
- display: block;
89
- width: 12px;
90
- height: 12px;
91
- background: url('chosen-sprite.png') -42px 1px no-repeat;
92
- font-size: 1px;
93
- }
94
- .chosen-container-single .chosen-single abbr:hover {
95
- background-position: -42px -10px;
96
- }
97
- .chosen-container-single.chosen-disabled .chosen-single abbr:hover {
98
- background-position: -42px -10px;
99
- }
100
- .chosen-container-single .chosen-single div {
101
- position: absolute;
102
- top: 0;
103
- right: 0;
104
- display: block;
105
- width: 18px;
106
- height: 100%;
107
- }
108
- .chosen-container-single .chosen-single div b {
109
- display: block;
110
- width: 100%;
111
- height: 100%;
112
- background: url('chosen-sprite.png') no-repeat 0px 2px;
113
- }
114
- .chosen-container-single .chosen-search {
115
- position: relative;
116
- z-index: 1010;
117
- margin: 0;
118
- padding: 3px 4px;
119
- white-space: nowrap;
120
- }
121
- .chosen-container-single .chosen-search input[type="text"] {
122
- margin: 1px 0;
123
- padding: 4px 20px 4px 5px;
124
- width: 100%;
125
- height: auto;
126
- outline: 0;
127
- border: 1px solid #aaa;
128
- background: white url('chosen-sprite.png') no-repeat 100% -20px;
129
- background: url('chosen-sprite.png') no-repeat 100% -20px;
130
- font-size: 1em;
131
- font-family: sans-serif;
132
- line-height: normal;
133
- border-radius: 0;
134
- }
135
- .chosen-container-single .chosen-drop {
136
- margin-top: -1px;
137
- border-radius: 0 0 4px 4px;
138
- background-clip: padding-box;
139
- }
140
- .chosen-container-single.chosen-container-single-nosearch .chosen-search {
141
- position: absolute;
142
- left: -9999px;
143
- }
144
-
145
- /* @end */
146
- /* @group Results */
147
- .chosen-container .chosen-results {
148
- color: #444;
149
- position: relative;
150
- overflow-x: hidden;
151
- overflow-y: auto;
152
- margin: 0 4px 4px 0;
153
- padding: 0 0 0 4px;
154
- max-height: 240px;
155
- -webkit-overflow-scrolling: touch;
156
- }
157
- .chosen-container .chosen-results li {
158
- display: none;
159
- margin: 0;
160
- padding: 5px 6px;
161
- list-style: none;
162
- line-height: 15px;
163
- word-wrap: break-word;
164
- -webkit-touch-callout: none;
165
- }
166
- .chosen-container .chosen-results li.active-result {
167
- display: list-item;
168
- cursor: pointer;
169
- }
170
- .chosen-container .chosen-results li.disabled-result {
171
- display: list-item;
172
- color: #ccc;
173
- cursor: default;
174
- }
175
- .chosen-container .chosen-results li.highlighted {
176
- background-color: #3875d7;
177
- background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc));
178
- background-image: -webkit-linear-gradient(#3875d7 20%, #2a62bc 90%);
179
- background-image: -moz-linear-gradient(#3875d7 20%, #2a62bc 90%);
180
- background-image: -o-linear-gradient(#3875d7 20%, #2a62bc 90%);
181
- background-image: linear-gradient(#3875d7 20%, #2a62bc 90%);
182
- color: #fff;
183
- }
184
- .chosen-container .chosen-results li.no-results {
185
- color: #777;
186
- display: list-item;
187
- background: #f4f4f4;
188
- }
189
- .chosen-container .chosen-results li.group-result {
190
- display: list-item;
191
- font-weight: bold;
192
- cursor: default;
193
- }
194
- .chosen-container .chosen-results li.group-option {
195
- padding-left: 15px;
196
- }
197
- .chosen-container .chosen-results li em {
198
- font-style: normal;
199
- text-decoration: underline;
200
- }
201
-
202
- /* @end */
203
- /* @group Multi Chosen */
204
- .chosen-container-multi .chosen-choices {
205
- position: relative;
206
- overflow: hidden;
207
- margin: 0;
208
- padding: 0 5px;
209
- width: 100%;
210
- height: auto !important;
211
- height: 1%;
212
- border: 1px solid #aaa;
213
- background-color: #fff;
214
- background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
215
- background-image: -webkit-linear-gradient(#eeeeee 1%, #ffffff 15%);
216
- background-image: -moz-linear-gradient(#eeeeee 1%, #ffffff 15%);
217
- background-image: -o-linear-gradient(#eeeeee 1%, #ffffff 15%);
218
- background-image: linear-gradient(#eeeeee 1%, #ffffff 15%);
219
- cursor: text;
220
- }
221
- .chosen-container-multi .chosen-choices li {
222
- float: left;
223
- list-style: none;
224
- }
225
- .chosen-container-multi .chosen-choices li.search-field {
226
- margin: 0;
227
- padding: 0;
228
- white-space: nowrap;
229
- }
230
- .chosen-container-multi .chosen-choices li.search-field input[type="text"] {
231
- margin: 1px 0;
232
- padding: 0;
233
- height: 25px;
234
- outline: 0;
235
- border: 0 !important;
236
- background: transparent !important;
237
- box-shadow: none;
238
- color: #999;
239
- font-size: 100%;
240
- font-family: sans-serif;
241
- line-height: normal;
242
- border-radius: 0;
243
- }
244
- .chosen-container-multi .chosen-choices li.search-choice {
245
- position: relative;
246
- margin: 3px 5px 3px 0;
247
- padding: 3px 20px 3px 5px;
248
- border: 1px solid #aaa;
249
- max-width: 100%;
250
- border-radius: 3px;
251
- background-color: #eeeeee;
252
- background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
253
- background-image: -webkit-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
254
- background-image: -moz-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
255
- background-image: -o-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
256
- background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
257
- background-size: 100% 19px;
258
- background-repeat: repeat-x;
259
- background-clip: padding-box;
260
- box-shadow: 0 0 2px white inset, 0 1px 0 rgba(0, 0, 0, 0.05);
261
- color: #333;
262
- line-height: 13px;
263
- cursor: default;
264
- }
265
- .chosen-container-multi .chosen-choices li.search-choice span {
266
- word-wrap: break-word;
267
- }
268
- .chosen-container-multi .chosen-choices li.search-choice .search-choice-close {
269
- position: absolute;
270
- top: 4px;
271
- right: 3px;
272
- display: block;
273
- width: 12px;
274
- height: 12px;
275
- background: url('chosen-sprite.png') -42px 1px no-repeat;
276
- font-size: 1px;
277
- }
278
- .chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover {
279
- background-position: -42px -10px;
280
- }
281
- .chosen-container-multi .chosen-choices li.search-choice-disabled {
282
- padding-right: 5px;
283
- border: 1px solid #ccc;
284
- background-color: #e4e4e4;
285
- background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
286
- background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
287
- background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
288
- background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
289
- background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
290
- color: #666;
291
- }
292
- .chosen-container-multi .chosen-choices li.search-choice-focus {
293
- background: #d4d4d4;
294
- }
295
- .chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close {
296
- background-position: -42px -10px;
297
- }
298
- .chosen-container-multi .chosen-results {
299
- margin: 0;
300
- padding: 0;
301
- }
302
- .chosen-container-multi .chosen-drop .result-selected {
303
- display: list-item;
304
- color: #ccc;
305
- cursor: default;
306
- }
307
-
308
- /* @end */
309
- /* @group Active */
310
- .chosen-container-active .chosen-single {
311
- border: 1px solid #5897fb;
312
- box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
313
- }
314
- .chosen-container-active.chosen-with-drop .chosen-single {
315
- border: 1px solid #aaa;
316
- -moz-border-radius-bottomright: 0;
317
- border-bottom-right-radius: 0;
318
- -moz-border-radius-bottomleft: 0;
319
- border-bottom-left-radius: 0;
320
- background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #eeeeee), color-stop(80%, #ffffff));
321
- background-image: -webkit-linear-gradient(#eeeeee 20%, #ffffff 80%);
322
- background-image: -moz-linear-gradient(#eeeeee 20%, #ffffff 80%);
323
- background-image: -o-linear-gradient(#eeeeee 20%, #ffffff 80%);
324
- background-image: linear-gradient(#eeeeee 20%, #ffffff 80%);
325
- box-shadow: 0 1px 0 #fff inset;
326
- }
327
- .chosen-container-active.chosen-with-drop .chosen-single div {
328
- border-left: none;
329
- background: transparent;
330
- }
331
- .chosen-container-active.chosen-with-drop .chosen-single div b {
332
- background-position: -18px 2px;
333
- }
334
- .chosen-container-active .chosen-choices {
335
- border: 1px solid #5897fb;
336
- box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
337
- }
338
- .chosen-container-active .chosen-choices li.search-field input[type="text"] {
339
- color: #222 !important;
340
- }
341
-
342
- /* @end */
343
- /* @group Disabled Support */
344
- .chosen-disabled {
345
- opacity: 0.5 !important;
346
- cursor: default;
347
- }
348
- .chosen-disabled .chosen-single {
349
- cursor: default;
350
- }
351
- .chosen-disabled .chosen-choices .search-choice .search-choice-close {
352
- cursor: default;
353
- }
354
-
355
- /* @end */
356
- /* @group Right to Left */
357
- .chosen-rtl {
358
- text-align: right;
359
- }
360
- .chosen-rtl .chosen-single {
361
- overflow: visible;
362
- padding: 0 8px 0 0;
363
- }
364
- .chosen-rtl .chosen-single span {
365
- margin-right: 0;
366
- margin-left: 26px;
367
- direction: rtl;
368
- }
369
- .chosen-rtl .chosen-single-with-deselect span {
370
- margin-left: 38px;
371
- }
372
- .chosen-rtl .chosen-single div {
373
- right: auto;
374
- left: 3px;
375
- }
376
- .chosen-rtl .chosen-single abbr {
377
- right: auto;
378
- left: 26px;
379
- }
380
- .chosen-rtl .chosen-choices li {
381
- float: right;
382
- }
383
- .chosen-rtl .chosen-choices li.search-field input[type="text"] {
384
- direction: rtl;
385
- }
386
- .chosen-rtl .chosen-choices li.search-choice {
387
- margin: 3px 5px 3px 0;
388
- padding: 3px 5px 3px 19px;
389
- }
390
- .chosen-rtl .chosen-choices li.search-choice .search-choice-close {
391
- right: auto;
392
- left: 4px;
393
- }
394
- .chosen-rtl.chosen-container-single-nosearch .chosen-search,
395
- .chosen-rtl .chosen-drop {
396
- left: 9999px;
397
- }
398
- .chosen-rtl.chosen-container-single .chosen-results {
399
- margin: 0 0 4px 4px;
400
- padding: 0 4px 0 0;
401
- }
402
- .chosen-rtl .chosen-results li.group-option {
403
- padding-right: 15px;
404
- padding-left: 0;
405
- }
406
- .chosen-rtl.chosen-container-active.chosen-with-drop .chosen-single div {
407
- border-right: none;
408
- }
409
- .chosen-rtl .chosen-search input[type="text"] {
410
- padding: 4px 5px 4px 20px;
411
- background: white url('chosen-sprite.png') no-repeat -30px -20px;
412
- background: url('chosen-sprite.png') no-repeat -30px -20px;
413
- direction: rtl;
414
- }
415
- .chosen-rtl.chosen-container-single .chosen-single div b {
416
- background-position: 6px 2px;
417
- }
418
- .chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b {
419
- background-position: -12px 2px;
420
- }
421
-
422
- /* @end */
423
- /* @group Retina compatibility */
424
- @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-resolution: 144dpi) {
425
- .chosen-rtl .chosen-search input[type="text"],
426
- .chosen-container-single .chosen-single abbr,
427
- .chosen-container-single .chosen-single div b,
428
- .chosen-container-single .chosen-search input[type="text"],
429
- .chosen-container-multi .chosen-choices .search-choice .search-choice-close,
430
- .chosen-container .chosen-results-scroll-down span,
431
- .chosen-container .chosen-results-scroll-up span {
432
- background-image: url('chosen-sprite@2x.png') !important;
433
- background-size: 52px 37px !important;
434
- background-repeat: no-repeat !important;
435
- }
436
- }
437
- /* @end */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/resources/css/fullcalendar.min.css ADDED
@@ -0,0 +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:"="}
backend/modules/calendar/resources/css/jquery-ui-1.10.1.css DELETED
@@ -1,1174 +0,0 @@
1
- /*! jQuery UI - v1.10.1 - 2013-02-15
2
- * http://jqueryui.com
3
- * Includes: jquery.ui.core.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css
4
- * Copyright (c) 2013 jQuery Foundation and other contributors Licensed MIT */
5
-
6
- /* Layout helpers
7
- ----------------------------------*/
8
- .ui-helper-hidden {
9
- display: none;
10
- }
11
- .ui-helper-hidden-accessible {
12
- border: 0;
13
- clip: rect(0 0 0 0);
14
- height: 1px;
15
- margin: -1px;
16
- overflow: hidden;
17
- padding: 0;
18
- position: absolute;
19
- width: 1px;
20
- }
21
- .ui-helper-reset {
22
- margin: 0;
23
- padding: 0;
24
- border: 0;
25
- outline: 0;
26
- line-height: 1.3;
27
- text-decoration: none;
28
- font-size: 100%;
29
- list-style: none;
30
- }
31
- .ui-helper-clearfix:before,
32
- .ui-helper-clearfix:after {
33
- content: "";
34
- display: table;
35
- border-collapse: collapse;
36
- }
37
- .ui-helper-clearfix:after {
38
- clear: both;
39
- }
40
- .ui-helper-clearfix {
41
- min-height: 0; /* support: IE7 */
42
- }
43
- .ui-helper-zfix {
44
- width: 100%;
45
- height: 100%;
46
- top: 0;
47
- left: 0;
48
- position: absolute;
49
- opacity: 0;
50
- filter:Alpha(Opacity=0);
51
- }
52
-
53
- .ui-front {
54
- z-index: 100;
55
- }
56
-
57
-
58
- /* Interaction Cues
59
- ----------------------------------*/
60
- .ui-state-disabled {
61
- cursor: default !important;
62
- }
63
-
64
-
65
- /* Icons
66
- ----------------------------------*/
67
-
68
- /* states and images */
69
- .ui-icon {
70
- display: block;
71
- text-indent: -99999px;
72
- overflow: hidden;
73
- background-repeat: no-repeat;
74
- }
75
-
76
-
77
- /* Misc visuals
78
- ----------------------------------*/
79
-
80
- /* Overlays */
81
- .ui-widget-overlay {
82
- position: fixed;
83
- top: 0;
84
- left: 0;
85
- width: 100%;
86
- height: 100%;
87
- }
88
- .ui-accordion .ui-accordion-header {
89
- display: block;
90
- cursor: pointer;
91
- position: relative;
92
- margin-top: 2px;
93
- padding: .5em .5em .5em .7em;
94
- min-height: 0; /* support: IE7 */
95
- }
96
- .ui-accordion .ui-accordion-icons {
97
- padding-left: 2.2em;
98
- }
99
- .ui-accordion .ui-accordion-noicons {
100
- padding-left: .7em;
101
- }
102
- .ui-accordion .ui-accordion-icons .ui-accordion-icons {
103
- padding-left: 2.2em;
104
- }
105
- .ui-accordion .ui-accordion-header .ui-accordion-header-icon {
106
- position: absolute;
107
- left: .5em;
108
- top: 50%;
109
- margin-top: -8px;
110
- }
111
- .ui-accordion .ui-accordion-content {
112
- padding: 1em 2.2em;
113
- border-top: 0;
114
- overflow: auto;
115
- }
116
- .ui-autocomplete {
117
- position: absolute;
118
- top: 0;
119
- left: 0;
120
- cursor: default;
121
- }
122
- .ui-button {
123
- display: inline-block;
124
- position: relative;
125
- padding: 0;
126
- line-height: normal;
127
- margin-right: .1em;
128
- cursor: pointer;
129
- vertical-align: middle;
130
- text-align: center;
131
- overflow: visible; /* removes extra width in IE */
132
- }
133
- .ui-button,
134
- .ui-button:link,
135
- .ui-button:visited,
136
- .ui-button:hover,
137
- .ui-button:active {
138
- text-decoration: none;
139
- }
140
- /* to make room for the icon, a width needs to be set here */
141
- .ui-button-icon-only {
142
- width: 2.2em;
143
- }
144
- /* button elements seem to need a little more width */
145
- button.ui-button-icon-only {
146
- width: 2.4em;
147
- }
148
- .ui-button-icons-only {
149
- width: 3.4em;
150
- }
151
- button.ui-button-icons-only {
152
- width: 3.7em;
153
- }
154
-
155
- /* button text element */
156
- .ui-button .ui-button-text {
157
- display: block;
158
- line-height: normal;
159
- }
160
- .ui-button-text-only .ui-button-text {
161
- padding: .4em 1em;
162
- }
163
- .ui-button-icon-only .ui-button-text,
164
- .ui-button-icons-only .ui-button-text {
165
- padding: .4em;
166
- text-indent: -9999999px;
167
- }
168
- .ui-button-text-icon-primary .ui-button-text,
169
- .ui-button-text-icons .ui-button-text {
170
- padding: .4em 1em .4em 2.1em;
171
- }
172
- .ui-button-text-icon-secondary .ui-button-text,
173
- .ui-button-text-icons .ui-button-text {
174
- padding: .4em 2.1em .4em 1em;
175
- }
176
- .ui-button-text-icons .ui-button-text {
177
- padding-left: 2.1em;
178
- padding-right: 2.1em;
179
- }
180
- /* no icon support for input elements, provide padding by default */
181
- input.ui-button {
182
- padding: .4em 1em;
183
- }
184
-
185
- /* button icon element(s) */
186
- .ui-button-icon-only .ui-icon,
187
- .ui-button-text-icon-primary .ui-icon,
188
- .ui-button-text-icon-secondary .ui-icon,
189
- .ui-button-text-icons .ui-icon,
190
- .ui-button-icons-only .ui-icon {
191
- position: absolute;
192
- top: 50%;
193
- margin-top: -8px;
194
- }
195
- .ui-button-icon-only .ui-icon {
196
- left: 50%;
197
- margin-left: -8px;
198
- }
199
- .ui-button-text-icon-primary .ui-button-icon-primary,
200
- .ui-button-text-icons .ui-button-icon-primary,
201
- .ui-button-icons-only .ui-button-icon-primary {
202
- left: .5em;
203
- }
204
- .ui-button-text-icon-secondary .ui-button-icon-secondary,
205
- .ui-button-text-icons .ui-button-icon-secondary,
206
- .ui-button-icons-only .ui-button-icon-secondary {
207
- right: .5em;
208
- }
209
-
210
- /* button sets */
211
- .ui-buttonset {
212
- margin-right: 7px;
213
- }
214
- .ui-buttonset .ui-button {
215
- margin-left: 0;
216
- margin-right: -.3em;
217
- }
218
-
219
- /* workarounds */
220
- /* reset extra padding in Firefox, see h5bp.com/l */
221
- input.ui-button::-moz-focus-inner,
222
- button.ui-button::-moz-focus-inner {
223
- border: 0;
224
- padding: 0;
225
- }
226
- .ui-datepicker {
227
- width: 17em;
228
- padding: .2em .2em 0;
229
- display: none;
230
- }
231
- .ui-datepicker .ui-datepicker-header {
232
- position: relative;
233
- padding: .2em 0;
234
- }
235
- .ui-datepicker .ui-datepicker-prev,
236
- .ui-datepicker .ui-datepicker-next {
237
- position: absolute;
238
- top: 2px;
239
- width: 1.8em;
240
- height: 1.8em;
241
- }
242
- .ui-datepicker .ui-datepicker-prev-hover,
243
- .ui-datepicker .ui-datepicker-next-hover {
244
- top: 1px;
245
- }
246
- .ui-datepicker .ui-datepicker-prev {
247
- left: 2px;
248
- }
249
- .ui-datepicker .ui-datepicker-next {
250
- right: 2px;
251
- }
252
- .ui-datepicker .ui-datepicker-prev-hover {
253
- left: 1px;
254
- }
255
- .ui-datepicker .ui-datepicker-next-hover {
256
- right: 1px;
257
- }
258
- .ui-datepicker .ui-datepicker-prev span,
259
- .ui-datepicker .ui-datepicker-next span {
260
- display: block;
261
- position: absolute;
262
- left: 50%;
263
- margin-left: -8px;
264
- top: 50%;
265
- margin-top: -8px;
266
- }
267
- .ui-datepicker .ui-datepicker-title {
268
- margin: 0 2.3em;
269
- line-height: 1.8em;
270
- text-align: center;
271
- }
272
- .ui-datepicker .ui-datepicker-title select {
273
- font-size: 1em;
274
- margin: 1px 0;
275
- }
276
- .ui-datepicker select.ui-datepicker-month-year {
277
- width: 100%;
278
- }
279
- .ui-datepicker select.ui-datepicker-month,
280
- .ui-datepicker select.ui-datepicker-year {
281
- width: 49%;
282
- }
283
- .ui-datepicker table {
284
- width: 100%;
285
- font-size: .9em;
286
- border-collapse: collapse;
287
- margin: 0 0 .4em;
288
- }
289
- .ui-datepicker th {
290
- padding: .7em .3em;
291
- text-align: center;
292
- font-weight: bold;
293
- border: 0;
294
- }
295
- .ui-datepicker td {
296
- border: 0;
297
- padding: 1px;
298
- }
299
- .ui-datepicker td span,
300
- .ui-datepicker td a {
301
- display: block;
302
- padding: .2em;
303
- text-align: right;
304
- text-decoration: none;
305
- }
306
- .ui-datepicker .ui-datepicker-buttonpane {
307
- background-image: none;
308
- margin: .7em 0 0 0;
309
- padding: 0 .2em;
310
- border-left: 0;
311
- border-right: 0;
312
- border-bottom: 0;
313
- }
314
- .ui-datepicker .ui-datepicker-buttonpane button {
315
- float: right;
316
- margin: .5em .2em .4em;
317
- cursor: pointer;
318
- padding: .2em .6em .3em .6em;
319
- width: auto;
320
- overflow: visible;
321
- }
322
- .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
323
- float: left;
324
- }
325
-
326
- /* with multiple calendars */
327
- .ui-datepicker.ui-datepicker-multi {
328
- width: auto;
329
- }
330
- .ui-datepicker-multi .ui-datepicker-group {
331
- float: left;
332
- }
333
- .ui-datepicker-multi .ui-datepicker-group table {
334
- width: 95%;
335
- margin: 0 auto .4em;
336
- }
337
- .ui-datepicker-multi-2 .ui-datepicker-group {
338
- width: 50%;
339
- }
340
- .ui-datepicker-multi-3 .ui-datepicker-group {
341
- width: 33.3%;
342
- }
343
- .ui-datepicker-multi-4 .ui-datepicker-group {
344
- width: 25%;
345
- }
346
- .ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
347
- .ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
348
- border-left-width: 0;
349
- }
350
- .ui-datepicker-multi .ui-datepicker-buttonpane {
351
- clear: left;
352
- }
353
- .ui-datepicker-row-break {
354
- clear: both;
355
- width: 100%;
356
- font-size: 0;
357
- }
358
-
359
- /* RTL support */
360
- .ui-datepicker-rtl {
361
- direction: rtl;
362
- }
363
- .ui-datepicker-rtl .ui-datepicker-prev {
364
- right: 2px;
365
- left: auto;
366
- }
367
- .ui-datepicker-rtl .ui-datepicker-next {
368
- left: 2px;
369
- right: auto;
370
- }
371
- .ui-datepicker-rtl .ui-datepicker-prev:hover {
372
- right: 1px;
373
- left: auto;
374
- }
375
- .ui-datepicker-rtl .ui-datepicker-next:hover {
376
- left: 1px;
377
- right: auto;
378
- }
379
- .ui-datepicker-rtl .ui-datepicker-buttonpane {
380
- clear: right;
381
- }
382
- .ui-datepicker-rtl .ui-datepicker-buttonpane button {
383
- float: left;
384
- }
385
- .ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
386
- .ui-datepicker-rtl .ui-datepicker-group {
387
- float: right;
388
- }
389
- .ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
390
- .ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
391
- border-right-width: 0;
392
- border-left-width: 1px;
393
- }
394
- .ui-dialog {
395
- position: absolute;
396
- top: 0;
397
- left: 0;
398
- padding: .2em;
399
- outline: 0;
400
- }
401
- .ui-dialog .ui-dialog-titlebar {
402
- padding: .4em 1em;
403
- position: relative;
404
- }
405
- .ui-dialog .ui-dialog-title {
406
- float: left;
407
- margin: .1em 0;
408
- white-space: nowrap;
409
- width: 90%;
410
- overflow: hidden;
411
- text-overflow: ellipsis;
412
- }
413
- .ui-dialog .ui-dialog-titlebar-close {
414
- position: absolute;
415
- right: .3em;
416
- top: 50%;
417
- width: 21px;
418
- margin: -10px 0 0 0;
419
- padding: 1px;
420
- height: 20px;
421
- }
422
- .ui-dialog .ui-dialog-content {
423
- position: relative;
424
- border: 0;
425
- padding: .5em 1em;
426
- background: none;
427
- overflow: auto;
428
- }
429
- .ui-dialog .ui-dialog-buttonpane {
430
- text-align: left;
431
- border-width: 1px 0 0 0;
432
- background-image: none;
433
- margin-top: .5em;
434
- padding: .3em 1em .5em .4em;
435
- }
436
- .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
437
- float: right;
438
- }
439
- .ui-dialog .ui-dialog-buttonpane button {
440
- margin: .5em .4em .5em 0;
441
- cursor: pointer;
442
- }
443
- .ui-dialog .ui-resizable-se {
444
- width: 12px;
445
- height: 12px;
446
- right: -5px;
447
- bottom: -5px;
448
- background-position: 16px 16px;
449
- }
450
- .ui-draggable .ui-dialog-titlebar {
451
- cursor: move;
452
- }
453
- .ui-menu {
454
- list-style: none;
455
- padding: 2px;
456
- margin: 0;
457
- display: block;
458
- outline: none;
459
- }
460
- .ui-menu .ui-menu {
461
- margin-top: -3px;
462
- position: absolute;
463
- }
464
- .ui-menu .ui-menu-item {
465
- margin: 0;
466
- padding: 0;
467
- width: 100%;
468
- }
469
- .ui-menu .ui-menu-divider {
470
- margin: 5px -2px 5px -2px;
471
- height: 0;
472
- font-size: 0;
473
- line-height: 0;
474
- border-width: 1px 0 0 0;
475
- }
476
- .ui-menu .ui-menu-item a {
477
- text-decoration: none;
478
- display: block;
479
- padding: 2px .4em;
480
- line-height: 1.5;
481
- min-height: 0; /* support: IE7 */
482
- font-weight: normal;
483
- }
484
- .ui-menu .ui-menu-item a.ui-state-focus,
485
- .ui-menu .ui-menu-item a.ui-state-active {
486
- font-weight: normal;
487
- margin: -1px;
488
- }
489
-
490
- .ui-menu .ui-state-disabled {
491
- font-weight: normal;
492
- margin: .4em 0 .2em;
493
- line-height: 1.5;
494
- }
495
- .ui-menu .ui-state-disabled a {
496
- cursor: default;
497
- }
498
-
499
- /* icon support */
500
- .ui-menu-icons {
501
- position: relative;
502
- }
503
- .ui-menu-icons .ui-menu-item a {
504
- position: relative;
505
- padding-left: 2em;
506
- }
507
-
508
- /* left-aligned */
509
- .ui-menu .ui-icon {
510
- position: absolute;
511
- top: .2em;
512
- left: .2em;
513
- }
514
-
515
- /* right-aligned */
516
- .ui-menu .ui-menu-icon {
517
- position: static;
518
- float: right;
519
- }
520
- .ui-progressbar {
521
- height: 2em;
522
- text-align: left;
523
- overflow: hidden;
524
- }
525
- .ui-progressbar .ui-progressbar-value {
526
- margin: -1px;
527
- height: 100%;
528
- }
529
- .ui-progressbar .ui-progressbar-overlay {
530
- background: url("images/animated-overlay.gif");
531
- height: 100%;
532
- filter: alpha(opacity=25);
533
- opacity: 0.25;
534
- }
535
- .ui-progressbar-indeterminate .ui-progressbar-value {
536
- background-image: none;
537
- }
538
- .ui-resizable {
539
- position: relative;
540
- }
541
- .ui-resizable-handle {
542
- position: absolute;
543
- font-size: 0.1px;
544
- display: block;
545
- }
546
- .ui-resizable-disabled .ui-resizable-handle,
547
- .ui-resizable-autohide .ui-resizable-handle {
548
- display: none;
549
- }
550
- .ui-resizable-n {
551
- cursor: n-resize;
552
- height: 7px;
553
- width: 100%;
554
- top: -5px;
555
- left: 0;
556
- }
557
- .ui-resizable-s {
558
- cursor: s-resize;
559
- height: 7px;
560
- width: 100%;
561
- bottom: -5px;
562
- left: 0;
563
- }
564
- .ui-resizable-e {
565
- cursor: e-resize;
566
- width: 7px;
567
- right: -5px;
568
- top: 0;
569
- height: 100%;
570
- }
571
- .ui-resizable-w {
572
- cursor: w-resize;
573
- width: 7px;
574
- left: -5px;
575
- top: 0;
576
- height: 100%;
577
- }
578
- .ui-resizable-se {
579
- cursor: se-resize;
580
- width: 12px;
581
- height: 12px;
582
- right: 1px;
583
- bottom: 1px;
584
- }
585
- .ui-resizable-sw {
586
- cursor: sw-resize;
587
- width: 9px;
588
- height: 9px;
589
- left: -5px;
590
- bottom: -5px;
591
- }
592
- .ui-resizable-nw {
593
- cursor: nw-resize;
594
- width: 9px;
595
- height: 9px;
596
- left: -5px;
597
- top: -5px;
598
- }
599
- .ui-resizable-ne {
600
- cursor: ne-resize;
601
- width: 9px;
602
- height: 9px;
603
- right: -5px;
604
- top: -5px;
605
- }
606
- .ui-selectable-helper {
607
- position: absolute;
608
- z-index: 100;
609
- border: 1px dotted black;
610
- }
611
- .ui-slider {
612
- position: relative;
613
- text-align: left;
614
- }
615
- .ui-slider .ui-slider-handle {
616
- position: absolute;
617
- z-index: 2;
618
- width: 1.2em;
619
- height: 1.2em;
620
- cursor: default;
621
- }
622
- .ui-slider .ui-slider-range {
623
- position: absolute;
624
- z-index: 1;
625
- font-size: .7em;
626
- display: block;
627
- border: 0;
628
- background-position: 0 0;
629
- }
630
-
631
- /* For IE8 - See #6727 */
632
- .ui-slider.ui-state-disabled .ui-slider-handle,
633
- .ui-slider.ui-state-disabled .ui-slider-range {
634
- filter: inherit;
635
- }
636
-
637
- .ui-slider-horizontal {
638
- height: .8em;
639
- }
640
- .ui-slider-horizontal .ui-slider-handle {
641
- top: -.3em;
642
- margin-left: -.6em;
643
- }
644
- .ui-slider-horizontal .ui-slider-range {
645
- top: 0;
646
- height: 100%;
647
- }
648
- .ui-slider-horizontal .ui-slider-range-min {
649
- left: 0;
650
- }
651
- .ui-slider-horizontal .ui-slider-range-max {
652
- right: 0;
653
- }
654
-
655
- .ui-slider-vertical {
656
- width: .8em;
657
- height: 100px;
658
- }
659
- .ui-slider-vertical .ui-slider-handle {
660
- left: -.3em;
661
- margin-left: 0;
662
- margin-bottom: -.6em;
663
- }
664
- .ui-slider-vertical .ui-slider-range {
665
- left: 0;
666
- width: 100%;
667
- }
668
- .ui-slider-vertical .ui-slider-range-min {
669
- bottom: 0;
670
- }
671
- .ui-slider-vertical .ui-slider-range-max {
672
- top: 0;
673
- }
674
- .ui-spinner {
675
- position: relative;
676
- display: inline-block;
677
- overflow: hidden;
678
- padding: 0;
679
- vertical-align: middle;
680
- }
681
- .ui-spinner-input {
682
- border: none;
683
- background: none;
684
- color: inherit;
685
- padding: 0;
686
- margin: .2em 0;
687
- vertical-align: middle;
688
- margin-left: .4em;
689
- margin-right: 22px;
690
- }
691
- .ui-spinner-button {
692
- width: 16px;
693
- height: 50%;
694
- font-size: .5em;
695
- padding: 0;
696
- margin: 0;
697
- text-align: center;
698
- position: absolute;
699
- cursor: default;
700
- display: block;
701
- overflow: hidden;
702
- right: 0;
703
- }
704
- /* more specificity required here to overide default borders */
705
- .ui-spinner a.ui-spinner-button {
706
- border-top: none;
707
- border-bottom: none;
708
- border-right: none;
709
- }
710
- /* vertical centre icon */
711
- .ui-spinner .ui-icon {
712
- position: absolute;
713
- margin-top: -8px;
714
- top: 50%;
715
- left: 0;
716
- }
717
- .ui-spinner-up {
718
- top: 0;
719
- }
720
- .ui-spinner-down {
721
- bottom: 0;
722
- }
723
-
724
- /* TR overrides */
725
- .ui-spinner .ui-icon-triangle-1-s {
726
- /* need to fix icons sprite */
727
- background-position: -65px -16px;
728
- }
729
- .ui-tabs {
730
- position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
731
- padding: .2em;
732
- }
733
- .ui-tabs .ui-tabs-nav {
734
- margin: 0;
735
- padding: .2em .2em 0;
736
- }
737
- .ui-tabs .ui-tabs-nav li {
738
- list-style: none;
739
- float: left;
740
- position: relative;
741
- top: 0;
742
- margin: 1px .2em 0 0;
743
- border-bottom: 0;
744
- padding: 0;
745
- white-space: nowrap;
746
- }
747
- .ui-tabs .ui-tabs-nav li a {
748
- float: left;
749
- padding: .5em 1em;
750
- text-decoration: none;
751
- }
752
- .ui-tabs .ui-tabs-nav li.ui-tabs-active {
753
- margin-bottom: -1px;
754
- padding-bottom: 1px;
755
- }
756
- .ui-tabs .ui-tabs-nav li.ui-tabs-active a,
757
- .ui-tabs .ui-tabs-nav li.ui-state-disabled a,
758
- .ui-tabs .ui-tabs-nav li.ui-tabs-loading a {
759
- cursor: text;
760
- }
761
- .ui-tabs .ui-tabs-nav li a, /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
762
- .ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a {
763
- cursor: pointer;
764
- }
765
- .ui-tabs .ui-tabs-panel {
766
- display: block;
767
- border-width: 0;
768
- padding: 1em 1.4em;
769
- background: none;
770
- }
771
- .ui-tooltip {
772
- padding: 8px;
773
- position: absolute;
774
- z-index: 9999;
775
- max-width: 300px;
776
- -webkit-box-shadow: 0 0 5px #aaa;
777
- box-shadow: 0 0 5px #aaa;
778
- }
779
- body .ui-tooltip {
780
- border-width: 2px;
781
- }
782
-
783
- /* Component containers
784
- ----------------------------------*/
785
- .ui-widget {
786
- font-family: Verdana,Arial,sans-serif/*{ffDefault}*/;
787
- font-size: 1.1em/*{fsDefault}*/;
788
- }
789
- .ui-widget .ui-widget {
790
- font-size: 1em;
791
- }
792
- .ui-widget input,
793
- .ui-widget select,
794
- .ui-widget textarea,
795
- .ui-widget button {
796
- font-family: Verdana,Arial,sans-serif/*{ffDefault}*/;
797
- font-size: 1em;
798
- }
799
- .ui-widget-content {
800
- border: 1px solid #aaaaaa/*{borderColorContent}*/;
801
- background: #ffffff/*{bgColorContent}*/ url(images/ui-bg_flat_75_ffffff_40x100.png)/*{bgImgUrlContent}*/ 50%/*{bgContentXPos}*/ 50%/*{bgContentYPos}*/ repeat-x/*{bgContentRepeat}*/;
802
- color: #222222/*{fcContent}*/;
803
- }
804
- .ui-widget-content a {
805
- color: #222222/*{fcContent}*/;
806
- }
807
- .ui-widget-header {
808
- border: 1px solid #aaaaaa/*{borderColorHeader}*/;
809
- background: #cccccc/*{bgColorHeader}*/ url(images/ui-bg_highlight-soft_75_cccccc_1x100.png)/*{bgImgUrlHeader}*/ 50%/*{bgHeaderXPos}*/ 50%/*{bgHeaderYPos}*/ repeat-x/*{bgHeaderRepeat}*/;
810
- color: #222222/*{fcHeader}*/;
811
- font-weight: bold;
812
- }
813
- .ui-widget-header a {
814
- color: #222222/*{fcHeader}*/;
815
- }
816
-
817
- /* Interaction states
818
- ----------------------------------*/
819
- .ui-state-default,
820
- .ui-widget-content .ui-state-default,
821
- .ui-widget-header .ui-state-default {
822
- border: 1px solid #d3d3d3/*{borderColorDefault}*/;
823
- background: #e6e6e6/*{bgColorDefault}*/ url(images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/;
824
- font-weight: normal/*{fwDefault}*/;
825
- color: #555555/*{fcDefault}*/;
826
- }
827
- .ui-state-default a,
828
- .ui-state-default a:link,
829
- .ui-state-default a:visited {
830
- color: #555555/*{fcDefault}*/;
831
- text-decoration: none;
832
- }
833
- .ui-state-hover,
834
- .ui-widget-content .ui-state-hover,
835
- .ui-widget-header .ui-state-hover,
836
- .ui-state-focus,
837
- .ui-widget-content .ui-state-focus,
838
- .ui-widget-header .ui-state-focus {
839
- border: 1px solid #999999/*{borderColorHover}*/;
840
- background: #dadada/*{bgColorHover}*/ url(images/ui-bg_glass_75_dadada_1x400.png)/*{bgImgUrlHover}*/ 50%/*{bgHoverXPos}*/ 50%/*{bgHoverYPos}*/ repeat-x/*{bgHoverRepeat}*/;
841
- font-weight: normal/*{fwDefault}*/;
842
- color: #212121/*{fcHover}*/;
843
- }
844
- .ui-state-hover a,
845
- .ui-state-hover a:hover,
846
- .ui-state-hover a:link,
847
- .ui-state-hover a:visited {
848
- color: #212121/*{fcHover}*/;
849
- text-decoration: none;
850
- }
851
- .ui-state-active,
852
- .ui-widget-content .ui-state-active,
853
- .ui-widget-header .ui-state-active {
854
- border: 1px solid #aaaaaa/*{borderColorActive}*/;
855
- background: #ffffff/*{bgColorActive}*/ url(images/ui-bg_glass_65_ffffff_1x400.png)/*{bgImgUrlActive}*/ 50%/*{bgActiveXPos}*/ 50%/*{bgActiveYPos}*/ repeat-x/*{bgActiveRepeat}*/;
856
- font-weight: normal/*{fwDefault}*/;
857
- color: #212121/*{fcActive}*/;
858
- }
859
- .ui-state-active a,
860
- .ui-state-active a:link,
861
- .ui-state-active a:visited {
862
- color: #212121/*{fcActive}*/;
863
- text-decoration: none;
864
- }
865
-
866
- /* Interaction Cues
867
- ----------------------------------*/
868
- .ui-state-highlight,
869
- .ui-widget-content .ui-state-highlight,
870
- .ui-widget-header .ui-state-highlight {
871
- border: 1px solid #fcefa1/*{borderColorHighlight}*/;
872
- background: #fbf9ee/*{bgColorHighlight}*/ url(images/ui-bg_glass_55_fbf9ee_1x400.png)/*{bgImgUrlHighlight}*/ 50%/*{bgHighlightXPos}*/ 50%/*{bgHighlightYPos}*/ repeat-x/*{bgHighlightRepeat}*/;
873
- color: #363636/*{fcHighlight}*/;
874
- }
875
- .ui-state-highlight a,
876
- .ui-widget-content .ui-state-highlight a,
877
- .ui-widget-header .ui-state-highlight a {
878
- color: #363636/*{fcHighlight}*/;
879
- }
880
- .ui-state-error,
881
- .ui-widget-content .ui-state-error,
882
- .ui-widget-header .ui-state-error {
883
- border: 1px solid #cd0a0a/*{borderColorError}*/;
884
- background: #fef1ec/*{bgColorError}*/ url(images/ui-bg_glass_95_fef1ec_1x400.png)/*{bgImgUrlError}*/ 50%/*{bgErrorXPos}*/ 50%/*{bgErrorYPos}*/ repeat-x/*{bgErrorRepeat}*/;
885
- color: #cd0a0a/*{fcError}*/;
886
- }
887
- .ui-state-error a,
888
- .ui-widget-content .ui-state-error a,
889
- .ui-widget-header .ui-state-error a {
890
- color: #cd0a0a/*{fcError}*/;
891
- }
892
- .ui-state-error-text,
893
- .ui-widget-content .ui-state-error-text,
894
- .ui-widget-header .ui-state-error-text {
895
- color: #cd0a0a/*{fcError}*/;
896
- }
897
- .ui-priority-primary,
898
- .ui-widget-content .ui-priority-primary,
899
- .ui-widget-header .ui-priority-primary {
900
- font-weight: bold;
901
- }
902
- .ui-priority-secondary,
903
- .ui-widget-content .ui-priority-secondary,
904
- .ui-widget-header .ui-priority-secondary {
905
- opacity: .7;
906
- filter:Alpha(Opacity=70);
907
- font-weight: normal;
908
- }
909
- .ui-state-disabled,
910
- .ui-widget-content .ui-state-disabled,
911
- .ui-widget-header .ui-state-disabled {
912
- opacity: .35;
913
- filter:Alpha(Opacity=35);
914
- background-image: none;
915
- }
916
- .ui-state-disabled .ui-icon {
917
- filter:Alpha(Opacity=35); /* For IE8 - See #6059 */
918
- }
919
-
920
- /* Icons
921
- ----------------------------------*/
922
-
923
- /* states and images */
924
- .ui-icon {
925
- width: 16px;
926
- height: 16px;
927
- background-position: 16px 16px;
928
- }
929
- .ui-icon,
930
- .ui-widget-content .ui-icon {
931
- background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/;
932
- }
933
- .ui-widget-header .ui-icon {
934
- background-image: url(images/ui-icons_222222_256x240.png)/*{iconsHeader}*/;
935
- }
936
- .ui-state-default .ui-icon {
937
- background-image: url(images/ui-icons_888888_256x240.png)/*{iconsDefault}*/;
938
- }
939
- .ui-state-hover .ui-icon,
940
- .ui-state-focus .ui-icon {
941
- background-image: url(images/ui-icons_454545_256x240.png)/*{iconsHover}*/;
942
- }
943
- .ui-state-active .ui-icon {
944
- background-image: url(images/ui-icons_454545_256x240.png)/*{iconsActive}*/;
945
- }
946
- .ui-state-highlight .ui-icon {
947
- background-image: url(images/ui-icons_2e83ff_256x240.png)/*{iconsHighlight}*/;
948
- }
949
- .ui-state-error .ui-icon,
950
- .ui-state-error-text .ui-icon {
951
- background-image: url(images/ui-icons_cd0a0a_256x240.png)/*{iconsError}*/;
952
- }
953
-
954
- /* positioning */
955
- .ui-icon-carat-1-n { background-position: 0 0; }
956
- .ui-icon-carat-1-ne { background-position: -16px 0; }
957
- .ui-icon-carat-1-e { background-position: -32px 0; }
958
- .ui-icon-carat-1-se { background-position: -48px 0; }
959
- .ui-icon-carat-1-s { background-position: -64px 0; }
960
- .ui-icon-carat-1-sw { background-position: -80px 0; }
961
- .ui-icon-carat-1-w { background-position: -96px 0; }
962
- .ui-icon-carat-1-nw { background-position: -112px 0; }
963
- .ui-icon-carat-2-n-s { background-position: -128px 0; }
964
- .ui-icon-carat-2-e-w { background-position: -144px 0; }
965
- .ui-icon-triangle-1-n { background-position: 0 -16px; }
966
- .ui-icon-triangle-1-ne { background-position: -16px -16px; }
967
- .ui-icon-triangle-1-e { background-position: -32px -16px; }
968
- .ui-icon-triangle-1-se { background-position: -48px -16px; }
969
- .ui-icon-triangle-1-s { background-position: -64px -16px; }
970
- .ui-icon-triangle-1-sw { background-position: -80px -16px; }
971
- .ui-icon-triangle-1-w { background-position: -96px -16px; }
972
- .ui-icon-triangle-1-nw { background-position: -112px -16px; }
973
- .ui-icon-triangle-2-n-s { background-position: -128px -16px; }
974
- .ui-icon-triangle-2-e-w { background-position: -144px -16px; }
975
- .ui-icon-arrow-1-n { background-position: 0 -32px; }
976
- .ui-icon-arrow-1-ne { background-position: -16px -32px; }
977
- .ui-icon-arrow-1-e { background-position: -32px -32px; }
978
- .ui-icon-arrow-1-se { background-position: -48px -32px; }
979
- .ui-icon-arrow-1-s { background-position: -64px -32px; }
980
- .ui-icon-arrow-1-sw { background-position: -80px -32px; }
981
- .ui-icon-arrow-1-w { background-position: -96px -32px; }
982
- .ui-icon-arrow-1-nw { background-position: -112px -32px; }
983
- .ui-icon-arrow-2-n-s { background-position: -128px -32px; }
984
- .ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
985
- .ui-icon-arrow-2-e-w { background-position: -160px -32px; }
986
- .ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
987
- .ui-icon-arrowstop-1-n { background-position: -192px -32px; }
988
- .ui-icon-arrowstop-1-e { background-position: -208px -32px; }
989
- .ui-icon-arrowstop-1-s { background-position: -224px -32px; }
990
- .ui-icon-arrowstop-1-w { background-position: -240px -32px; }
991
- .ui-icon-arrowthick-1-n { background-position: 0 -48px; }
992
- .ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
993
- .ui-icon-arrowthick-1-e { background-position: -32px -48px; }
994
- .ui-icon-arrowthick-1-se { background-position: -48px -48px; }
995
- .ui-icon-arrowthick-1-s { background-position: -64px -48px; }
996
- .ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
997
- .ui-icon-arrowthick-1-w { background-position: -96px -48px; }
998
- .ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
999
- .ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
1000
- .ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
1001
- .ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
1002
- .ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
1003
- .ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
1004
- .ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
1005
- .ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
1006
- .ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
1007
- .ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
1008
- .ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
1009
- .ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
1010
- .ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
1011
- .ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
1012
- .ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
1013
- .ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
1014
- .ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
1015
- .ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
1016
- .ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
1017
- .ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
1018
- .ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
1019
- .ui-icon-arrow-4 { background-position: 0 -80px; }
1020
- .ui-icon-arrow-4-diag { background-position: -16px -80px; }
1021
- .ui-icon-extlink { background-position: -32px -80px; }
1022
- .ui-icon-newwin { background-position: -48px -80px; }
1023
- .ui-icon-refresh { background-position: -64px -80px; }
1024
- .ui-icon-shuffle { background-position: -80px -80px; }
1025
- .ui-icon-transfer-e-w { background-position: -96px -80px; }
1026
- .ui-icon-transferthick-e-w { background-position: -112px -80px; }
1027
- .ui-icon-folder-collapsed { background-position: 0 -96px; }
1028
- .ui-icon-folder-open { background-position: -16px -96px; }
1029
- .ui-icon-document { background-position: -32px -96px; }
1030
- .ui-icon-document-b { background-position: -48px -96px; }
1031
- .ui-icon-note { background-position: -64px -96px; }
1032
- .ui-icon-mail-closed { background-position: -80px -96px; }
1033
- .ui-icon-mail-open { background-position: -96px -96px; }
1034
- .ui-icon-suitcase { background-position: -112px -96px; }
1035
- .ui-icon-comment { background-position: -128px -96px; }
1036
- .ui-icon-person { background-position: -144px -96px; }
1037
- .ui-icon-print { background-position: -160px -96px; }
1038
- .ui-icon-trash { background-position: -176px -96px; }
1039
- .ui-icon-locked { background-position: -192px -96px; }
1040
- .ui-icon-unlocked { background-position: -208px -96px; }
1041
- .ui-icon-bookmark { background-position: -224px -96px; }
1042
- .ui-icon-tag { background-position: -240px -96px; }
1043
- .ui-icon-home { background-position: 0 -112px; }
1044
- .ui-icon-flag { background-position: -16px -112px; }
1045
- .ui-icon-calendar { background-position: -32px -112px; }
1046
- .ui-icon-cart { background-position: -48px -112px; }
1047
- .ui-icon-pencil { background-position: -64px -112px; }
1048
- .ui-icon-clock { background-position: -80px -112px; }
1049
- .ui-icon-disk { background-position: -96px -112px; }
1050
- .ui-icon-calculator { background-position: -112px -112px; }
1051
- .ui-icon-zoomin { background-position: -128px -112px; }
1052
- .ui-icon-zoomout { background-position: -144px -112px; }
1053
- .ui-icon-search { background-position: -160px -112px; }
1054
- .ui-icon-wrench { background-position: -176px -112px; }
1055
- .ui-icon-gear { background-position: -192px -112px; }
1056
- .ui-icon-heart { background-position: -208px -112px; }
1057
- .ui-icon-star { background-position: -224px -112px; }
1058
- .ui-icon-link { background-position: -240px -112px; }
1059
- .ui-icon-cancel { background-position: 0 -128px; }
1060
- .ui-icon-plus { background-position: -16px -128px; }
1061
- .ui-icon-plusthick { background-position: -32px -128px; }
1062
- .ui-icon-minus { background-position: -48px -128px; }
1063
- .ui-icon-minusthick { background-position: -64px -128px; }
1064
- .ui-icon-close { background-position: -80px -128px; }
1065
- .ui-icon-closethick { background-position: -96px -128px; }
1066
- .ui-icon-key { background-position: -112px -128px; }
1067
- .ui-icon-lightbulb { background-position: -128px -128px; }
1068
- .ui-icon-scissors { background-position: -144px -128px; }
1069
- .ui-icon-clipboard { background-position: -160px -128px; }
1070
- .ui-icon-copy { background-position: -176px -128px; }
1071
- .ui-icon-contact { background-position: -192px -128px; }
1072
- .ui-icon-image { background-position: -208px -128px; }
1073
- .ui-icon-video { background-position: -224px -128px; }
1074
- .ui-icon-script { background-position: -240px -128px; }
1075
- .ui-icon-alert { background-position: 0 -144px; }
1076
- .ui-icon-info { background-position: -16px -144px; }
1077
- .ui-icon-notice { background-position: -32px -144px; }
1078
- .ui-icon-help { background-position: -48px -144px; }
1079
- .ui-icon-check { background-position: -64px -144px; }
1080
- .ui-icon-bullet { background-position: -80px -144px; }
1081
- .ui-icon-radio-on { background-position: -96px -144px; }
1082
- .ui-icon-radio-off { background-position: -112px -144px; }
1083
- .ui-icon-pin-w { background-position: -128px -144px; }
1084
- .ui-icon-pin-s { background-position: -144px -144px; }
1085
- .ui-icon-play { background-position: 0 -160px; }
1086
- .ui-icon-pause { background-position: -16px -160px; }
1087
- .ui-icon-seek-next { background-position: -32px -160px; }
1088
- .ui-icon-seek-prev { background-position: -48px -160px; }
1089
- .ui-icon-seek-end { background-position: -64px -160px; }
1090
- .ui-icon-seek-start { background-position: -80px -160px; }
1091
- /* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
1092
- .ui-icon-seek-first { background-position: -80px -160px; }
1093
- .ui-icon-stop { background-position: -96px -160px; }
1094
- .ui-icon-eject { background-position: -112px -160px; }
1095
- .ui-icon-volume-off { background-position: -128px -160px; }
1096
- .ui-icon-volume-on { background-position: -144px -160px; }
1097
- .ui-icon-power { background-position: 0 -176px; }
1098
- .ui-icon-signal-diag { background-position: -16px -176px; }
1099
- .ui-icon-signal { background-position: -32px -176px; }
1100
- .ui-icon-battery-0 { background-position: -48px -176px; }
1101
- .ui-icon-battery-1 { background-position: -64px -176px; }
1102
- .ui-icon-battery-2 { background-position: -80px -176px; }
1103
- .ui-icon-battery-3 { background-position: -96px -176px; }
1104
- .ui-icon-circle-plus { background-position: 0 -192px; }
1105
- .ui-icon-circle-minus { background-position: -16px -192px; }
1106
- .ui-icon-circle-close { background-position: -32px -192px; }
1107
- .ui-icon-circle-triangle-e { background-position: -48px -192px; }
1108
- .ui-icon-circle-triangle-s { background-position: -64px -192px; }
1109
- .ui-icon-circle-triangle-w { background-position: -80px -192px; }
1110
- .ui-icon-circle-triangle-n { background-position: -96px -192px; }
1111
- .ui-icon-circle-arrow-e { background-position: -112px -192px; }
1112
- .ui-icon-circle-arrow-s { background-position: -128px -192px; }
1113
- .ui-icon-circle-arrow-w { background-position: -144px -192px; }
1114
- .ui-icon-circle-arrow-n { background-position: -160px -192px; }
1115
- .ui-icon-circle-zoomin { background-position: -176px -192px; }
1116
- .ui-icon-circle-zoomout { background-position: -192px -192px; }
1117
- .ui-icon-circle-check { background-position: -208px -192px; }
1118
- .ui-icon-circlesmall-plus { background-position: 0 -208px; }
1119
- .ui-icon-circlesmall-minus { background-position: -16px -208px; }
1120
- .ui-icon-circlesmall-close { background-position: -32px -208px; }
1121
- .ui-icon-squaresmall-plus { background-position: -48px -208px; }
1122
- .ui-icon-squaresmall-minus { background-position: -64px -208px; }
1123
- .ui-icon-squaresmall-close { background-position: -80px -208px; }
1124
- .ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
1125
- .ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
1126
- .ui-icon-grip-solid-vertical { background-position: -32px -224px; }
1127
- .ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
1128
- .ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
1129
- .ui-icon-grip-diagonal-se { background-position: -80px -224px; }
1130
-
1131
-
1132
- /* Misc visuals
1133
- ----------------------------------*/
1134
-
1135
- /* Corner radius */
1136
- .ui-corner-all,
1137
- .ui-corner-top,
1138
- .ui-corner-left,
1139
- .ui-corner-tl {
1140
- border-top-left-radius: 4px/*{cornerRadius}*/;
1141
- }
1142
- .ui-corner-all,
1143
- .ui-corner-top,
1144
- .ui-corner-right,
1145
- .ui-corner-tr {
1146
- border-top-right-radius: 4px/*{cornerRadius}*/;
1147
- }
1148
- .ui-corner-all,
1149
- .ui-corner-bottom,
1150
- .ui-corner-left,
1151
- .ui-corner-bl {
1152
- border-bottom-left-radius: 4px/*{cornerRadius}*/;
1153
- }
1154
- .ui-corner-all,
1155
- .ui-corner-bottom,
1156
- .ui-corner-right,
1157
- .ui-corner-br {
1158
- border-bottom-right-radius: 4px/*{cornerRadius}*/;
1159
- }
1160
-
1161
- /* Overlays */
1162
- .ui-widget-overlay {
1163
- background: #aaaaaa/*{bgColorOverlay}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlOverlay}*/ 50%/*{bgOverlayXPos}*/ 50%/*{bgOverlayYPos}*/ repeat-x/*{bgOverlayRepeat}*/;
1164
- opacity: .3/*{opacityOverlay}*/;
1165
- filter: Alpha(Opacity=30)/*{opacityFilterOverlay}*/;
1166
- }
1167
- .ui-widget-shadow {
1168
- margin: -8px/*{offsetTopShadow}*/ 0 0 -8px/*{offsetLeftShadow}*/;
1169
- padding: 8px/*{thicknessShadow}*/;
1170
- background: #aaaaaa/*{bgColorShadow}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlShadow}*/ 50%/*{bgShadowXPos}*/ 50%/*{bgShadowYPos}*/ repeat-x/*{bgShadowRepeat}*/;
1171
- opacity: .3/*{opacityShadow}*/;
1172
- filter: Alpha(Opacity=30)/*{opacityFilterShadow}*/;
1173
- border-radius: 8px/*{cornerRadiusShadow}*/;
1174
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/resources/css/jquery.weekcalendar.css DELETED
@@ -1,287 +0,0 @@
1
- .wc-container {
2
- font-size: 14px;
3
- font-family: arial, helvetica;
4
- }
5
-
6
- .wc-toolbar {
7
- padding: 1em;
8
- font-size:0.8em;
9
- }
10
-
11
- .wc-toolbar .wc-nav {
12
- float:left;
13
- }
14
-
15
- .wc-toolbar .wc-display {
16
- float: right;
17
- }
18
-
19
- .wc-toolbar button {
20
- margin-top: 0;
21
- margin-bottom: 0;
22
- }
23
-
24
- .wc-toolbar .wc-title {
25
- text-align: center;
26
- padding:0;
27
- margin:0;
28
- }
29
-
30
- .wc-container table {
31
- border-collapse: collapse;
32
- border-spacing: 0;
33
- }
34
- .wc-container table td {
35
- margin: 0;
36
- padding: 0;
37
- }
38
-
39
- .wc-header {
40
- background: #eee;
41
- border-width:1px 0;
42
- border-style:solid;
43
- }
44
- .wc-header table{
45
- width: 100%;
46
- table-layout:fixed;
47
- }
48
-
49
- .wc-grid-timeslot-header,
50
- .wc-header .wc-time-column-header {
51
- width: 45px;
52
- }
53
-
54
- .wc-header .wc-scrollbar-shim {
55
- /* this width replace js */
56
- }
57
-
58
- .wc-header .wc-day-column-header {
59
- text-align: center;
60
- padding: 0.4em;
61
- }
62
-
63
- .wc-header .wc-user-header{
64
- text-align: center;
65
- padding: 0.4em 0;
66
- overflow:hidden;
67
- }
68
- .wc-grid-timeslot-header {
69
- background: #eee;
70
- }
71
-
72
-
73
-
74
- .wc-scrollable-grid {
75
- overflow: auto;
76
- overflow-x: hidden !important;
77
- overflow-y: auto !important;
78
- position: relative;
79
- background-color: #fff;
80
- width: 100%;
81
- }
82
-
83
-
84
- table.wc-time-slots {
85
- width: 100%;
86
- table-layout: fixed;
87
- cursor: default;
88
- overflow:hidden;
89
- }
90
-
91
- .wc-day-column {
92
- width: 13.5%;
93
- overflow: visible;
94
- vertical-align: top;
95
- }
96
- .wc-day-column-header{border-width: 0 0 0 3px; border-style: solid;border-color:#aaaaaa;}
97
- .wc-scrollable-grid .wc-day-column-last,
98
- .wc-scrollable-grid .wc-day-column-middle{border-width: 0 0 0 1px; border-style: dashed;}
99
- .wc-scrollable-grid .wc-day-column-first{border-width: 0 0 0 3px; border-style: double;}
100
-
101
- .wc-day-column-inner {
102
- width: 100%;
103
- position:relative;
104
- }
105
-
106
- .wc-no-height-wrapper{
107
- position:relative;
108
- overflow: visible;
109
- height: 0px;
110
- }
111
-
112
- .wc-time-slot-wrapper {
113
- /* top: 3px;*/
114
- }
115
- .wc-oddeven-wrapper .wc-full-height-column{
116
- /* top: 2px; */
117
- /* Modern Browsers */ opacity: 0.4;
118
- /* IE 8 */ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)";
119
- /* IE 5-7 */ filter: alpha(opacity=40);
120
- /* Netscape */ -moz-opacity: 0.4;
121
- /* Safari 1 */ -khtml-opacity: 0.4;
122
- }
123
- .wc-freebusy-wrapper .wc-freebusy{
124
- /* top: 1px;*/
125
- /* Modern Browsers */ opacity: 0.4;
126
- /* IE 8 */ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)";
127
- /* IE 5-7 */ filter: alpha(opacity=40);
128
- /* Netscape */ -moz-opacity: 0.4;
129
- /* Safari 1 */ -khtml-opacity: 0.4;
130
- }
131
-
132
- .wc-time-slots {
133
- position: absolute;
134
- width: 100%;
135
- }
136
-
137
- .wc-column-odd,
138
- .wc-column-even.ui-state-hover{background-image:none;border:none;}
139
-
140
- .wc-header .wc-today.ui-state-active{background-image:none;}
141
- .wc-header .wc-today.wc-day-column-header{border-width:0 3px; border-style: solid;}
142
- .wc-header .wc-user-header{border-width:0;}
143
-
144
- .wc-time-slots .wc-day-column.ui-state-default{background:transparent;}
145
- .wc-time-slots .wc-today.ui-state-active{background-image:none;}
146
- .wc-header .wc-today.ui-state-active.wc-day-column-middle{border-width:0;}
147
- .wc-header .wc-today.ui-state-active.wc-day-column-first{border-left-width:3px;}
148
- .wc-header .wc-today.ui-state-active.wc-day-column-last{border-right-width:3px;}
149
-
150
- .wc-full-height-column{
151
- display:block;
152
- /* width:100%;*/
153
- }
154
-
155
-
156
- .wc-time-header-cell {
157
- padding: 5px;
158
- height: 80px; /* reference height */
159
- }
160
-
161
-
162
- .wc-time-slot {
163
- border-bottom: 1px dotted #ddd;
164
- }
165
-
166
- .wc-hour-header {
167
- text-align: right;
168
- }
169
- .wc-hour-header.ui-state-active,
170
- .wc-hour-header.ui-state-default{
171
- border-width:0 0 1px 0;
172
- }
173
-
174
- .wc-hour-end, .wc-hour-header {
175
- border-bottom: 1px solid #ccc;
176
- color: #555;
177
-
178
- }
179
-
180
- .wc-business-hours {
181
- background-color: #E6EEF1;
182
- border-bottom: 1px solid #ccc;
183
- color: #333;
184
- font-size: 1.4em;
185
-
186
- }
187
-
188
- .wc-business-hours .wc-am-pm {
189
- font-size: 0.6em;
190
- }
191
-
192
- .wc-day-header-cell {
193
- text-align: center;
194
- vertical-align: middle;
195
- padding: 5px;
196
- }
197
-
198
-
199
-
200
- .wc-time-slot-header .wc-header-cell {
201
- text-align: right;
202
- padding-right: 10px;
203
- }
204
-
205
- .wc-cal-event {
206
- background-color: #68a1e5;
207
- /* Modern Browsers */ opacity: 0.8;
208
- /* IE 8 */ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
209
- /* IE 5-7 */ filter: alpha(opacity=80);
210
- /* Netscape */ -moz-opacity: 0.8;
211
- /* Safari 1 */ -khtml-opacity: 0.8;
212
- position: absolute;
213
- text-align: center;
214
- overflow: hidden;
215
- cursor: pointer;
216
- color: #fff;
217
- width: 100%;
218
- display: none;
219
- }
220
-
221
-
222
- .wc-cal-event-delete {
223
- float: right;
224
- cursor: pointer;
225
- width: 16px;
226
- height: 16px;
227
- }
228
-
229
- .wc-cal-event.ui-resizable-resizing {
230
- cursor: s-resize;
231
- }
232
-
233
- .wc-cal-event .wc-time {
234
- background-color: #2b72d0;
235
- border: 1px solid #1b62c0;
236
- color: #fff;
237
- padding: 0;
238
- font-weight: bold;
239
- }
240
-
241
- .wc-container .ui-draggable .wc-time {
242
- cursor: move;
243
- }
244
-
245
- .wc-cal-event .wc-title {
246
- position: relative;
247
- }
248
-
249
- .wc-container .ui-resizable-s {
250
- height: 10px;
251
- line-height: 10px;
252
- bottom: -2px;
253
- font-size: .75em;
254
- }
255
-
256
-
257
- .wc-container .ui-draggable-dragging {
258
- z-index: 1000;
259
- }
260
-
261
- .free-busy-free{}
262
- .free-busy-busy{
263
- background:url("../images/ui-bg_flat_0_aaaaaa_40x100.png") repeat scroll 50% 50% #666666;
264
- }
265
-
266
- /** hourLine */
267
-
268
- .wc-hourline {
269
- height: 0pt;
270
- border-top: 2px solid #FF7F6E;
271
- overflow: hidden;
272
- position: absolute;
273
- width: inherit;
274
- }
275
-
276
- /* IE6 hacks */
277
- * html .wc-no-height-wrapper{position:absolute;}
278
- * html .wc-time-slot-wrapper{top:3px;}
279
- * html .wc-grid-row-oddeven{top:2px;}
280
- * html .wc-grid-row-freebusy{top:1px;}
281
-
282
- /* IE7 hacks */
283
- *:first-child+html .wc-no-height-wrapper{position:relative;}
284
- *:first-child+html .wc-time-slot-wrapper{top:3px;}
285
- *:first-child+html .wc-grid-row-oddeven{top:2px;}
286
- *:first-child+html .wc-grid-row-freebusy{top:1px;}
287
- *:first-child+html .wc-time-slots .wc-today{/* due to rendering issues, no background */background:none;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/resources/images/calendar1.png DELETED
Binary file
backend/modules/calendar/resources/images/chosen-sprite.png DELETED
Binary file
backend/modules/calendar/resources/images/information.png DELETED
Binary file
backend/modules/calendar/resources/images/ui-bg_flat_0_aaaaaa_40x100.png DELETED
Binary file
backend/modules/calendar/resources/images/user-white.png DELETED
Binary file
backend/modules/calendar/resources/images/user.png DELETED
Binary file
backend/modules/calendar/resources/js/calendar.js CHANGED
@@ -1,525 +1,383 @@
1
  jQuery(function ($) {
2
- var $week_calendar_wrapper = $('#week_calendar_wrapper'),
3
- $week_calendar = $week_calendar_wrapper.find('.ab-calendar-element'),
4
- $tabs = $week_calendar_wrapper.find('.nav-tabs'),
5
- $day_calendar_wrapper = $('#day_calendar_wrapper'),
6
- $day_calendar = $day_calendar_wrapper.find('.ab-calendar-element'),
7
- $first_day_of_week = parseInt($('#ab_calendar_data_holder .ab-calendar-first-day').text(), 10),
8
- $time_format = $('#ab_calendar_data_holder .ab-calendar-time-format').text(),
9
- $staff_tabs = $('.ab-calendar-tab'),
10
- // Staff filter vars
11
- $staff_filter_button = $('#ab-staff-button'),
12
- $all_staff_option = $('#ab-filter-all-staff'),
13
- $staff_option = $('.ab-staff-option'),
14
-
15
- $calendar_common_options = {
16
- timeslotsPerHour: BooklyL10n.timeslotsPerHour,
17
- timeslotHeight: 25,
18
- scrollToHourMillis : 0,
19
- businessHours: {start: 8, end: 18},
20
- firstDayOfWeek: $first_day_of_week,
21
- hourLine: true,
22
- displayFreeBusys: true,
23
- useShortDayNames: true,
24
- showHeader: false,
25
- headerSeparator: '',
26
- dateFormat: ', M d',
27
- timeFormat: $time_format,
28
- use24Hour: ($time_format.toLowerCase().indexOf('a') == -1),
29
- newEventText: BooklyL10n.new_appointment,
30
- allowEventDelete: true,
31
- draggable: function(calEvent, element) {
32
- return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  },
34
- resizable: function(calEvent, element) {
35
- return false;
 
 
 
 
 
 
 
 
 
36
  },
37
- eventDelete: function(calEvent, element, dayFreeBusyManager, calendar, clickEvent) {
38
- if (confirm(BooklyL10n.are_you_sure)) {
39
- $.post(ajaxurl, {'action' : 'ab_delete_appointment', 'appointment_id' : calEvent.id }, function () {
40
- calendar.weekCalendar('removeEvent', calEvent.id);
41
- });
42
- }
 
 
 
 
 
 
 
 
43
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  eventRender : function(calEvent, $event) {
45
- $event.css('backgroundColor', calEvent.color);
46
- $event.find('.wc-time').css({
47
- backgroundColor : calEvent.color,
48
- borderLeftColor : calEvent.color,
49
- borderRightColor : calEvent.color,
50
- borderTopColor : calEvent.color,
51
- borderBottomColor : '#ABABAB'
52
- });
53
- },
54
- eventAfterRender : function(calEvent, $calEventList) {
55
- $calEventList.each(function () {
56
- var $calEvent = $(this);
57
- var titleHeight = $calEvent.find('.wc-title').height();
58
- var origHeight = $calEvent.height();
59
-
60
- if ( origHeight < titleHeight ) {
61
- var $info = $('<div class="wc-information"/>');
62
- var $delete = $('.wc-cal-event-delete', $calEvent);
63
-
64
- $delete.hide();
65
- $('.wc-time', $calEvent).prepend($info);
66
-
67
- // mouse handlers
68
- $info.on('mouseenter', function () {
69
- $calEvent.css({height: (titleHeight + 30), 'z-index': 1});
70
- $info.hide();
71
- $delete.show();
72
- });
73
- $calEvent.on('mouseleave', function () {
74
- $calEvent.css({height: origHeight, 'z-index': 'auto'});
75
- $delete.hide();
76
- $info.show();
77
- });
78
  }
79
- });
80
- },
81
- eventBody : function(calEvent, calendar) {
82
- var body = '<div class="wc-service-name">' + calEvent.title + '</div>';
83
- if (calEvent.desc) {
84
- body += calEvent.desc;
85
- }
86
- return body;
87
- }
88
- },
89
- $week_calendar_options = {
90
- daysToShow: 7,
91
- eventNew: function(calEvent, element, dayFreeBusyManager, calendar, mouseupEvent) {
92
- element.hide().remove();
93
- showAppointmentDialog(
94
- null,
95
- $week_calendar_wrapper.find('.ab-calendar-tab.active').data('staff-id'),
96
- calEvent.start,
97
- null,
98
- calendar,
99
- 'week'
100
- );
101
- },
102
- eventClick: function(calEvent, element, dayFreeBusyManager, calendar, clickEvent) {
103
- showAppointmentDialog(
104
- calEvent.id,
105
- $week_calendar_wrapper.find('.ab-calendar-tab.active').data('staff-id'),
106
- calEvent.start,
107
- calEvent.end,
108
- calendar,
109
- 'week'
110
- );
111
- },
112
- data: function(start, end, callback) {
113
- $.post(
114
- ajaxurl,
115
- {
116
- action : 'ab_week_staff_appointments',
117
- start_date : getFormattedDateForCalendar(start),
118
- end_date : getFormattedDateForCalendar(end),
119
- staff_id : $week_calendar_wrapper.find('.ab-calendar-tab.active').data('staff-id')
120
- },
121
- function (response) {
122
- var appointments = $.map(response.events, function(value) {
123
- return {
124
- id : parseInt(value.id, 10),
125
- start : new Date(value.start),
126
- end : new Date(value.end),
127
- title : value.title,
128
- desc : value.desc,
129
- color : value.color
130
- };
131
- });
132
- var free_busys = $.map(response.freebusys, function(value) {
133
- return {
134
- start : new Date(value.start),
135
- end : new Date(value.end),
136
- free : value.free
137
- };
138
- });
139
-
140
- callback({ events: appointments, freebusys: free_busys });
141
- },
142
- 'json'
143
- );
144
- },
145
- height: function() {
146
- var $window_height = $(window).height(),
147
- $wp_admin_bar_height = $('#wpadminbar').height(),
148
- $ab_calendar_header_height = $('#ab_calendar_header').height(),
149
- $ab_calendar_tabs_height = $('#week_calendar_wrapper .tabbable').outerHeight(true),
150
- $height_to_reduce = $wp_admin_bar_height + $ab_calendar_header_height + $ab_calendar_tabs_height,
151
- $wrap = $('#wpbody-content .wrap');
152
-
153
- if ($wrap.css('margin-top')) {
154
- $height_to_reduce += parseInt($wrap.css('margin-top').replace('px', ''), 10);
155
- }
156
-
157
- if ($wrap.css('margin-bottom')) {
158
- $height_to_reduce += parseInt($wrap.css('margin-bottom').replace('px', ''), 10);
159
- }
160
-
161
- return $window_height - $height_to_reduce;
162
- }
163
- },
164
- $day_calendar_options = {
165
- data: function(start, end, callback) {
166
- var selected_staff_ids = [];
167
- $('.ab-staff-option:checked').each(function() {
168
- selected_staff_ids.push(this.value);
169
- });
170
- var data = {
171
- action : 'ab_day_staff_appointments',
172
- start_date : getFormattedDateForCalendar(start),
173
- staff_id : selected_staff_ids
174
- };
175
- $.post(
176
- ajaxurl, data, function (response) {
177
- var appointments = $.map(response.events, function(value) {
178
- return {
179
- id : parseInt(value.id, 10),
180
- start : new Date(value.start),
181
- end : new Date(value.end),
182
- title : value.title,
183
- color : value.color,
184
- desc : value.desc,
185
- userId : parseInt(value.userId, 10)
186
- };
187
- });
188
- var free_busys = $.map(response.freebusys, function(value) {
189
- return {
190
- start : new Date(value.start),
191
- end : new Date(value.end),
192
- free : value.free,
193
- userId : parseInt(value.userId, 10)
194
- };
195
- });
196
-
197
- callback({ events: appointments, freebusys: free_busys });
198
- },
199
- 'json'
200
- );
201
  },
202
- users: [],
203
- getUserId: function(user, index, calendar) {
204
- return user.staff_id;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  },
206
- getUserName: function(user, index, calendar) {
207
- return user.full_name;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  },
209
- daysToShow: 1,
210
- height: function() {
211
- var $window_height = $(window).height(),
212
- $wp_admin_bar_height = $('#wpadminbar').height(),
213
- $ab_calendar_header_height = $('#ab_calendar_header').height(),
214
- $height_to_reduce = $wp_admin_bar_height + $ab_calendar_header_height + $('#day_calendar_wrapper').outerHeight(true),
215
- $wrap = $('#wpbody-content .wrap');
216
-
217
- if ($wrap.css('margin-top')) {
218
- $height_to_reduce += parseInt($wrap.css('margin-top').replace('px', ''), 10);
219
- }
220
-
221
- if ($wrap.css('margin-bottom')) {
222
- $height_to_reduce += parseInt($wrap.css('margin-bottom').replace('px', ''), 10);
223
- }
224
-
225
- return $window_height - $height_to_reduce;
 
 
 
 
 
 
 
226
  },
227
- eventNew: function(calEvent, element, dayFreeBusyManager, calendar, mouseupEvent) {
228
- element.hide().remove();
229
- showAppointmentDialog(
230
- null,
231
- calEvent.userId,
232
- calEvent.start,
233
- null,
234
- calendar,
235
- 'day'
236
- );
237
  },
238
- eventClick: function(calEvent, element, dayFreeBusyManager, calendar, clickEvent) {
239
- showAppointmentDialog(
240
- calEvent.id,
241
- calEvent.userId,
242
- calEvent.start,
243
- calEvent.end,
244
- calendar,
245
- 'day'
246
- );
247
  }
248
- };
249
-
250
- // Datepickers.
251
- var week_picker = new WeekPicker();
252
- var day_picker = new DayPicker();
253
- week_picker.attachCalendar($week_calendar);
254
-
255
- // week calendar
256
- $week_calendar.weekCalendar($.extend({}, $calendar_common_options, $week_calendar_options));
257
-
258
- // click on tabs
259
- $tabs.find('.ab-calendar-tab').on('click', function(e) {
260
- e.stopPropagation();
261
- $('.ab-calendar-tab').removeClass('active');
262
- $(this).addClass('active');
263
- // prevents console error of initialization
264
- $week_calendar.weekCalendar(); $week_calendar.weekCalendar('refresh');
265
- });
266
-
267
- // today
268
- $('.ab-nav-calendar .ab-calendar-today').on('click', function(){
269
- var $active_view_button = $('.ab-nav-calendar .ab-calendar-switch-view.ab-button-active');
270
- if ($active_view_button.hasClass('ab-calendar-day')) {
271
- day_picker.setDate(new Date());
272
  } else {
273
- week_picker.setDate(new Date());
274
  }
275
- });
276
 
277
- // day/week view
278
- $('.ab-nav-calendar .ab-calendar-switch-view').on('click', function() {
279
- var $this = $(this);
280
-
281
- if ($this.hasClass('ab-button-active')) {
282
- return false;
283
- }
284
- $('.ab-nav-calendar .ab-calendar-switch-view').not($this).removeClass('ab-button-active');
285
- $('.ab-nav-calendar .ab-calendar-today').removeClass('ab-button-active');
286
- $this.addClass('ab-button-active');
287
-
288
- // Switch to day-view
289
- if ($this.hasClass('ab-calendar-day')) {
290
- $week_calendar_wrapper.hide();
291
- $week_calendar.remove();
292
- week_picker.hide();
293
-
294
- var $day_calendar_container = $day_calendar_wrapper.find('.ab-calendar-element-container'),
295
- date_from_week_picker = week_picker.getStartDate(),
296
-
297
- /**
298
- * Checks if current date belongs to selected week of week-picker
299
- *
300
- * @return bool
301
- */
302
- is_current_week = function() {
303
- var now_date = new Date(),
304
- first_day_of_week = startAndEndOfWeek(date_from_week_picker).first_day_of_week,
305
- last_day_of_week = startAndEndOfWeek(date_from_week_picker).last_day_of_week;
306
-
307
- return first_day_of_week <= now_date && now_date <= last_day_of_week;
308
- },
309
-
310
- /**
311
- * result is an object of dates for start (monday)
312
- * and end (sunday) of week based on supplied date object
313
- *
314
- * @return object
315
- */
316
- startAndEndOfWeek = function(date) {
317
- var result = {
318
- first_day_of_week : null,
319
- last_day_of_week : null
320
- };
321
-
322
- // If no date object supplied, use current date
323
- // Copy date so don't modify supplied date
324
- var now = date ? new Date(date) : new Date();
325
-
326
- // set time to some convenient value
327
- now.setHours(0, 0, 0, 0);
328
-
329
- // Get the previous Monday
330
- var monday = new Date(now);
331
- monday.setDate(monday.getDate() - monday.getDay() + 1);
332
-
333
- // Get next Sunday
334
- var sunday = new Date(now);
335
- sunday.setDate(sunday.getDate() - sunday.getDay() + 7);
336
-
337
- // set result's days
338
- result.first_day_of_week = monday;
339
- result.last_day_of_week = sunday;
340
-
341
- return result;
342
- };
343
-
344
- // Set visible users.
345
- var users = [];
346
- $('.ab-staff-option:checked').each(function() {
347
- users.push({staff_id: parseInt(this.value), full_name: $(this).next().text()});
348
- });
349
- $day_calendar = $('<div class="ab-calendar-element" />').appendTo($day_calendar_container);
350
- $day_calendar.weekCalendar($.extend({date: date_from_week_picker}, $calendar_common_options, $day_calendar_options, {users: users}));
351
- $day_calendar_wrapper.show();
352
-
353
- day_picker.attachCalendar($day_calendar);
354
-
355
- // if week is current - set current date, otherwise set date from week-picker
356
- is_current_week() ?
357
- day_picker.setDate(new Date(), true) :
358
- day_picker.setDate(date_from_week_picker, true);
359
-
360
- day_picker.show();
361
-
362
- // styles-fixing
363
- $('td.wc-scrollbar-shim').hide();
364
- // Switch to week view
365
- } else {
366
- $day_calendar_wrapper.hide();
367
- $day_calendar.remove();
368
- day_picker.hide();
369
-
370
- var $week_calendar_container = $week_calendar_wrapper.find('.ab-calendar-element-container'),
371
- date_from_day_picker = day_picker.getDate();
372
-
373
- // Show tabs based on selected staff members.
374
- var active_set = false;
375
- $staff_option.each(function() {
376
- if (this.checked) {
377
- $('.ab-staff-tab-' + this.value).show().toggleClass('active', active_set === false);
378
- active_set = true;
379
- } else {
380
- $('.ab-staff-tab-' + this.value).hide().removeClass('active');
381
  }
382
- });
383
-
384
- $week_calendar = $('<div class="ab-calendar-element" />').appendTo($week_calendar_container);
385
- if (active_set) {
386
- $week_calendar_wrapper.show();
387
- }
388
- $week_calendar.weekCalendar($.extend({date: date_from_day_picker}, $calendar_common_options, $week_calendar_options));
389
- scrollShim = document.querySelector('.wc-scrollbar-shim');
390
- if ( scrollShim !== null ) scrollShim.style.width = scrollWidth + 'px';
391
-
392
- // Set date from day calendar
393
- week_picker.setDate(date_from_day_picker, false);
394
- week_picker.attachCalendar($week_calendar);
395
- week_picker.show();
396
- } // end of Week View
397
- });
398
-
399
- // Staff filter
400
- $('.ab-staff-filter-button').on('click', function (e) {
401
- e.stopPropagation();
402
- var menu = $(this).parent().find('.dropdown-menu');
403
- if (menu.hasClass('open')) {
404
- menu.removeClass('open').hide();
405
- } else {
406
- menu.addClass('open').show();
407
- }
408
- });
409
 
410
- $('.dropdown-menu').on('click', function (e) {
411
- e.stopPropagation();
412
- });
413
 
414
- $all_staff_option.on('change', function () {
415
- $staff_option.prop('checked', this.checked);
416
- if (this.checked) {
417
- $staff_option.filter(':first').trigger('change');
418
- $staff_tabs.show();
419
- } else {
420
- $week_calendar_wrapper.hide();
421
- $day_calendar_wrapper.hide();
422
- }
423
- });
424
-
425
- $staff_option.on('change', function (e) {
426
- var self = $(this),
427
- $tab = $('.ab-staff-tab-' + self.val()),
428
- $active_tab = $('ul.nav-tabs').find('li.active'),
429
- is_day = $('button.ab-calendar-day').hasClass('ab-button-active'),
430
- staff_option_checked = parseInt($staff_option.filter(':checked').length),
431
- unchecked_options = [];
432
-
433
- $all_staff_option.prop('checked', $staff_option.filter(':not(:checked)').length == 0);
434
-
435
- if (is_day) { // Day
436
- // checkboxes
437
- if (staff_option_checked) {
438
- $day_calendar_wrapper.show();
439
- // Refresh visible users in calendar.
440
- var users = [];
441
- $('.ab-staff-option:checked').each(function() {
442
- users.push({staff_id: parseInt(this.value), full_name: $(this).next().text()});
443
- });
444
- $day_calendar.weekCalendar('option', 'users', users);
445
- $day_calendar.weekCalendar('refresh');
446
- // css-fix
447
- $('td.wc-scrollbar-shim').hide();
448
- } else { // No staff selected
449
- $day_calendar_wrapper.hide();
450
- }
451
- } else { // Week
452
- if (this.checked) {
453
- $tab.show().click();
454
- $staff_option.each(function(k, v) {
455
- if ($(v).is(':not(:checked)') && !unchecked_options[$(v).val()]) {
456
- unchecked_options.push($(v).val());
457
- }
458
- });
459
- $active_tab.parent().find('li').each(function(k, v) {
460
- unchecked_options.forEach(function(option) {
461
- if ($(v).data('staff-id') == option) {
462
- $('ul.nav-tabs').find('li').filter('[data-staff-id="' + option + '"]').hide();
463
  }
464
- });
465
- });
466
- } else {
467
- $tab.hide();
468
- $active_tab.is(':visible') ? $active_tab.click() : $staff_tabs.filter(':visible').filter(':first').click();
469
- }
470
- staff_option_checked ? $week_calendar_wrapper.show() : $week_calendar_wrapper.hide();
471
- }
472
 
473
- // Changes staff filter button name
474
- var selected_staff_numb = $staff_option.filter(':checked').length;
475
- if (selected_staff_numb == 0) {
476
- $staff_filter_button.text('No staff selected');
477
- } else if (selected_staff_numb == 1) {
478
- $staff_filter_button.text($("label[for='" + $staff_option.filter(':checked').attr('id') + "']").text());
479
- } else {
480
- $staff_filter_button.text(selected_staff_numb + ' staff members');
481
- }
482
- });
483
 
484
- // End staff filter
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
 
486
- /**
487
- * Get formatted date(php: Y-m-d H:i:s) for calendar
488
- *
489
- * @param date
490
- * @return {String}
491
- */
492
- function getFormattedDateForCalendar(date) {
493
- var $hours = date.getHours(), $minutes = date.getMinutes();
494
 
495
- if ($hours < 10) {
496
- $hours = '0' + $hours;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
  }
498
 
499
- if ($minutes < 10) {
500
- $minutes = '0' + $minutes;
 
 
 
 
 
 
 
 
501
  }
502
 
503
- return $.datepicker.formatDate( 'yy-mm-dd ', date ) + $hours + ':' + $minutes + ':00';
504
- }
 
 
 
 
 
 
 
 
505
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
 
507
- /*
508
- * scroll width
509
- *
510
- * not null for preventing console errors when no one staff exists
511
- */
512
- var scroll = document.querySelector('.wc-scrollable-grid'),
513
- scrollShim = document.querySelector('.wc-scrollbar-shim'),
514
- scrollWidth = scroll !== null ? scroll.offsetWidth - scroll.clientWidth : 0;
515
 
516
- if ( scrollShim !== null ) scrollShim.style.width = scrollWidth + 'px';
517
 
518
- /* firefox bug border */
519
- if ( $.browser.mozilla )
520
- $('.wc-time-column-header:first-child').css('width','43px');
521
 
522
- $('#email_notification').on('click', function() {
523
- $('#email_notification_text').show();
524
- });
525
  });
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 = [],
10
+ staffIds = getCookie('bookly_cal_st_ids'),
11
+ tabId = getCookie('bookly_cal_tab_id'),
12
+ lastView = getCookie('bookly_cal_view'),
13
+ views = 'month,agendaWeek,agendaDay,multiStaffDay';
14
+
15
+ if (views.indexOf(lastView) == -1) {
16
+ lastView = 'agendaWeek';
17
+ }
18
+ // Init tabs and staff member filters.
19
+ if (staffIds === null) {
20
+ $staff.each(function(index, value){
21
+ this.checked = true;
22
+ $tabs.filter('[data-staff_id=' + this.value + ']').show();
23
+ });
24
+ } else if (staffIds != '') {
25
+ $.each(staffIds.split(','), function (index, value){
26
+ $staff.filter('[value=' + value + ']').prop('checked', true);
27
+ $tabs.filter('[data-staff_id=' + value + ']').show();
28
+ });
29
+ } else {
30
+ $('.dropdown-toggle').dropdown('toggle');
31
+ }
32
+
33
+ $tabs.filter('[data-staff_id=' + tabId + ']').addClass('active');
34
+ if ($tabs.filter('li.active').length == 0) {
35
+ $tabs.eq(0).addClass('active').show();
36
+ $staff.filter('[value=' + $tabs.eq(0).data('staff_id') + ']').prop('checked', true);
37
+ }
38
+ updateStaffButton();
39
+
40
+ // Init FullCalendar.
41
+ $fullCalendar.fullCalendar({
42
+ // General Display.
43
+ firstDay: BooklyL10n.startOfWeek,
44
+ header: {
45
+ left: 'prev,next today',
46
+ center: 'title',
47
+ right: views
48
  },
49
+ height: heightFC(),
50
+ // Views.
51
+ defaultView: lastView,
52
+ scrollTime: firstHour+':00:00',
53
+ views: {
54
+ agendaWeek: {
55
+ columnFormat: 'ddd, D'
56
+ },
57
+ multiStaffDay: {
58
+ staffMembers: staffMembers
59
+ }
60
  },
61
+ eventBackgroundColor: 'silver',
62
+ // Agenda Options.
63
+ allDaySlot: false,
64
+ allDayText: BooklyL10n.allDay,
65
+ axisFormat: BooklyL10n.mjsTimeFormat,
66
+ slotDuration: BooklyL10n.slotDuration,
67
+ // Text/Time Customization.
68
+ timeFormat: BooklyL10n.mjsTimeFormat,
69
+ displayEventEnd: true,
70
+ buttonText : {
71
+ today: BooklyL10n.today,
72
+ month: BooklyL10n.month,
73
+ week: BooklyL10n.week,
74
+ day: BooklyL10n.day
75
  },
76
+ monthNames: BooklyL10n.longMonths,
77
+ monthNamesShort: BooklyL10n.shortMonths,
78
+ dayNames: BooklyL10n.longDays,
79
+ dayNamesShort: BooklyL10n.shortDays,
80
+ // Event Dragging & Resizing.
81
+ editable: false,
82
+ // Event Data.
83
+ eventSources: [{
84
+ url: ajaxurl,
85
+ data: {
86
+ action : 'ab_get_staff_appointments',
87
+ staff_ids : function() {
88
+ var ids = [];
89
+ if ($tabs.filter('.active').data('staff_id') == 0) {
90
+ for (var i = 0; i < staffMembers.length; ++i) {
91
+ ids.push(staffMembers[i].id);
92
+ }
93
+ } else {
94
+ ids.push($tabs.filter('.active').data('staff_id'));
95
+ }
96
+ return ids;
97
+ }
98
+ }
99
+ }],
100
+ // Event Rendering.
101
  eventRender : function(calEvent, $event) {
102
+ var body = '<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;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  }
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 () {
114
+ $fullCalendar.fullCalendar('removeEvents', calEvent.id);
115
+ });
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);
155
+ var staffMembers = view.opt('staffMembers');
156
+ staff_id = staffMembers[cell.col].id;
157
+ visible_staff_id = 0;
158
+ } else {
159
+ staff_id = visible_staff_id = $tabs.filter('.active').data('staff_id');
160
+ }
161
+
162
+ showAppointmentDialog(
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();
174
+ }
175
+ }
176
+ );
177
  },
178
+ eventClick: function(calEvent, jsEvent, view) {
179
+ var visible_staff_id;
180
+ if (view.type == 'multiStaffDay') {
181
+ visible_staff_id = 0;
182
+ } else {
183
+ visible_staff_id = calEvent.staffId;
184
+ }
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);
195
+ $fullCalendar.fullCalendar('updateEvent', calEvent);
196
+ } else {
197
+ // Switch to the event owner tab.
198
+ jQuery('li[data-staff_id=' + event.staffId + ']').click();
199
+ }
200
+ }
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
+
211
+ $('.fc-agendaDay-button').addClass('fc-corner-right');
212
+ if ($tabs.filter('.active').data('staff_id') == 0) {
213
+ $('.fc-agendaDay-button').hide();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  } else {
215
+ $('.fc-multiStaffDay-button').hide();
216
  }
 
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');
260
+
261
+ var staff_id = $(this).data('staff_id');
262
+ setCookie('bookly_cal_tab_id', staff_id);
263
+
264
+ if (staff_id == 0) {
265
+ $('.fc-agendaDay-button').hide();
266
+ $('.fc-multiStaffDay-button').show();
267
+ $fullCalendar.fullCalendar('changeView', 'multiStaffDay');
268
+ $fullCalendar.fullCalendar('refetchEvents');
269
+ } else {
270
+ $('.fc-multiStaffDay-button').hide();
271
+ $('.fc-agendaDay-button').show();
272
+ var view = $fullCalendar.fullCalendar('getView');
273
+ if (view.type == 'multiStaffDay') {
274
+ $fullCalendar.fullCalendar('changeView', 'agendaDay');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  }
276
+ $fullCalendar.fullCalendar('refetchEvents');
277
+ }
278
+ });
 
 
 
 
 
279
 
280
+ $('.dropdown-menu').on('click', function (e) {
281
+ e.stopPropagation();
282
+ });
 
 
 
 
 
 
 
283
 
284
+ /**
285
+ * On show all staff checkbox click.
286
+ */
287
+ $showAll.on('change', function () {
288
+ $tabs.filter('[data-staff_id!=0]').toggle(this.checked);
289
+ $staff
290
+ .prop('checked', this.checked)
291
+ .filter(':first').triggerHandler('change');
292
+ });
293
+
294
+ /**
295
+ * On staff checkbox click.
296
+ */
297
+ $staff.on('change', function (e) {
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');
305
+ if (view.type == 'multiStaffDay') {
306
+ view.displayView($fullCalendar.fullCalendar('getDate'));
307
+ }
308
+ $fullCalendar.fullCalendar('refetchEvents');
309
+ }
310
+ });
311
 
312
+ function updateStaffButton() {
313
+ $showAll.prop('checked', $staff.filter(':not(:checked)').length == 0);
 
 
 
 
 
 
314
 
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
+ });
322
+ setCookie('bookly_cal_st_ids', ids);
323
+
324
+ // Update button text.
325
+ var number = $staff.filter(':checked').length;
326
+ if (number == 0) {
327
+ $staffButton.text(BooklyL10n.noStaffSelected);
328
+ } else if (number == 1) {
329
+ $staffButton.text($staff.filter(':checked').data('staff_name'));
330
+ } else {
331
+ $staffButton.text(number + '/' + $staff.length);
332
+ }
333
  }
334
 
335
+ /**
336
+ * Set cookie.
337
+ *
338
+ * @param key
339
+ * @param value
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
 
347
+ /**
348
+ * Get cookie.
349
+ *
350
+ * @param key
351
+ * @return {*}
352
+ */
353
+ function getCookie(key) {
354
+ var keyValue = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
355
+ return keyValue ? keyValue[2] : null;
356
+ }
357
 
358
+ /**
359
+ * Calculate height of FullCalendar.
360
+ *
361
+ * @return {number}
362
+ */
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
+
370
+ if ($wrap.css('margin-top')) {
371
+ height_to_reduce += parseInt($wrap.css('margin-top').replace('px', ''), 10);
372
+ }
373
 
374
+ if ($wrap.css('margin-bottom')) {
375
+ height_to_reduce += parseInt($wrap.css('margin-bottom').replace('px', ''), 10);
376
+ }
 
 
 
 
 
377
 
378
+ var res = window_height - height_to_reduce - 130;
379
 
380
+ return res > 620 ? res : 620;
381
+ }
 
382
 
 
 
 
383
  });
backend/modules/calendar/resources/js/calendar_daypicker.js DELETED
@@ -1,126 +0,0 @@
1
- /**
2
- * Day calendar datepicker.
3
- */
4
- function DayPicker() {
5
- // Private functions and variables.
6
- var widget = {
7
- $container : null,
8
- $month_days : null,
9
- $prev_date_handler : null,
10
- $next_date_handler : null,
11
- $picker : null, // jQuery UI DatePicker.
12
- $calendar : null, // WeekCalendar instance.
13
- first_day : null,
14
- rebuildDayLine : function(date) {
15
- var timestamp = +date / 1000,
16
- seconds_in_day = 60 * 60 * 24;
17
- for (var i = 1; i <= 7; ++ i) {
18
- var next_day_timestamp = (timestamp + (i * seconds_in_day)) * 1000,
19
- next_day = new Date(next_day_timestamp),
20
- $day_of_month = jQuery(this.$month_days.get(i - 1));
21
- $day_of_month
22
- .text(next_day.getDate())
23
- .data('date_timestamp', next_day_timestamp / 1000);
24
- }
25
- },
26
- /**
27
- * Constructor.
28
- */
29
- init : function() {
30
- this.$container = jQuery('div#day-calendar-picker');
31
- this.$month_days = this.$container.find('.ab-day-of-month');
32
- this.$prev_date_handler = this.$container.find('.ab-week-picker-arrow-prev');
33
- this.$next_date_handler = this.$container.find('.ab-week-picker-arrow-next');
34
- this.$picker = this.$container.find('#appendedInput');
35
- this.first_day = this.$container.data('first_day');
36
- // Init datepicker.
37
- this.$picker.datepicker({
38
- dateFormat : BooklyL10n['dateFormat'],
39
- showOtherMonths : true,
40
- selectOtherMonths : true,
41
- firstDay : widget.first_day,
42
- monthNames : BooklyL10n['longMonths'],
43
- monthNamesShort : BooklyL10n['shortMonths'],
44
- dayNames : BooklyL10n['longDays'],
45
- dayNamesMin : BooklyL10n['shortDays'],
46
- dayNamesShort : BooklyL10n['shortDays'],
47
- onSelect : function() {
48
- var date = widget.$picker.datepicker('getDate');
49
- widget.rebuildDayLine(date);
50
- if (widget.$calendar) {
51
- widget.$calendar.weekCalendar('gotoWeek', date);
52
- }
53
- widget.$month_days.parent().removeClass('active');
54
- }
55
- });
56
- // Handle events.
57
- this.$month_days.on('click', function(e) {
58
- e.preventDefault();
59
- var $clicked = jQuery(this),
60
- $clicked_day_date = new Date(parseInt($clicked.data('date_timestamp'), 10) * 1000);
61
- widget.rebuildDayLine($clicked_day_date);
62
- widget.$picker.datepicker('setDate', $clicked_day_date);
63
- widget.$month_days.parent().removeClass('active');
64
- $clicked.parent().addClass('active');
65
- if (widget.$calendar) {
66
- widget.$calendar.weekCalendar('gotoWeek', $clicked_day_date);
67
- }
68
- });
69
- this.$prev_date_handler.on('click', function() {
70
- var date = new Date();
71
- date.setDate(widget.$picker.datepicker('getDate').getDate() - 1);
72
- widget.rebuildDayLine(date);
73
- widget.$picker.datepicker('setDate', date);
74
- if (widget.$calendar) {
75
- widget.$calendar.weekCalendar('gotoWeek', date);
76
- }
77
- });
78
- this.$next_date_handler.on('click', function() {
79
- var date = new Date();
80
- date.setDate(widget.$picker.datepicker('getDate').getDate() + 1);
81
- widget.rebuildDayLine(date);
82
- widget.$picker.datepicker('setDate', date);
83
- if (widget.$calendar) {
84
- widget.$calendar.weekCalendar('gotoWeek', date);
85
- }
86
- });
87
- }
88
- };
89
-
90
- // Init.
91
- widget.init();
92
-
93
- // Return public methods.
94
- return {
95
- show : function() {
96
- widget.$container.show();
97
- },
98
- hide : function() {
99
- widget.$container.hide();
100
- },
101
-
102
- /**
103
- * Set specific date to the widget (and update $calendar if update_week_calendar is true).
104
- *
105
- * @param date
106
- * @param update_week_calendar
107
- */
108
- setDate : function(date, update_week_calendar) {
109
- widget.rebuildDayLine(date);
110
- widget.$picker.datepicker('setDate', date);
111
- if (widget.$calendar && (update_week_calendar === undefined || update_week_calendar)) {
112
- widget.$calendar.weekCalendar('gotoWeek', date);
113
- }
114
- },
115
- getDate : function() {
116
- return widget.$picker.datepicker('getDate');
117
- },
118
- /**
119
- * Attach WeekCalendar to the picker.
120
- * @param WeekCalendar $calendar
121
- */
122
- attachCalendar : function($calendar) {
123
- widget.$calendar = $calendar;
124
- }
125
- };
126
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/resources/js/calendar_weekpicker.js DELETED
@@ -1,168 +0,0 @@
1
- /**
2
- * Week calendar datepicker.
3
- */
4
- function WeekPicker() {
5
- // Private functions and variables.
6
- var widget = {
7
- $container : null,
8
- $picker : null, // jQuery UI DatePicker.
9
- $output : null, // Place to display week-start and week-end dates.
10
- $calendar : null, // WeekCalendar instance.
11
- start_date : null,
12
- end_date : null,
13
- first_day : null,
14
- date_format : null,
15
- /**
16
- * Set start and end dates based on Date object.
17
- * @param Date date.
18
- */
19
- updateStartAndEndDates : function(date) {
20
- var seconds_in_one_day = 86400000;
21
- var days_to_rewind = date.getDay() <= 0 ? 7 - this.first_day : date.getDay() - this.first_day;
22
- var days_to_add = 6;
23
- this.start_date = new Date(date.valueOf() - days_to_rewind * seconds_in_one_day); //rewind to start day
24
- this.end_date = new Date(this.start_date.valueOf() + days_to_add * seconds_in_one_day); //add 6 days to get last day
25
- },
26
- /**
27
- * Highlight all days in currently selected week.
28
- */
29
- highlightSelectedWeek : function() {
30
- window.setTimeout(function () {
31
- widget.$picker.find('.ui-datepicker-current-day a').addClass('ui-state-active')
32
- }, 1);
33
- },
34
- /**
35
- * Display start and end dates of the selected week.
36
- * @param Date start_date
37
- * @param Date end_date
38
- */
39
- outputFormattedDate : function() {
40
- // show formatted date values
41
- var formatted_start_date = jQuery.datepicker.formatDate(this.date_format, this.start_date, {monthNamesShort: BooklyL10n['shortMonths'], monthNames : BooklyL10n['longMonths']}),
42
- formatted_end_date = jQuery.datepicker.formatDate(this.date_format, this.end_date, {monthNamesShort: BooklyL10n['shortMonths'], monthNames : BooklyL10n['longMonths']});
43
- this.$output.val(BooklyL10n['Week'] + formatted_start_date + ' - ' + formatted_end_date);
44
- },
45
- /**
46
- * Set specific date to the widget (and update $calendar if update_week_calendar is true).
47
- *
48
- * @param date
49
- * @param update_week_calendar
50
- */
51
- setDate : function(date, update_week_calendar) {
52
- this.updateStartAndEndDates(date);
53
- this.$picker.datepicker('setDate', date);
54
- this.outputFormattedDate();
55
- this.highlightSelectedWeek();
56
- if (this.$calendar && (update_week_calendar === undefined || update_week_calendar)) {
57
- this.$calendar.weekCalendar('gotoWeek', this.start_date);
58
- }
59
- },
60
- /**
61
- * Constructor.
62
- */
63
- init : function() {
64
- this.$container = jQuery('div#week-calendar-picker');
65
- this.$picker = this.$container.find('div.ab-week-picker');
66
- this.$output = this.$container.find('input.ab-date-calendar');
67
- this.first_day = this.$container.data('first_day');
68
- this.date_format = BooklyL10n['dateFormat'];
69
- // Init start and end dates.
70
- this.updateStartAndEndDates(new Date());
71
- // Init datepicker.
72
- this.$picker.datepicker({
73
- showOtherMonths : true,
74
- selectOtherMonths : true,
75
- firstDay : widget.first_day,
76
- monthNames : BooklyL10n['longMonths'],
77
- monthNamesShort : BooklyL10n['shortMonths'],
78
- dayNames : BooklyL10n['longDays'],
79
- dayNamesMin : BooklyL10n['shortDays'],
80
- dayNamesShort : BooklyL10n['shortDays'],
81
- onSelect : function(dateText, inst) {
82
- widget.updateStartAndEndDates(widget.$picker.datepicker('getDate'));
83
- widget.outputFormattedDate();
84
- widget.highlightSelectedWeek();
85
- if (widget.$calendar) {
86
- widget.$calendar.weekCalendar('gotoWeek', widget.start_date);
87
- }
88
- },
89
- beforeShowDay : function(date) {
90
- var cssClass = '';
91
- if ((date >= widget.start_date || date.getDate() == widget.start_date.getDate()) && date <= widget.end_date) {
92
- cssClass = 'ui-datepicker-current-day';
93
- }
94
- return [true, cssClass];
95
- },
96
- onChangeMonthYear : function(year, month, inst) {
97
- widget.highlightSelectedWeek();
98
- }
99
- });
100
- this.highlightSelectedWeek();
101
- // Display start and end dates.
102
- this.outputFormattedDate();
103
- // Handle events.
104
- this.$container
105
- .on('mousemove', '.ui-datepicker-calendar tr', function() {
106
- jQuery(this).find('td a').addClass('ui-state-hover');
107
- })
108
- .on('mouseleave', '.ui-datepicker-calendar tr', function() {
109
- jQuery(this).find('td a').removeClass('ui-state-hover');
110
- });
111
- jQuery('body').click(function(e) {
112
- jQuery('.dropdown-menu:visible').removeClass('open').hide();
113
- if (widget.$picker.is(':visible')) {
114
- widget.$picker.hide();
115
- }
116
- });
117
- this.$picker.click(function(e) {
118
- e.stopPropagation();
119
- });
120
- // open week picker
121
- this.$output.on('click', function(e) {
122
- widget.$picker.show();
123
- e.stopPropagation();
124
- });
125
- // do not close week picker when the previous or next arrow is clicked
126
- this.$container.find('.prev, .next').on('click', function(e) {
127
- e.stopPropagation();
128
- });
129
- // handle click on the "previous week" arrow
130
- this.$container.find('.prev').on('click', function() {
131
- var date = widget.$picker.datepicker('getDate');
132
- date.addDays(-7);
133
- widget.setDate(date);
134
- });
135
- // handle click on the "next week" arrow
136
- this.$container.find('.next').on('click', function() {
137
- var date = widget.$picker.datepicker('getDate');
138
- date.addDays(7);
139
- widget.setDate(date);
140
- });
141
- }
142
- };
143
-
144
- // Init.
145
- widget.init();
146
- // Return public methods.
147
- return {
148
- show : function() {
149
- widget.$container.show();
150
- },
151
- hide : function() {
152
- widget.$container.hide();
153
- },
154
- setDate : function(date, update_week_calendar) {
155
- widget.setDate(date, update_week_calendar);
156
- },
157
- getStartDate : function() {
158
- return widget.start_date;
159
- },
160
- /**
161
- * Attach WeekCalendar to the picker.
162
- * @param WeekCalendar $calendar
163
- */
164
- attachCalendar : function($calendar) {
165
- widget.$calendar = $calendar;
166
- }
167
- };
168
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/resources/js/chosen.jquery.js DELETED
@@ -1,1229 +0,0 @@
1
- /*!
2
- Chosen, a Select Box Enhancer for jQuery and Prototype
3
- by Patrick Filler for Harvest, http://getharvest.com
4
-
5
- Version 1.2.0
6
- Full source at https://github.com/harvesthq/chosen
7
- Copyright (c) 2011-2014 Harvest http://getharvest.com
8
-
9
- MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
10
- This file is generated by `grunt build`, do not edit it by hand.
11
- */
12
-
13
- (function() {
14
- var $, AbstractChosen, Chosen, SelectParser, _ref,
15
- __hasProp = {}.hasOwnProperty,
16
- __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
17
-
18
- SelectParser = (function() {
19
- function SelectParser() {
20
- this.options_index = 0;
21
- this.parsed = [];
22
- }
23
-
24
- SelectParser.prototype.add_node = function(child) {
25
- if (child.nodeName.toUpperCase() === "OPTGROUP") {
26
- return this.add_group(child);
27
- } else {
28
- return this.add_option(child);
29
- }
30
- };
31
-
32
- SelectParser.prototype.add_group = function(group) {
33
- var group_position, option, _i, _len, _ref, _results;
34
- group_position = this.parsed.length;
35
- this.parsed.push({
36
- array_index: group_position,
37
- group: true,
38
- label: this.escapeExpression(group.label),
39
- children: 0,
40
- disabled: group.disabled
41
- });
42
- _ref = group.childNodes;
43
- _results = [];
44
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
45
- option = _ref[_i];
46
- _results.push(this.add_option(option, group_position, group.disabled));
47
- }
48
- return _results;
49
- };
50
-
51
- SelectParser.prototype.add_option = function(option, group_position, group_disabled) {
52
- if (option.nodeName.toUpperCase() === "OPTION") {
53
- if (option.text !== "") {
54
- if (group_position != null) {
55
- this.parsed[group_position].children += 1;
56
- }
57
- this.parsed.push({
58
- array_index: this.parsed.length,
59
- options_index: this.options_index,
60
- value: option.value,
61
- text: option.text,
62
- html: option.innerHTML,
63
- selected: option.selected,
64
- disabled: group_disabled === true ? group_disabled : option.disabled,
65
- group_array_index: group_position,
66
- classes: option.className,
67
- style: option.style.cssText
68
- });
69
- } else {
70
- this.parsed.push({
71
- array_index: this.parsed.length,
72
- options_index: this.options_index,
73
- empty: true
74
- });
75
- }
76
- return this.options_index += 1;
77
- }
78
- };
79
-
80
- SelectParser.prototype.escapeExpression = function(text) {
81
- var map, unsafe_chars;
82
- if ((text == null) || text === false) {
83
- return "";
84
- }
85
- if (!/[\&\<\>\"\'\`]/.test(text)) {
86
- return text;
87
- }
88
- map = {
89
- "<": "&lt;",
90
- ">": "&gt;",
91
- '"': "&quot;",
92
- "'": "&#x27;",
93
- "`": "&#x60;"
94
- };
95
- unsafe_chars = /&(?!\w+;)|[\<\>\"\'\`]/g;
96
- return text.replace(unsafe_chars, function(chr) {
97
- return map[chr] || "&amp;";
98
- });
99
- };
100
-
101
- return SelectParser;
102
-
103
- })();
104
-
105
- SelectParser.select_to_array = function(select) {
106
- var child, parser, _i, _len, _ref;
107
- parser = new SelectParser();
108
- _ref = select.childNodes;
109
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
110
- child = _ref[_i];
111
- parser.add_node(child);
112
- }
113
- return parser.parsed;
114
- };
115
-
116
- AbstractChosen = (function() {
117
- function AbstractChosen(form_field, options) {
118
- this.form_field = form_field;
119
- this.options = options != null ? options : {};
120
- if (!AbstractChosen.browser_is_supported()) {
121
- return;
122
- }
123
- this.is_multiple = this.form_field.multiple;
124
- this.set_default_text();
125
- this.set_default_values();
126
- this.setup();
127
- this.set_up_html();
128
- this.register_observers();
129
- }
130
-
131
- AbstractChosen.prototype.set_default_values = function() {
132
- var _this = this;
133
- this.click_test_action = function(evt) {
134
- return _this.test_active_click(evt);
135
- };
136
- this.activate_action = function(evt) {
137
- return _this.activate_field(evt);
138
- };
139
- this.active_field = false;
140
- this.mouse_on_container = false;
141
- this.results_showing = false;
142
- this.result_highlighted = null;
143
- this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false;
144
- this.disable_search_threshold = this.options.disable_search_threshold || 0;
145
- this.disable_search = this.options.disable_search || false;
146
- this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true;
147
- this.group_search = this.options.group_search != null ? this.options.group_search : true;
148
- this.search_contains = this.options.search_contains || false;
149
- this.single_backstroke_delete = this.options.single_backstroke_delete != null ? this.options.single_backstroke_delete : true;
150
- this.max_selected_options = this.options.max_selected_options || Infinity;
151
- this.inherit_select_classes = this.options.inherit_select_classes || false;
152
- this.display_selected_options = this.options.display_selected_options != null ? this.options.display_selected_options : true;
153
- return this.display_disabled_options = this.options.display_disabled_options != null ? this.options.display_disabled_options : true;
154
- };
155
-
156
- AbstractChosen.prototype.set_default_text = function() {
157
- if (this.form_field.getAttribute("data-placeholder")) {
158
- this.default_text = this.form_field.getAttribute("data-placeholder");
159
- } else if (this.is_multiple) {
160
- this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text;
161
- } else {
162
- this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text;
163
- }
164
- return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || AbstractChosen.default_no_result_text;
165
- };
166
-
167
- AbstractChosen.prototype.mouse_enter = function() {
168
- return this.mouse_on_container = true;
169
- };
170
-
171
- AbstractChosen.prototype.mouse_leave = function() {
172
- return this.mouse_on_container = false;
173
- };
174
-
175
- AbstractChosen.prototype.input_focus = function(evt) {
176
- var _this = this;
177
- if (this.is_multiple) {
178
- if (!this.active_field) {
179
- return setTimeout((function() {
180
- return _this.container_mousedown();
181
- }), 50);
182
- }
183
- } else {
184
- if (!this.active_field) {
185
- return this.activate_field();
186
- }
187
- }
188
- };
189
-
190
- AbstractChosen.prototype.input_blur = function(evt) {
191
- var _this = this;
192
- if (!this.mouse_on_container) {
193
- this.active_field = false;
194
- return setTimeout((function() {
195
- return _this.blur_test();
196
- }), 100);
197
- }
198
- };
199
-
200
- AbstractChosen.prototype.results_option_build = function(options) {
201
- var content, data, _i, _len, _ref;
202
- content = '';
203
- _ref = this.results_data;
204
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
205
- data = _ref[_i];
206
- if (data.group) {
207
- content += this.result_add_group(data);
208
- } else {
209
- content += this.result_add_option(data);
210
- }
211
- if (options != null ? options.first : void 0) {
212
- if (data.selected && this.is_multiple) {
213
- this.choice_build(data);
214
- } else if (data.selected && !this.is_multiple) {
215
- this.single_set_selected_text(data.text);
216
- }
217
- }
218
- }
219
- return content;
220
- };
221
-
222
- AbstractChosen.prototype.result_add_option = function(option) {
223
- var classes, option_el;
224
- if (!option.search_match) {
225
- return '';
226
- }
227
- if (!this.include_option_in_results(option)) {
228
- return '';
229
- }
230
- classes = [];
231
- if (!option.disabled && !(option.selected && this.is_multiple)) {
232
- classes.push("active-result");
233
- }
234
- if (option.disabled && !(option.selected && this.is_multiple)) {
235
- classes.push("disabled-result");
236
- }
237
- if (option.selected) {
238
- classes.push("result-selected");
239
- }
240
- if (option.group_array_index != null) {
241
- classes.push("group-option");
242
- }
243
- if (option.classes !== "") {
244
- classes.push(option.classes);
245
- }
246
- option_el = document.createElement("li");
247
- option_el.className = classes.join(" ");
248
- option_el.style.cssText = option.style;
249
- option_el.setAttribute("data-option-array-index", option.array_index);
250
- option_el.innerHTML = option.search_text;
251
- return this.outerHTML(option_el);
252
- };
253
-
254
- AbstractChosen.prototype.result_add_group = function(group) {
255
- var group_el;
256
- if (!(group.search_match || group.group_match)) {
257
- return '';
258
- }
259
- if (!(group.active_options > 0)) {
260
- return '';
261
- }
262
- group_el = document.createElement("li");
263
- group_el.className = "group-result";
264
- group_el.innerHTML = group.search_text;
265
- return this.outerHTML(group_el);
266
- };
267
-
268
- AbstractChosen.prototype.results_update_field = function() {
269
- this.set_default_text();
270
- if (!this.is_multiple) {
271
- this.results_reset_cleanup();
272
- }
273
- this.result_clear_highlight();
274
- this.results_build();
275
- if (this.results_showing) {
276
- return this.winnow_results();
277
- }
278
- };
279
-
280
- AbstractChosen.prototype.reset_single_select_options = function() {
281
- var result, _i, _len, _ref, _results;
282
- _ref = this.results_data;
283
- _results = [];
284
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
285
- result = _ref[_i];
286
- if (result.selected) {
287
- _results.push(result.selected = false);
288
- } else {
289
- _results.push(void 0);
290
- }
291
- }
292
- return _results;
293
- };
294
-
295
- AbstractChosen.prototype.results_toggle = function() {
296
- if (this.results_showing) {
297
- return this.results_hide();
298
- } else {
299
- return this.results_show();
300
- }
301
- };
302
-
303
- AbstractChosen.prototype.results_search = function(evt) {
304
- if (this.results_showing) {
305
- return this.winnow_results();
306
- } else {
307
- return this.results_show();
308
- }
309
- };
310
-
311
- AbstractChosen.prototype.winnow_results = function() {
312
- var escapedSearchText, option, regex, results, results_group, searchText, startpos, text, zregex, _i, _len, _ref;
313
- this.no_results_clear();
314
- results = 0;
315
- searchText = this.get_search_text();
316
- escapedSearchText = searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
317
- zregex = new RegExp(escapedSearchText, 'i');
318
- regex = this.get_search_regex(escapedSearchText);
319
- _ref = this.results_data;
320
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
321
- option = _ref[_i];
322
- option.search_match = false;
323
- results_group = null;
324
- if (this.include_option_in_results(option)) {
325
- if (option.group) {
326
- option.group_match = false;
327
- option.active_options = 0;
328
- }
329
- if ((option.group_array_index != null) && this.results_data[option.group_array_index]) {
330
- results_group = this.results_data[option.group_array_index];
331
- if (results_group.active_options === 0 && results_group.search_match) {
332
- results += 1;
333
- }
334
- results_group.active_options += 1;
335
- }
336
- if (!(option.group && !this.group_search)) {
337
- option.search_text = option.group ? option.label : option.text;
338
- option.search_match = this.search_string_match(option.search_text, regex);
339
- if (option.search_match && !option.group) {
340
- results += 1;
341
- }
342
- if (option.search_match) {
343
- if (searchText.length) {
344
- startpos = option.search_text.search(zregex);
345
- text = option.search_text.substr(0, startpos + searchText.length) + '</em>' + option.search_text.substr(startpos + searchText.length);
346
- option.search_text = text.substr(0, startpos) + '<em>' + text.substr(startpos);
347
- }
348
- if (results_group != null) {
349
- results_group.group_match = true;
350
- }
351
- } else if ((option.group_array_index != null) && this.results_data[option.group_array_index].search_match) {
352
- option.search_match = true;
353
- }
354
- }
355
- }
356
- }
357
- this.result_clear_highlight();
358
- if (results < 1 && searchText.length) {
359
- this.update_results_content("");
360
- return this.no_results(searchText);
361
- } else {
362
- this.update_results_content(this.results_option_build());
363
- return this.winnow_results_set_highlight();
364
- }
365
- };
366
-
367
- AbstractChosen.prototype.get_search_regex = function(escaped_search_string) {
368
- var regex_anchor;
369
- regex_anchor = this.search_contains ? "" : "^";
370
- return new RegExp(regex_anchor + escaped_search_string, 'i');
371
- };
372
-
373
- AbstractChosen.prototype.search_string_match = function(search_string, regex) {
374
- var part, parts, _i, _len;
375
- if (regex.test(search_string)) {
376
- return true;
377
- } else if (this.enable_split_word_search && (search_string.indexOf(" ") >= 0 || search_string.indexOf("[") === 0)) {
378
- parts = search_string.replace(/\[|\]/g, "").split(" ");
379
- if (parts.length) {
380
- for (_i = 0, _len = parts.length; _i < _len; _i++) {
381
- part = parts[_i];
382
- if (regex.test(part)) {
383
- return true;
384
- }
385
- }
386
- }
387
- }
388
- };
389
-
390
- AbstractChosen.prototype.choices_count = function() {
391
- var option, _i, _len, _ref;
392
- if (this.selected_option_count != null) {
393
- return this.selected_option_count;
394
- }
395
- this.selected_option_count = 0;
396
- _ref = this.form_field.options;
397
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
398
- option = _ref[_i];
399
- if (option.selected) {
400
- this.selected_option_count += 1;
401
- }
402
- }
403
- return this.selected_option_count;
404
- };
405
-
406
- AbstractChosen.prototype.choices_click = function(evt) {
407
- evt.preventDefault();
408
- if (!(this.results_showing || this.is_disabled)) {
409
- return this.results_show();
410
- }
411
- };
412
-
413
- AbstractChosen.prototype.keyup_checker = function(evt) {
414
- var stroke, _ref;
415
- stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
416
- this.search_field_scale();
417
- switch (stroke) {
418
- case 8:
419
- if (this.is_multiple && this.backstroke_length < 1 && this.choices_count() > 0) {
420
- return this.keydown_backstroke();
421
- } else if (!this.pending_backstroke) {
422
- this.result_clear_highlight();
423
- return this.results_search();
424
- }
425
- break;
426
- case 13:
427
- evt.preventDefault();
428
- if (this.results_showing) {
429
- return this.result_select(evt);
430
- }
431
- break;
432
- case 27:
433
- if (this.results_showing) {
434
- this.results_hide();
435
- }
436
- return true;
437
- case 9:
438
- case 38:
439
- case 40:
440
- case 16:
441
- case 91:
442
- case 17:
443
- break;
444
- default:
445
- return this.results_search();
446
- }
447
- };
448
-
449
- AbstractChosen.prototype.clipboard_event_checker = function(evt) {
450
- var _this = this;
451
- return setTimeout((function() {
452
- return _this.results_search();
453
- }), 50);
454
- };
455
-
456
- AbstractChosen.prototype.container_width = function() {
457
- if (this.options.width != null) {
458
- return this.options.width;
459
- } else {
460
- return "" + this.form_field.offsetWidth + "px";
461
- }
462
- };
463
-
464
- AbstractChosen.prototype.include_option_in_results = function(option) {
465
- if (this.is_multiple && (!this.display_selected_options && option.selected)) {
466
- return false;
467
- }
468
- if (!this.display_disabled_options && option.disabled) {
469
- return false;
470
- }
471
- if (option.empty) {
472
- return false;
473
- }
474
- return true;
475
- };
476
-
477
- AbstractChosen.prototype.search_results_touchstart = function(evt) {
478
- this.touch_started = true;
479
- return this.search_results_mouseover(evt);
480
- };
481
-
482
- AbstractChosen.prototype.search_results_touchmove = function(evt) {
483
- this.touch_started = false;
484
- return this.search_results_mouseout(evt);
485
- };
486
-
487
- AbstractChosen.prototype.search_results_touchend = function(evt) {
488
- if (this.touch_started) {
489
- return this.search_results_mouseup(evt);
490
- }
491
- };
492
-
493
- AbstractChosen.prototype.outerHTML = function(element) {
494
- var tmp;
495
- if (element.outerHTML) {
496
- return element.outerHTML;
497
- }
498
- tmp = document.createElement("div");
499
- tmp.appendChild(element);
500
- return tmp.innerHTML;
501
- };
502
-
503
- AbstractChosen.browser_is_supported = function() {
504
- if (window.navigator.appName === "Microsoft Internet Explorer") {
505
- return document.documentMode >= 8;
506
- }
507
- if (/iP(od|hone)/i.test(window.navigator.userAgent)) {
508
- return false;
509
- }
510
- if (/Android/i.test(window.navigator.userAgent)) {
511
- if (/Mobile/i.test(window.navigator.userAgent)) {
512
- return false;
513
- }
514
- }
515
- return true;
516
- };
517
-
518
- AbstractChosen.default_multiple_text = "Select Some Options";
519
-
520
- AbstractChosen.default_single_text = "Select an Option";
521
-
522
- AbstractChosen.default_no_result_text = "No results match";
523
-
524
- return AbstractChosen;
525
-
526
- })();
527
-
528
- $ = jQuery;
529
-
530
- $.fn.extend({
531
- chosen: function(options) {
532
- if (!AbstractChosen.browser_is_supported()) {
533
- return this;
534
- }
535
- return this.each(function(input_field) {
536
- var $this, chosen;
537
- $this = $(this);
538
- chosen = $this.data('chosen');
539
- if (options === 'destroy' && chosen instanceof Chosen) {
540
- chosen.destroy();
541
- } else if (!(chosen instanceof Chosen)) {
542
- $this.data('chosen', new Chosen(this, options));
543
- }
544
- });
545
- }
546
- });
547
-
548
- Chosen = (function(_super) {
549
- __extends(Chosen, _super);
550
-
551
- function Chosen() {
552
- _ref = Chosen.__super__.constructor.apply(this, arguments);
553
- return _ref;
554
- }
555
-
556
- Chosen.prototype.setup = function() {
557
- this.form_field_jq = $(this.form_field);
558
- this.current_selectedIndex = this.form_field.selectedIndex;
559
- return this.is_rtl = this.form_field_jq.hasClass("chosen-rtl");
560
- };
561
-
562
- Chosen.prototype.set_up_html = function() {
563
- var container_classes, container_props;
564
- container_classes = ["chosen-container"];
565
- container_classes.push("chosen-container-" + (this.is_multiple ? "multi" : "single"));
566
- if (this.inherit_select_classes && this.form_field.className) {
567
- container_classes.push(this.form_field.className);
568
- }
569
- if (this.is_rtl) {
570
- container_classes.push("chosen-rtl");
571
- }
572
- container_props = {
573
- 'class': container_classes.join(' '),
574
- 'style': "width: " + (this.container_width()) + ";",
575
- 'title': this.form_field.title
576
- };
577
- if (this.form_field.id.length) {
578
- container_props.id = this.form_field.id.replace(/[^\w]/g, '_') + "_chosen";
579
- }
580
- this.container = $("<div />", container_props);
581
- if (this.is_multiple) {
582
- this.container.html('<ul class="chosen-choices"><li class="search-field"><input type="text" value="' + this.default_text + '" class="default" autocomplete="off" style="width:25px;" /></li></ul><div class="chosen-drop"><ul class="chosen-results"></ul></div>');
583
- } else {
584
- this.container.html('<a class="chosen-single chosen-default" tabindex="-1"><span>' + this.default_text + '</span><div><b></b></div></a><div class="chosen-drop"><div class="chosen-search"><input type="text" autocomplete="off" /></div><ul class="chosen-results"></ul></div>');
585
- }
586
- this.form_field_jq.hide().after(this.container);
587
- this.dropdown = this.container.find('div.chosen-drop').first();
588
- this.search_field = this.container.find('input').first();
589
- this.search_results = this.container.find('ul.chosen-results').first();
590
- this.search_field_scale();
591
- this.search_no_results = this.container.find('li.no-results').first();
592
- if (this.is_multiple) {
593
- this.search_choices = this.container.find('ul.chosen-choices').first();
594
- this.search_container = this.container.find('li.search-field').first();
595
- } else {
596
- this.search_container = this.container.find('div.chosen-search').first();
597
- this.selected_item = this.container.find('.chosen-single').first();
598
- }
599
- this.results_build();
600
- this.set_tab_index();
601
- this.set_label_behavior();
602
- return this.form_field_jq.trigger("chosen:ready", {
603
- chosen: this
604
- });
605
- };
606
-
607
- Chosen.prototype.register_observers = function() {
608
- var _this = this;
609
- this.container.bind('touchstart.chosen', function(evt) {
610
- _this.container_mousedown(evt);
611
- });
612
- this.container.bind('touchend.chosen', function(evt) {
613
- _this.container_mouseup(evt);
614
- });
615
- this.container.bind('mousedown.chosen', function(evt) {
616
- _this.container_mousedown(evt);
617
- });
618
- this.container.bind('mouseup.chosen', function(evt) {
619
- _this.container_mouseup(evt);
620
- });
621
- this.container.bind('mouseenter.chosen', function(evt) {
622
- _this.mouse_enter(evt);
623
- });
624
- this.container.bind('mouseleave.chosen', function(evt) {
625
- _this.mouse_leave(evt);
626
- });
627
- this.search_results.bind('mouseup.chosen', function(evt) {
628
- _this.search_results_mouseup(evt);
629
- });
630
- this.search_results.bind('mouseover.chosen', function(evt) {
631
- _this.search_results_mouseover(evt);
632
- });
633
- this.search_results.bind('mouseout.chosen', function(evt) {
634
- _this.search_results_mouseout(evt);
635
- });
636
- this.search_results.bind('mousewheel.chosen DOMMouseScroll.chosen', function(evt) {
637
- _this.search_results_mousewheel(evt);
638
- });
639
- this.search_results.bind('touchstart.chosen', function(evt) {
640
- _this.search_results_touchstart(evt);
641
- });
642
- this.search_results.bind('touchmove.chosen', function(evt) {
643
- _this.search_results_touchmove(evt);
644
- });
645
- this.search_results.bind('touchend.chosen', function(evt) {
646
- _this.search_results_touchend(evt);
647
- });
648
- this.form_field_jq.bind("chosen:updated.chosen", function(evt) {
649
- _this.results_update_field(evt);
650
- });
651
- this.form_field_jq.bind("chosen:activate.chosen", function(evt) {
652
- _this.activate_field(evt);
653
- });
654
- this.form_field_jq.bind("chosen:open.chosen", function(evt) {
655
- _this.container_mousedown(evt);
656
- });
657
- this.form_field_jq.bind("chosen:close.chosen", function(evt) {
658
- _this.input_blur(evt);
659
- });
660
- this.search_field.bind('blur.chosen', function(evt) {
661
- _this.input_blur(evt);
662
- });
663
- this.search_field.bind('keyup.chosen', function(evt) {
664
- _this.keyup_checker(evt);
665
- });
666
- this.search_field.bind('keydown.chosen', function(evt) {
667
- _this.keydown_checker(evt);
668
- });
669
- this.search_field.bind('focus.chosen', function(evt) {
670
- _this.input_focus(evt);
671
- });
672
- this.search_field.bind('cut.chosen', function(evt) {
673
- _this.clipboard_event_checker(evt);
674
- });
675
- this.search_field.bind('paste.chosen', function(evt) {
676
- _this.clipboard_event_checker(evt);
677
- });
678
- if (this.is_multiple) {
679
- return this.search_choices.bind('click.chosen', function(evt) {
680
- _this.choices_click(evt);
681
- });
682
- } else {
683
- return this.container.bind('click.chosen', function(evt) {
684
- evt.preventDefault();
685
- });
686
- }
687
- };
688
-
689
- Chosen.prototype.destroy = function() {
690
- $(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
691
- if (this.search_field[0].tabIndex) {
692
- this.form_field_jq[0].tabIndex = this.search_field[0].tabIndex;
693
- }
694
- this.container.remove();
695
- this.form_field_jq.removeData('chosen');
696
- return this.form_field_jq.show();
697
- };
698
-
699
- Chosen.prototype.search_field_disabled = function() {
700
- this.is_disabled = this.form_field_jq[0].disabled;
701
- if (this.is_disabled) {
702
- this.container.addClass('chosen-disabled');
703
- this.search_field[0].disabled = true;
704
- if (!this.is_multiple) {
705
- this.selected_item.unbind("focus.chosen", this.activate_action);
706
- }
707
- return this.close_field();
708
- } else {
709
- this.container.removeClass('chosen-disabled');
710
- this.search_field[0].disabled = false;
711
- if (!this.is_multiple) {
712
- return this.selected_item.bind("focus.chosen", this.activate_action);
713
- }
714
- }
715
- };
716
-
717
- Chosen.prototype.container_mousedown = function(evt) {
718
- if (!this.is_disabled) {
719
- if (evt && evt.type === "mousedown" && !this.results_showing) {
720
- evt.preventDefault();
721
- }
722
- if (!((evt != null) && ($(evt.target)).hasClass("search-choice-close"))) {
723
- if (!this.active_field) {
724
- if (this.is_multiple) {
725
- this.search_field.val("");
726
- }
727
- $(this.container[0].ownerDocument).bind('click.chosen', this.click_test_action);
728
- this.results_show();
729
- } else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chosen-single").length)) {
730
- evt.preventDefault();
731
- this.results_toggle();
732
- }
733
- return this.activate_field();
734
- }
735
- }
736
- };
737
-
738
- Chosen.prototype.container_mouseup = function(evt) {
739
- if (evt.target.nodeName === "ABBR" && !this.is_disabled) {
740
- return this.results_reset(evt);
741
- }
742
- };
743
-
744
- Chosen.prototype.search_results_mousewheel = function(evt) {
745
- var delta;
746
- if (evt.originalEvent) {
747
- delta = evt.originalEvent.deltaY || -evt.originalEvent.wheelDelta || evt.originalEvent.detail;
748
- }
749
- if (delta != null) {
750
- evt.preventDefault();
751
- if (evt.type === 'DOMMouseScroll') {
752
- delta = delta * 40;
753
- }
754
- return this.search_results.scrollTop(delta + this.search_results.scrollTop());
755
- }
756
- };
757
-
758
- Chosen.prototype.blur_test = function(evt) {
759
- if (!this.active_field && this.container.hasClass("chosen-container-active")) {
760
- return this.close_field();
761
- }
762
- };
763
-
764
- Chosen.prototype.close_field = function() {
765
- $(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
766
- this.active_field = false;
767
- this.results_hide();
768
- this.container.removeClass("chosen-container-active");
769
- this.clear_backstroke();
770
- this.show_search_field_default();
771
- return this.search_field_scale();
772
- };
773
-
774
- Chosen.prototype.activate_field = function() {
775
- this.container.addClass("chosen-container-active");
776
- this.active_field = true;
777
- this.search_field.val(this.search_field.val());
778
- return this.search_field.focus();
779
- };
780
-
781
- Chosen.prototype.test_active_click = function(evt) {
782
- var active_container;
783
- active_container = $(evt.target).closest('.chosen-container');
784
- if (active_container.length && this.container[0] === active_container[0]) {
785
- return this.active_field = true;
786
- } else {
787
- return this.close_field();
788
- }
789
- };
790
-
791
- Chosen.prototype.results_build = function() {
792
- this.parsing = true;
793
- this.selected_option_count = null;
794
- this.results_data = SelectParser.select_to_array(this.form_field);
795
- if (this.is_multiple) {
796
- this.search_choices.find("li.search-choice").remove();
797
- } else if (!this.is_multiple) {
798
- this.single_set_selected_text();
799
- if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) {
800
- this.search_field[0].readOnly = true;
801
- this.container.addClass("chosen-container-single-nosearch");
802
- } else {
803
- this.search_field[0].readOnly = false;
804
- this.container.removeClass("chosen-container-single-nosearch");
805
- }
806
- }
807
- this.update_results_content(this.results_option_build({
808
- first: true
809
- }));
810
- this.search_field_disabled();
811
- this.show_search_field_default();
812
- this.search_field_scale();
813
- return this.parsing = false;
814
- };
815
-
816
- Chosen.prototype.result_do_highlight = function(el) {
817
- var high_bottom, high_top, maxHeight, visible_bottom, visible_top;
818
- if (el.length) {
819
- this.result_clear_highlight();
820
- this.result_highlight = el;
821
- this.result_highlight.addClass("highlighted");
822
- maxHeight = parseInt(this.search_results.css("maxHeight"), 10);
823
- visible_top = this.search_results.scrollTop();
824
- visible_bottom = maxHeight + visible_top;
825
- high_top = this.result_highlight.position().top + this.search_results.scrollTop();
826
- high_bottom = high_top + this.result_highlight.outerHeight();
827
- if (high_bottom >= visible_bottom) {
828
- return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0);
829
- } else if (high_top < visible_top) {
830
- return this.search_results.scrollTop(high_top);
831
- }
832
- }
833
- };
834
-
835
- Chosen.prototype.result_clear_highlight = function() {
836
- if (this.result_highlight) {
837
- this.result_highlight.removeClass("highlighted");
838
- }
839
- return this.result_highlight = null;
840
- };
841
-
842
- Chosen.prototype.results_show = function() {
843
- if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
844
- this.form_field_jq.trigger("chosen:maxselected", {
845
- chosen: this
846
- });
847
- return false;
848
- }
849
- this.container.addClass("chosen-with-drop");
850
- this.results_showing = true;
851
- this.search_field.focus();
852
- this.search_field.val(this.search_field.val());
853
- this.winnow_results();
854
- return this.form_field_jq.trigger("chosen:showing_dropdown", {
855
- chosen: this
856
- });
857
- };
858
-
859
- Chosen.prototype.update_results_content = function(content) {
860
- return this.search_results.html(content);
861
- };
862
-
863
- Chosen.prototype.results_hide = function() {
864
- if (this.results_showing) {
865
- this.result_clear_highlight();
866
- this.container.removeClass("chosen-with-drop");
867
- this.form_field_jq.trigger("chosen:hiding_dropdown", {
868
- chosen: this
869
- });
870
- }
871
- return this.results_showing = false;
872
- };
873
-
874
- Chosen.prototype.set_tab_index = function(el) {
875
- var ti;
876
- if (this.form_field.tabIndex) {
877
- ti = this.form_field.tabIndex;
878
- this.form_field.tabIndex = -1;
879
- return this.search_field[0].tabIndex = ti;
880
- }
881
- };
882
-
883
- Chosen.prototype.set_label_behavior = function() {
884
- var _this = this;
885
- this.form_field_label = this.form_field_jq.parents("label");
886
- if (!this.form_field_label.length && this.form_field.id.length) {
887
- this.form_field_label = $("label[for='" + this.form_field.id + "']");
888
- }
889
- if (this.form_field_label.length > 0) {
890
- return this.form_field_label.bind('click.chosen', function(evt) {
891
- if (_this.is_multiple) {
892
- return _this.container_mousedown(evt);
893
- } else {
894
- return _this.activate_field();
895
- }
896
- });
897
- }
898
- };
899
-
900
- Chosen.prototype.show_search_field_default = function() {
901
- if (this.is_multiple && this.choices_count() < 1 && !this.active_field) {
902
- this.search_field.val(this.default_text);
903
- return this.search_field.addClass("default");
904
- } else {
905
- this.search_field.val("");
906
- return this.search_field.removeClass("default");
907
- }
908
- };
909
-
910
- Chosen.prototype.search_results_mouseup = function(evt) {
911
- var target;
912
- target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
913
- if (target.length) {
914
- this.result_highlight = target;
915
- this.result_select(evt);
916
- return this.search_field.focus();
917
- }
918
- };
919
-
920
- Chosen.prototype.search_results_mouseover = function(evt) {
921
- var target;
922
- target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
923
- if (target) {
924
- return this.result_do_highlight(target);
925
- }
926
- };
927
-
928
- Chosen.prototype.search_results_mouseout = function(evt) {
929
- if ($(evt.target).hasClass("active-result" || $(evt.target).parents('.active-result').first())) {
930
- return this.result_clear_highlight();
931
- }
932
- };
933
-
934
- Chosen.prototype.choice_build = function(item) {
935
- var choice, close_link,
936
- _this = this;
937
- choice = $('<li />', {
938
- "class": "search-choice"
939
- }).html("<span>" + item.html + "</span>");
940
- if (item.disabled) {
941
- choice.addClass('search-choice-disabled');
942
- } else {
943
- close_link = $('<a />', {
944
- "class": 'search-choice-close',
945
- 'data-option-array-index': item.array_index
946
- });
947
- close_link.bind('click.chosen', function(evt) {
948
- return _this.choice_destroy_link_click(evt);
949
- });
950
- choice.append(close_link);
951
- }
952
- return this.search_container.before(choice);
953
- };
954
-
955
- Chosen.prototype.choice_destroy_link_click = function(evt) {
956
- evt.preventDefault();
957
- evt.stopPropagation();
958
- if (!this.is_disabled) {
959
- return this.choice_destroy($(evt.target));
960
- }
961
- };
962
-
963
- Chosen.prototype.choice_destroy = function(link) {
964
- if (this.result_deselect(link[0].getAttribute("data-option-array-index"))) {
965
- this.show_search_field_default();
966
- if (this.is_multiple && this.choices_count() > 0 && this.search_field.val().length < 1) {
967
- this.results_hide();
968
- }
969
- link.parents('li').first().remove();
970
- return this.search_field_scale();
971
- }
972
- };
973
-
974
- Chosen.prototype.results_reset = function() {
975
- this.reset_single_select_options();
976
- this.form_field.options[0].selected = true;
977
- this.single_set_selected_text();
978
- this.show_search_field_default();
979
- this.results_reset_cleanup();
980
- this.form_field_jq.trigger("change");
981
- if (this.active_field) {
982
- return this.results_hide();
983
- }
984
- };
985
-
986
- Chosen.prototype.results_reset_cleanup = function() {
987
- this.current_selectedIndex = this.form_field.selectedIndex;
988
- return this.selected_item.find("abbr").remove();
989
- };
990
-
991
- Chosen.prototype.result_select = function(evt) {
992
- var high, item;
993
- if (this.result_highlight) {
994
- high = this.result_highlight;
995
- this.result_clear_highlight();
996
- if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
997
- this.form_field_jq.trigger("chosen:maxselected", {
998
- chosen: this
999
- });
1000
- return false;
1001
- }
1002
- if (this.is_multiple) {
1003
- high.removeClass("active-result");
1004
- } else {
1005
- this.reset_single_select_options();
1006
- }
1007
- item = this.results_data[high[0].getAttribute("data-option-array-index")];
1008
- item.selected = true;
1009
- this.form_field.options[item.options_index].selected = true;
1010
- this.selected_option_count = null;
1011
- if (this.is_multiple) {
1012
- this.choice_build(item);
1013
- } else {
1014
- this.single_set_selected_text(item.text);
1015
- }
1016
- if (!((evt.metaKey || evt.ctrlKey) && this.is_multiple)) {
1017
- this.results_hide();
1018
- }
1019
- this.search_field.val("");
1020
- if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex) {
1021
- this.form_field_jq.trigger("change", {
1022
- 'selected': this.form_field.options[item.options_index].value
1023
- });
1024
- }
1025
- this.current_selectedIndex = this.form_field.selectedIndex;
1026
- return this.search_field_scale();
1027
- }
1028
- };
1029
-
1030
- Chosen.prototype.single_set_selected_text = function(text) {
1031
- if (text == null) {
1032
- text = this.default_text;
1033
- }
1034
- if (text === this.default_text) {
1035
- this.selected_item.addClass("chosen-default");
1036
- } else {
1037
- this.single_deselect_control_build();
1038
- this.selected_item.removeClass("chosen-default");
1039
- }
1040
- return this.selected_item.find("span").text(text);
1041
- };
1042
-
1043
- Chosen.prototype.result_deselect = function(pos) {
1044
- var result_data;
1045
- result_data = this.results_data[pos];
1046
- if (!this.form_field.options[result_data.options_index].disabled) {
1047
- result_data.selected = false;
1048
- this.form_field.options[result_data.options_index].selected = false;
1049
- this.selected_option_count = null;
1050
- this.result_clear_highlight();
1051
- if (this.results_showing) {
1052
- this.winnow_results();
1053
- }
1054
- this.form_field_jq.trigger("change", {
1055
- deselected: this.form_field.options[result_data.options_index].value
1056
- });
1057
- this.search_field_scale();
1058
- return true;
1059
- } else {
1060
- return false;
1061
- }
1062
- };
1063
-
1064
- Chosen.prototype.single_deselect_control_build = function() {
1065
- if (!this.allow_single_deselect) {
1066
- return;
1067
- }
1068
- if (!this.selected_item.find("abbr").length) {
1069
- this.selected_item.find("span").first().after("<abbr class=\"search-choice-close\"></abbr>");
1070
- }
1071
- return this.selected_item.addClass("chosen-single-with-deselect");
1072
- };
1073
-
1074
- Chosen.prototype.get_search_text = function() {
1075
- if (this.search_field.val() === this.default_text) {
1076
- return "";
1077
- } else {
1078
- return $('<div/>').text($.trim(this.search_field.val())).html();
1079
- }
1080
- };
1081
-
1082
- Chosen.prototype.winnow_results_set_highlight = function() {
1083
- var do_high, selected_results;
1084
- selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : [];
1085
- do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first();
1086
- if (do_high != null) {
1087
- return this.result_do_highlight(do_high);
1088
- }
1089
- };
1090
-
1091
- Chosen.prototype.no_results = function(terms) {
1092
- var no_results_html;
1093
- no_results_html = $('<li class="no-results">' + this.results_none_found + ' "<span></span>"</li>');
1094
- no_results_html.find("span").first().html(terms);
1095
- this.search_results.append(no_results_html);
1096
- return this.form_field_jq.trigger("chosen:no_results", {
1097
- chosen: this
1098
- });
1099
- };
1100
-
1101
- Chosen.prototype.no_results_clear = function() {
1102
- return this.search_results.find(".no-results").remove();
1103
- };
1104
-
1105
- Chosen.prototype.keydown_arrow = function() {
1106
- var next_sib;
1107
- if (this.results_showing && this.result_highlight) {
1108
- next_sib = this.result_highlight.nextAll("li.active-result").first();
1109
- if (next_sib) {
1110
- return this.result_do_highlight(next_sib);
1111
- }
1112
- } else {
1113
- return this.results_show();
1114
- }
1115
- };
1116
-
1117
- Chosen.prototype.keyup_arrow = function() {
1118
- var prev_sibs;
1119
- if (!this.results_showing && !this.is_multiple) {
1120
- return this.results_show();
1121
- } else if (this.result_highlight) {
1122
- prev_sibs = this.result_highlight.prevAll("li.active-result");
1123
- if (prev_sibs.length) {
1124
- return this.result_do_highlight(prev_sibs.first());
1125
- } else {
1126
- if (this.choices_count() > 0) {
1127
- this.results_hide();
1128
- }
1129
- return this.result_clear_highlight();
1130
- }
1131
- }
1132
- };
1133
-
1134
- Chosen.prototype.keydown_backstroke = function() {
1135
- var next_available_destroy;
1136
- if (this.pending_backstroke) {
1137
- this.choice_destroy(this.pending_backstroke.find("a").first());
1138
- return this.clear_backstroke();
1139
- } else {
1140
- next_available_destroy = this.search_container.siblings("li.search-choice").last();
1141
- if (next_available_destroy.length && !next_available_destroy.hasClass("search-choice-disabled")) {
1142
- this.pending_backstroke = next_available_destroy;
1143
- if (this.single_backstroke_delete) {
1144
- return this.keydown_backstroke();
1145
- } else {
1146
- return this.pending_backstroke.addClass("search-choice-focus");
1147
- }
1148
- }
1149
- }
1150
- };
1151
-
1152
- Chosen.prototype.clear_backstroke = function() {
1153
- if (this.pending_backstroke) {
1154
- this.pending_backstroke.removeClass("search-choice-focus");
1155
- }
1156
- return this.pending_backstroke = null;
1157
- };
1158
-
1159
- Chosen.prototype.keydown_checker = function(evt) {
1160
- var stroke, _ref1;
1161
- stroke = (_ref1 = evt.which) != null ? _ref1 : evt.keyCode;
1162
- this.search_field_scale();
1163
- if (stroke !== 8 && this.pending_backstroke) {
1164
- this.clear_backstroke();
1165
- }
1166
- switch (stroke) {
1167
- case 8:
1168
- this.backstroke_length = this.search_field.val().length;
1169
- break;
1170
- case 9:
1171
- if (this.results_showing && !this.is_multiple) {
1172
- this.result_select(evt);
1173
- }
1174
- this.mouse_on_container = false;
1175
- break;
1176
- case 13:
1177
- if (this.results_showing) {
1178
- evt.preventDefault();
1179
- }
1180
- break;
1181
- case 32:
1182
- if (this.disable_search) {
1183
- evt.preventDefault();
1184
- }
1185
- break;
1186
- case 38:
1187
- evt.preventDefault();
1188
- this.keyup_arrow();
1189
- break;
1190
- case 40:
1191
- evt.preventDefault();
1192
- this.keydown_arrow();
1193
- break;
1194
- }
1195
- };
1196
-
1197
- Chosen.prototype.search_field_scale = function() {
1198
- var div, f_width, h, style, style_block, styles, w, _i, _len;
1199
- if (this.is_multiple) {
1200
- h = 0;
1201
- w = 0;
1202
- style_block = "position:absolute; left: -1000px; top: -1000px; display:none;";
1203
- styles = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing'];
1204
- for (_i = 0, _len = styles.length; _i < _len; _i++) {
1205
- style = styles[_i];
1206
- style_block += style + ":" + this.search_field.css(style) + ";";
1207
- }
1208
- div = $('<div />', {
1209
- 'style': style_block
1210
- });
1211
- div.text(this.search_field.val());
1212
- $('body').append(div);
1213
- w = div.width() + 25;
1214
- div.remove();
1215
- f_width = this.container.outerWidth();
1216
- if (w > f_width - 10) {
1217
- w = f_width - 10;
1218
- }
1219
- return this.search_field.css({
1220
- 'width': w + 'px'
1221
- });
1222
- }
1223
- };
1224
-
1225
- return Chosen;
1226
-
1227
- })(AbstractChosen);
1228
-
1229
- }).call(this);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/resources/js/fc-multistaff-view.js ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function($) {
2
+ var FC = $.fullCalendar; // a reference to FullCalendar's root namespace
3
+
4
+ var MultiStaffView = FC.views.agenda.class.extend({
5
+ initialize: function() {
6
+ FC.views.agenda.class.prototype.initialize.apply(this, arguments);
7
+
8
+ this.timeGrid.rangeUpdated = function() {
9
+ var view = this.view;
10
+ var colDates = [];
11
+ var date;
12
+
13
+ date = this.start.clone();
14
+ while (date.isBefore(this.end)) {
15
+ for (var i = 0; i < this.view.opt('staffMembers').length; ++ i) {
16
+ // For each staff create separate column.
17
+ colDates.push(date.clone());
18
+ }
19
+ date.add(1, 'day');
20
+ date = view.skipHiddenDays(date);
21
+ }
22
+
23
+ if (this.isRTL) {
24
+ colDates.reverse();
25
+ }
26
+
27
+ this.colDates = colDates;
28
+ this.colCnt = colDates.length;
29
+ this.rowCnt = Math.ceil((this.maxTime - this.minTime) / this.snapDuration);
30
+ };
31
+
32
+ this.timeGrid.groupSegCols = function(segs) {
33
+ var segCols = [];
34
+ var i;
35
+
36
+ for (i = 0; i < this.colCnt; i++) {
37
+ segCols.push([]);
38
+ }
39
+
40
+ var staffCols = {};
41
+ for (var i = 0; i < this.view.opt('staffMembers').length; ++ i) {
42
+ staffCols[this.view.opt('staffMembers')[i].id] = i;
43
+ }
44
+
45
+ for (i = 0; i < segs.length; i++) {
46
+ segCols[staffCols[segs[i].event.staffId]].push(segs[i]);
47
+ }
48
+
49
+ return segCols;
50
+ };
51
+
52
+ this.timeGrid.rangeToSegs = function(range) {
53
+ var colCnt = this.colCnt;
54
+ var segs = [];
55
+ var seg;
56
+ var col;
57
+ var colDate;
58
+ var colRange;
59
+
60
+ // Take staff id into account too.
61
+ range = {
62
+ start: range.start.clone().stripZone(),
63
+ end: range.end.clone().stripZone(),
64
+ staffId: range.event.staffId
65
+ };
66
+
67
+ for (col = 0; col < colCnt; col++) {
68
+ colDate = this.colDates[col];
69
+ colRange = {
70
+ start: colDate.clone().time(this.minTime),
71
+ end: colDate.clone().time(this.maxTime),
72
+ staffId: this.view.opt('staffMembers')[col].id
73
+ };
74
+ seg = intersectionToSeg(range, colRange);
75
+ if (seg) {
76
+ seg.col = col;
77
+ segs.push(seg);
78
+ }
79
+ }
80
+
81
+ return segs;
82
+ };
83
+
84
+ this.timeGrid.headHtml = function() {
85
+ var rowCellHtml = '';
86
+ var col;
87
+
88
+ for (col = 0; col < this.colCnt; col++) {
89
+ rowCellHtml += '<th class="fc-day-header fc-widget-header fc-mon">' + this.view.opt('staffMembers')[col].name + '</th>'
90
+ }
91
+
92
+ rowCellHtml = this.bookendCells(rowCellHtml, 'head', 0);
93
+
94
+ return '' +
95
+ '<div class="fc-row ' + this.view.widgetHeaderClass + '">' +
96
+ '<table>' +
97
+ '<thead>' +
98
+ '<tr>' + rowCellHtml + '</tr>' +
99
+ '</thead>' +
100
+ '</table>' +
101
+ '</div>';
102
+ };
103
+ }
104
+ });
105
+
106
+
107
+ FC.views.multiStaff = {
108
+ 'class': MultiStaffView, // register our class with the view system
109
+ defaults: {
110
+ staffMembers: [],
111
+ allDaySlot: true,
112
+ allDayText: 'all-day',
113
+ slotDuration: '00:30:00',
114
+ minTime: '00:00:00',
115
+ maxTime: '24:00:00',
116
+ slotEventOverlap: true // a bad name. confused with overlap/constraint system
117
+ }
118
+ };
119
+
120
+ FC.views.multiStaffDay = {
121
+ type: 'multiStaff',
122
+ duration: { days: 1 }
123
+ };
124
+
125
+ function intersectionToSeg(subjectRange, constraintRange) {
126
+ var subjectStart = subjectRange.start;
127
+ var subjectEnd = subjectRange.end;
128
+ var constraintStart = constraintRange.start;
129
+ var constraintEnd = constraintRange.end;
130
+ var segStart, segEnd;
131
+ var isStart, isEnd;
132
+
133
+ // Take staff id into account too.
134
+ if (subjectEnd > constraintStart && subjectStart < constraintEnd && subjectRange.staffId == constraintRange.staffId) {
135
+
136
+ if (subjectStart >= constraintStart) {
137
+ segStart = subjectStart.clone();
138
+ isStart = true;
139
+ } else {
140
+ segStart = constraintStart.clone();
141
+ isStart = false;
142
+ }
143
+
144
+ if (subjectEnd <= constraintEnd) {
145
+ segEnd = subjectEnd.clone();
146
+ isEnd = true;
147
+ } else {
148
+ segEnd = constraintEnd.clone();
149
+ isEnd = false;
150
+ }
151
+
152
+ return {
153
+ start: segStart,
154
+ end: segEnd,
155
+ isStart: isStart,
156
+ isEnd: isEnd
157
+ };
158
+ }
159
+ }
160
+ })(jQuery);
backend/modules/calendar/resources/js/fullcalendar.min.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
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});
backend/modules/calendar/resources/js/jquery.weekcalendar.js DELETED
@@ -1,2951 +0,0 @@
1
- /*
2
- * jQuery.weekCalendar v2.0-dev
3
- *
4
- * for support join us at the google group:
5
- * - http://groups.google.com/group/jquery-week-calendar
6
- * have a look to the wiki for documentation:
7
- * - http://wiki.github.com/themouette/jquery-week-calendar/
8
- * something went bad ? report an issue:
9
- * - http://github.com/themouette/jquery-week-calendar/issues
10
- * get the last version on github:
11
- * - http://github.com/themouette/jquery-week-calendar
12
- *
13
- * Copyright (c) 2009 Rob Monie
14
- * Copyright (c) 2010 Julien MUETTON
15
- * Dual licensed under the MIT and GPL licenses:
16
- * http://www.opensource.org/licenses/mit-license.php
17
- * http://www.gnu.org/licenses/gpl.html
18
- *
19
- * If you're after a monthly calendar plugin, check out this one :
20
- * http://arshaw.com/fullcalendar/
21
- */
22
-
23
- (function($) {
24
- // check the jquery version
25
- var _v = $.fn.jquery.split('.'),
26
- _jQuery14OrLower = (10 * _v[0] + _v[1]) < 15;
27
-
28
- $.widget('ui.weekCalendar', (function() {
29
- var _currentAjaxCall, _hourLineTimeout;
30
-
31
- return {
32
- options: {
33
- date: new Date(),
34
- timeFormat: null,
35
- dateFormat: 'M d, Y',
36
- alwaysDisplayTimeMinutes: true,
37
- use24Hour: false,
38
- daysToShow: 7,
39
- minBodyHeight: 100,
40
- firstDayOfWeek: function(calendar) {
41
- if ($(calendar).weekCalendar('option', 'daysToShow') != 5) {
42
- return 0;
43
- } else {
44
- //workweek
45
- return 1;
46
- }
47
- }, // 0 = Sunday, 1 = Monday, 2 = Tuesday, ... , 6 = Saturday
48
- useShortDayNames: false,
49
- timeSeparator: ' to ',
50
- startParam: 'start',
51
- endParam: 'end',
52
- businessHours: {start: 8, end: 18, limitDisplay: false},
53
- newEventText: 'New Event',
54
- timeslotHeight: 20,
55
- defaultEventLength: 2,
56
- timeslotsPerHour: 4,
57
- minDate: null,
58
- maxDate: null,
59
- showHeader: true,
60
- buttons: true,
61
- buttonText: {
62
- today: 'today',
63
- lastWeek: 'previous',
64
- nextWeek: 'next'
65
- },
66
- switchDisplay: {},
67
- scrollToHourMillis: 500,
68
- allowEventDelete: false,
69
- allowCalEventOverlap: false,
70
- overlapEventsSeparate: false,
71
- totalEventsWidthPercentInOneColumn: 100,
72
- readonly: false,
73
- allowEventCreation: true,
74
- hourLine: false,
75
- deletable: function(calEvent, element) {
76
- return true;
77
- },
78
- draggable: function(calEvent, element) {
79
- return true;
80
- },
81
- resizable: function(calEvent, element) {
82
- return true;
83
- },
84
- eventClick: function(calEvent, element, dayFreeBusyManager,
85
- calendar, clickEvent) {
86
- },
87
- eventRender: function(calEvent, element) {
88
- return element;
89
- },
90
- eventAfterRender: function(calEvent, element) {
91
- return element;
92
- },
93
- eventRefresh: function(calEvent, element) {
94
- return element;
95
- },
96
- eventDrag: function(calEvent, element) {
97
- },
98
- eventDrop: function(calEvent, element) {
99
- },
100
- eventResize: function(calEvent, element) {
101
- },
102
- eventNew: function(calEvent, element, dayFreeBusyManager,
103
- calendar, mouseupEvent) {
104
- },
105
- eventMouseover: function(calEvent, $event) {
106
- },
107
- eventMouseout: function(calEvent, $event) {
108
- },
109
- eventDelete: function(calEvent, element, dayFreeBusyManager,
110
- calendar, clickEvent) {
111
- calendar.weekCalendar('removeEvent',calEvent.id);
112
- },
113
- calendarBeforeLoad: function(calendar) {
114
- },
115
- calendarAfterLoad: function(calendar) {
116
- },
117
- noEvents: function() {
118
- },
119
- eventHeader: function(calEvent, calendar) {
120
- var options = calendar.weekCalendar('option');
121
- var one_hour = 3600000;
122
- var displayTitleWithTime = calEvent.end.getTime() - calEvent.start.getTime() <= (one_hour / options.timeslotsPerHour);
123
- if (displayTitleWithTime) {
124
- return calendar.weekCalendar(
125
- 'formatTime', calEvent.start) +
126
- ': ' + calEvent.title;
127
- } else {
128
- return calendar.weekCalendar(
129
- 'formatTime', calEvent.start) +
130
- options.timeSeparator +
131
- calendar.weekCalendar(
132
- 'formatTime', calEvent.end);
133
- }
134
- },
135
- eventBody: function(calEvent, calendar) {
136
- return calEvent.title;
137
- },
138
- shortMonths: BooklyL10n['shortMonths'],
139
- longMonths: BooklyL10n['longMonths'],
140
- shortDays: BooklyL10n['shortDays'],
141
- longDays: BooklyL10n['longDays'],
142
- /* multi-users options */
143
- /**
144
- * the available users for calendar.
145
- * if you want to display users separately, enable the
146
- * showAsSeparateUsers option.
147
- * if you provide a list of user and do not enable showAsSeparateUsers
148
- * option, then only the events that belongs to one or several of
149
- * given users will be displayed
150
- * @type {array}
151
- */
152
- users: [],
153
- /**
154
- * should the calendar be displayed with separate column for each
155
- * users.
156
- * note that this option does nothing if you do not provide at least
157
- * one user.
158
- * @type {boolean}
159
- */
160
- showAsSeparateUsers: true,
161
- /**
162
- * callback used to read user id from a user object.
163
- * @param {Object} user the user to retrieve the id from.
164
- * @param {number} index the user index from user list.
165
- * @param {jQuery} calendar the calendar object.
166
- * @return {int|String} the user id.
167
- */
168
- getUserId: function(user, index, calendar) {
169
- return index;
170
- },
171
- /**
172
- * callback used to read user name from a user object.
173
- * @param {Object} user the user to retrieve the name from.
174
- * @param {number} index the user index from user list.
175
- * @param {jQuery} calendar the calendar object.
176
- * @return {String} the user name.
177
- */
178
- getUserName: function(user, index, calendar) {
179
- return user;
180
- },
181
- /**
182
- * reads the id(s) of user(s) for who the event should be displayed.
183
- * @param {Object} calEvent the calEvent to read informations from.
184
- * @param {jQuery} calendar the calendar object.
185
- * @return {number|String|Array} the user id(s) to appened events for.
186
- */
187
- getEventUserId: function(calEvent, calendar) {
188
- return calEvent.userId;
189
- },
190
- /**
191
- * sets user id(s) to the calEvent
192
- * @param {Object} calEvent the calEvent to set informations to.
193
- * @param {jQuery} calendar the calendar object.
194
- * @return {Object} the calEvent with modified user id.
195
- */
196
- setEventUserId: function(userId, calEvent, calendar) {
197
- calEvent.userId = userId;
198
- return calEvent;
199
- },
200
- /* freeBusy options */
201
- /**
202
- * should the calendar display freebusys ?
203
- * @type {boolean}
204
- */
205
- displayFreeBusys: false,
206
- /**
207
- * read the id(s) for who the freebusy is available
208
- * @param {Object} calEvent the calEvent to read informations from.
209
- * @param {jQuery} calendar the calendar object.
210
- * @return {number|String|Array} the user id(s) to appened events for.
211
- */
212
- getFreeBusyUserId: function(calFreeBusy, calendar) {
213
- return calFreeBusy.userId;
214
- },
215
- /**
216
- * the default freeBusy object, used to manage default state
217
- * @type {Object}
218
- */
219
- defaultFreeBusy: {free: false},
220
- /**
221
- * function used to display the freeBusy element
222
- * @type {Function}
223
- * @param {Object} freeBusy the freeBusy timeslot to render.
224
- * @param {jQuery} $freeBusy the freeBusy HTML element.
225
- * @param {jQuery} calendar the calendar element.
226
- */
227
- freeBusyRender: function(freeBusy, $freeBusy, calendar) {
228
- if (!freeBusy.free) {
229
- $freeBusy.addClass('free-busy-busy');
230
- }
231
- else {
232
- $freeBusy.addClass('free-busy-free');
233
- }
234
- return $freeBusy;
235
- },
236
- /* other options */
237
- /**
238
- * true means start on first day of week, false means starts on
239
- * startDate.
240
- * @param {jQuery} calendar the calendar object.
241
- * @type {Function|bool}
242
- */
243
- startOnFirstDayOfWeek: function(calendar) {
244
- return $(calendar).weekCalendar('option', 'daysToShow') >= 5;
245
- },
246
- /**
247
- * should the columns be rendered alternatively using odd/even
248
- * class
249
- * @type {boolean}
250
- */
251
- displayOddEven: false,
252
- textSize: 13,
253
- /**
254
- * the title attribute for the calendar. possible placeholders are:
255
- * <ul>
256
- * <li>%start%</li>
257
- * <li>%end%</li>
258
- * <li>%date%</li>
259
- * </ul>
260
- * @type {Function|string}
261
- * @param {number} option daysToShow.
262
- * @return {String} the title attribute for the calendar.
263
- */
264
- title: '%start% - %end%',
265
- /**
266
- * default options to pass to callback
267
- * you can pass a function returning an object or a litteral object
268
- * @type {object|function(#calendar)}
269
- */
270
- jsonOptions: {},
271
- headerSeparator: '<br />',
272
- /**
273
- * returns formatted header for day display
274
- * @type {function(date,calendar)}
275
- */
276
- getHeaderDate: null,
277
- preventDragOnEventCreation: false,
278
- /**
279
- * the event on which to bind calendar resize
280
- * @type {string}
281
- */
282
- resizeEvent: 'resize.weekcalendar'
283
- },
284
-
285
- /***********************
286
- * Initialise calendar *
287
- ***********************/
288
- _create: function() {
289
- var self = this;
290
- self._computeOptions();
291
- self._setupEventDelegation();
292
- self._renderCalendar();
293
- self._loadCalEvents();
294
- self._resizeCalendar();
295
-
296
- if (this.options.resizeEvent) {
297
- $(window).unbind(this.options.resizeEvent);
298
- $(window).bind(this.options.resizeEvent, function() {
299
- self._resizeCalendar();
300
- });
301
- }
302
-
303
- },
304
-
305
- /********************
306
- * public functions *
307
- ********************/
308
- /*
309
- * Refresh the events for the currently displayed week.
310
- */
311
- refresh: function() {
312
- //reload with existing week
313
- this._loadCalEvents(this.element.data('startDate'));
314
- },
315
-
316
- /*
317
- * Clear all events currently loaded into the calendar
318
- */
319
- clear: function() {
320
- this._clearCalendar();
321
- },
322
-
323
- /*
324
- * Go to this week
325
- */
326
- today: function() {
327
- this._clearCalendar();
328
- this._loadCalEvents(new Date());
329
- },
330
-
331
- /*
332
- * Go to the previous week relative to the currently displayed week
333
- */
334
- prevWeek: function() {
335
- //minus more than 1 day to be sure we're in previous week - account for daylight savings or other anomolies
336
- var newDate = new Date(this.element.data('startDate').getTime() - (MILLIS_IN_WEEK / 6));
337
- this._clearCalendar();
338
- this._loadCalEvents(newDate);
339
- },
340
-
341
- /*
342
- * Go to the next week relative to the currently displayed week
343
- */
344
- nextWeek: function() {
345
- //add 8 days to be sure of being in prev week - allows for daylight savings or other anomolies
346
- var newDate = new Date(this.element.data('startDate').getTime() + MILLIS_IN_WEEK + MILLIS_IN_DAY);
347
- this._clearCalendar();
348
- this._loadCalEvents(newDate);
349
- },
350
-
351
- /*
352
- * Reload the calendar to whatever week the date passed in falls on.
353
- */
354
- gotoWeek: function(date) {
355
- this._clearCalendar();
356
- this._loadCalEvents(date);
357
- },
358
-
359
- /*
360
- * Reload the calendar to whatever week the date passed in falls on.
361
- */
362
- gotoDate: function(date) {
363
- this._clearCalendar();
364
- this._loadCalEvents(date);
365
- },
366
-
367
- /**
368
- * change the number of days to show
369
- */
370
- setDaysToShow: function(daysToShow) {
371
- var self = this;
372
- var hour = self._getCurrentScrollHour();
373
- self.options.daysToShow = daysToShow;
374
- $(self.element).html('');
375
- self._renderCalendar();
376
- self._loadCalEvents();
377
- self._resizeCalendar();
378
- self._scrollToHour(hour, false);
379
-
380
- if (this.options.resizeEvent) {
381
- $(window).unbind(this.options.resizeEvent);
382
- $(window).bind(this.options.resizeEvent, function() {
383
- self._resizeCalendar();
384
- });
385
- }
386
- },
387
-
388
- /*
389
- * Remove an event based on it's id
390
- */
391
- removeEvent: function(eventId) {
392
-
393
- var self = this;
394
-
395
- self.element.find('.wc-cal-event').each(function() {
396
- if ($(this).data('calEvent').id === eventId) {
397
- $(this).remove();
398
- return false;
399
- }
400
- });
401
-
402
- //this could be more efficient rather than running on all days regardless...
403
- self.element.find('.wc-day-column-inner').each(function() {
404
- self._adjustOverlappingEvents($(this));
405
- });
406
- },
407
-
408
- /*
409
- * Removes any events that have been added but not yet saved (have no id).
410
- * This is useful to call after adding a freshly saved new event.
411
- */
412
- removeUnsavedEvents: function() {
413
-
414
- var self = this;
415
-
416
- self.element.find('.wc-new-cal-event').each(function() {
417
- $(this).remove();
418
- });
419
-
420
- //this could be more efficient rather than running on all days regardless...
421
- self.element.find('.wc-day-column-inner').each(function() {
422
- self._adjustOverlappingEvents($(this));
423
- });
424
- },
425
-
426
- /*
427
- * update an event in the calendar. If the event exists it refreshes
428
- * it's rendering. If it's a new event that does not exist in the calendar
429
- * it will be added.
430
- */
431
- updateEvent: function(calEvent) {
432
- this._updateEventInCalendar(calEvent);
433
- },
434
-
435
- /*
436
- * Returns an array of timeslot start and end times based on
437
- * the configured grid of the calendar. Returns in both date and
438
- * formatted time based on the 'timeFormat' config option.
439
- */
440
- getTimeslotTimes: function(date) {
441
- var options = this.options;
442
- var firstHourDisplayed = options.businessHours.limitDisplay ? options.businessHours.start : 0;
443
- var startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), firstHourDisplayed);
444
-
445
- var times = [],
446
- startMillis = startDate.getTime();
447
- for (var i = 0; i < options.timeslotsPerDay; i++) {
448
- var endMillis = startMillis + options.millisPerTimeslot;
449
- times[i] = {
450
- start: new Date(startMillis),
451
- startFormatted: this.formatTime(new Date(startMillis), options.timeFormat),
452
- end: new Date(endMillis),
453
- endFormatted: this.formatTime(new Date(endMillis), options.timeFormat)
454
- };
455
- startMillis = endMillis;
456
- }
457
- return times;
458
- },
459
-
460
- formatDate: function(date, format) {
461
- if (format) {
462
- return this._formatDate(date, format);
463
- } else {
464
- return this._formatDate(date, this.options.dateFormat);
465
- }
466
- },
467
-
468
- formatTime: function(date, format) {
469
- if (format) {
470
- return this._formatDate(date, format);
471
- } else if (this.options.timeFormat) {
472
- return this._formatDate(date, this.options.timeFormat);
473
- } else if (this.options.use24Hour) {
474
- return this._formatDate(date, 'H:i');
475
- } else {
476
- return this._formatDate(date, 'h:i a');
477
- }
478
- },
479
-
480
- serializeEvents: function() {
481
- var self = this;
482
- var calEvents = [];
483
-
484
- self.element.find('.wc-cal-event').each(function() {
485
- calEvents.push($(this).data('calEvent'));
486
- });
487
- return calEvents;
488
- },
489
-
490
- next: function() {
491
- if (this._startOnFirstDayOfWeek()) {
492
- return this.nextWeek();
493
- }
494
- var newDate = new Date(this.element.data('startDate').getTime());
495
- newDate.setDate(newDate.getDate() + this.options.daysToShow);
496
-
497
- this._clearCalendar();
498
- this._loadCalEvents(newDate);
499
- },
500
-
501
- prev: function() {
502
- if (this._startOnFirstDayOfWeek()) {
503
- return this.prevWeek();
504
- }
505
- var newDate = new Date(this.element.data('startDate').getTime());
506
- newDate.setDate(newDate.getDate() - this.options.daysToShow);
507
-
508
- this._clearCalendar();
509
- this._loadCalEvents(newDate);
510
- },
511
- getCurrentFirstDay: function() {
512
- return this._dateFirstDayOfWeek(this.options.date || new Date());
513
- },
514
- getCurrentLastDay: function() {
515
- return this._addDays(this.getCurrentFirstDay(), this.options.daysToShow - 1);
516
- },
517
-
518
- /*********************
519
- * private functions *
520
- *********************/
521
- _setOption: function(key, value) {
522
- var self = this;
523
- if (self.options[key] != value) {
524
- // event callback change, no need to re-render the events
525
- if (key == 'beforeEventNew') {
526
- self.options[key] = value;
527
- return;
528
- }
529
-
530
- // this could be made more efficient at some stage by caching the
531
- // events array locally in a store but this should be done in conjunction
532
- // with a proper binding model.
533
-
534
- var currentEvents = self.element.find('.wc-cal-event').map(function() {
535
- return $(this).data('calEvent');
536
- });
537
-
538
- var newOptions = {};
539
- newOptions[key] = value;
540
- self._renderEvents({events: currentEvents, options: newOptions}, self.element.find('.wc-day-column-inner'));
541
- }
542
- },
543
-
544
- // compute dynamic options based on other config values
545
- _computeOptions: function() {
546
- var options = this.options;
547
- if (options.businessHours.limitDisplay) {
548
- options.timeslotsPerDay = options.timeslotsPerHour * (options.businessHours.end - options.businessHours.start);
549
- options.millisToDisplay = (options.businessHours.end - options.businessHours.start) * 3600000; // 60 * 60 * 1000
550
- options.millisPerTimeslot = options.millisToDisplay / options.timeslotsPerDay;
551
- } else {
552
- options.timeslotsPerDay = options.timeslotsPerHour * 24;
553
- options.millisToDisplay = MILLIS_IN_DAY;
554
- options.millisPerTimeslot = MILLIS_IN_DAY / options.timeslotsPerDay;
555
- }
556
- },
557
-
558
- /*
559
- * Resize the calendar scrollable height based on the provided function in options.
560
- */
561
- _resizeCalendar: function() {
562
- var options = this.options;
563
- if (options && $.isFunction(options.height)) {
564
- var calendarHeight = options.height(this.element);
565
- var headerHeight = this.element.find('.wc-header').outerHeight();
566
- var navHeight = this.element.find('.wc-toolbar').outerHeight();
567
- var scrollContainerHeight = Math.max(calendarHeight - navHeight - headerHeight, options.minBodyHeight);
568
- var timeslotHeight = this.element.find('.wc-time-slots').outerHeight();
569
- this.element.find('.wc-scrollable-grid').height(scrollContainerHeight);
570
- if (timeslotHeight <= scrollContainerHeight) {
571
- this.element.find('.wc-scrollbar-shim').width(0);
572
- }
573
- this._trigger('resize', this.element);
574
- }
575
- },
576
-
577
- _findScrollBarWidth: function() {
578
- var parent = $('<div style="width:50px;height:50px;overflow:auto"><div/></div>').appendTo('body');
579
- var child = parent.children();
580
- var width = child.innerWidth() - child.height(99).innerWidth();
581
- parent.remove();
582
- return width || /* default to 16 that is the average */ 16;
583
- },
584
-
585
- /*
586
- * configure calendar interaction events that are able to use event
587
- * delegation for greater efficiency
588
- */
589
- _setupEventDelegation: function() {
590
- var self = this;
591
- var options = this.options;
592
-
593
- this.element.click(function(event) {
594
- var $target = $(event.target),
595
- freeBusyManager;
596
-
597
- // click is disabled
598
- if ($target.data('preventClick')) {
599
- return;
600
- }
601
-
602
- var $calEvent = $target.hasClass('wc-cal-event') ?
603
- $target :
604
- $target.parents('.wc-cal-event');
605
- if (!$calEvent.length || !$calEvent.data('calEvent')) {
606
- return;
607
- }
608
-
609
- freeBusyManager = self.getFreeBusyManagerForEvent($calEvent.data('calEvent'));
610
-
611
- if (options.allowEventDelete && $target.hasClass('wc-cal-event-delete')) {
612
- options.eventDelete($calEvent.data('calEvent'), $calEvent, freeBusyManager, self.element, event);
613
- } else {
614
- options.eventClick($calEvent.data('calEvent'), $calEvent, freeBusyManager, self.element, event);
615
- }
616
- }).mouseover(function(event) {
617
- var $target = $(event.target);
618
- var $calEvent = $target.hasClass('wc-cal-event') ?
619
- $target :
620
- $target.parents('.wc-cal-event');
621
-
622
- if (!$calEvent.length || !$calEvent.data('calEvent')) {
623
- return;
624
- }
625
-
626
- if (self._isDraggingOrResizing($calEvent)) {
627
- return;
628
- }
629
-
630
- options.eventMouseover($calEvent.data('calEvent'), $calEvent, event);
631
- }).mouseout(function(event) {
632
- var $target = $(event.target);
633
- var $calEvent = $target.hasClass('wc-cal-event') ?
634
- $target :
635
- $target.parents('.wc-cal-event');
636
-
637
- if (!$calEvent.length || !$calEvent.data('calEvent')) {
638
- return;
639
- }
640
-
641
- if (self._isDraggingOrResizing($calEvent)) {
642
- return;
643
- }
644
-
645
- options.eventMouseout($calEvent.data('calEvent'), $calEvent, event);
646
- });
647
- },
648
-
649
- /**
650
- * check if a ui draggable or resizable is currently being dragged or
651
- * resized.
652
- */
653
- _isDraggingOrResizing: function($target) {
654
- return $target.hasClass('ui-draggable-dragging') ||
655
- $target.hasClass('ui-resizable-resizing');
656
- },
657
-
658
- /*
659
- * Render the main calendar layout
660
- */
661
- _renderCalendar: function() {
662
- var $calendarContainer, $weekDayColumns;
663
- var self = this;
664
- var options = this.options;
665
-
666
- $calendarContainer = $('<div class=\"ui-widget wc-container\">').appendTo(self.element);
667
-
668
- //render the different parts
669
- // nav links
670
- self._renderCalendarButtons($calendarContainer);
671
- // header
672
- self._renderCalendarHeader($calendarContainer);
673
- // body
674
- self._renderCalendarBody($calendarContainer);
675
-
676
- $weekDayColumns = $calendarContainer.find('.wc-day-column-inner');
677
- $weekDayColumns.each(function(i, val) {
678
- if (!options.readonly) {
679
- self._addDroppableToWeekDay($(this));
680
- if (options.allowEventCreation) {
681
- self._setupEventCreationForWeekDay($(this));
682
- }
683
- }
684
- });
685
- },
686
-
687
- /**
688
- * render the nav buttons on top of the calendar
689
- */
690
- _renderCalendarButtons: function($calendarContainer) {
691
- var self = this, options = this.options;
692
- if ( !options.showHeader ) return;
693
- if (options.buttons) {
694
- var calendarNavHtml = '';
695
-
696
- calendarNavHtml += '<div class=\"ui-widget-header wc-toolbar\">';
697
- calendarNavHtml += '<div class=\"wc-display\"></div>';
698
- calendarNavHtml += '<div class=\"wc-nav\">';
699
- calendarNavHtml += '<button class=\"wc-prev\">' + options.buttonText.lastWeek + '</button>';
700
- calendarNavHtml += '<button class=\"wc-today\">' + options.buttonText.today + '</button>';
701
- calendarNavHtml += '<button class=\"wc-next\">' + options.buttonText.nextWeek + '</button>';
702
- calendarNavHtml += '</div>';
703
- calendarNavHtml += '<h1 class=\"wc-title\"></h1>';
704
- calendarNavHtml += '</div>';
705
-
706
- $(calendarNavHtml).appendTo($calendarContainer);
707
-
708
- $calendarContainer.find('.wc-nav .wc-today')
709
- .button({
710
- icons: {primary: 'ui-icon-home'}})
711
- .click(function() {
712
- self.today();
713
- return false;
714
- });
715
-
716
- $calendarContainer.find('.wc-nav .wc-prev')
717
- .button({
718
- text: false,
719
- icons: {primary: 'ui-icon-seek-prev'}})
720
- .click(function() {
721
- self.element.weekCalendar('prev');
722
- return false;
723
- });
724
-
725
- $calendarContainer.find('.wc-nav .wc-next')
726
- .button({
727
- text: false,
728
- icons: {primary: 'ui-icon-seek-next'}})
729
- .click(function() {
730
- self.element.weekCalendar('next');
731
- return false;
732
- });
733
-
734
- // now add buttons to switch display
735
- if (this.options.switchDisplay && $.isPlainObject(this.options.switchDisplay)) {
736
- var $container = $calendarContainer.find('.wc-display');
737
- $.each(this.options.switchDisplay, function(label, option) {
738
- var _id = 'wc-switch-display-' + option;
739
- var _input = $('<input type="radio" id="' + _id + '" name="wc-switch-display" class="wc-switch-display"/>');
740
- var _label = $('<label for="' + _id + '"></label>');
741
- _label.html(label);
742
- _input.val(option);
743
- if (parseInt(self.options.daysToShow, 10) === parseInt(option, 10)) {
744
- _input.attr('checked', 'checked');
745
- }
746
- $container
747
- .append(_input)
748
- .append(_label);
749
- });
750
- $container.find('input').change(function() {
751
- self.setDaysToShow(parseInt($(this).val(), 10));
752
- });
753
- }
754
- $calendarContainer.find('.wc-nav, .wc-display').buttonset();
755
- var _height = $calendarContainer.find('.wc-nav').outerHeight();
756
- $calendarContainer.find('.wc-title')
757
- .height(_height)
758
- .css('line-height', _height + 'px');
759
- }else{
760
- var calendarNavHtml = '';
761
- calendarNavHtml += '<div class=\"ui-widget-header wc-toolbar\">';
762
- calendarNavHtml += '<h1 class=\"wc-title\"></h1>';
763
- calendarNavHtml += '</div>';
764
- $(calendarNavHtml).appendTo($calendarContainer);
765
-
766
- }
767
- },
768
-
769
- /**
770
- * render the calendar header, including date and user header
771
- */
772
- _renderCalendarHeader: function($calendarContainer) {
773
- var self = this, options = this.options,
774
- showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
775
- rowspan = '', colspan = '', calendarHeaderHtml;
776
-
777
- if (showAsSeparatedUser) {
778
- rowspan = ' rowspan=\"2\"';
779
- colspan = ' colspan=\"' + options.users.length + '\" ';
780
- }
781
-
782
- //first row
783
- calendarHeaderHtml = '<div class=\"ui-widget-content wc-header\">';
784
- calendarHeaderHtml += '<table><tbody><tr><td class=\"wc-time-column-header\" style=\"width:44px\"></td>';
785
- for (var i = 1; i <= options.daysToShow; i++) {
786
- calendarHeaderHtml += '<td class=\"wc-day-column-header wc-day-' + i + '\"' + colspan + '></td>';
787
- }
788
- calendarHeaderHtml += '<td class=\"wc-scrollbar-shim\"' + rowspan + '></td></tr>';
789
-
790
- //users row
791
- if (showAsSeparatedUser) {
792
- calendarHeaderHtml += '<tr><td class=\"wc-time-column-header\"></td>';
793
- var uLength = options.users.length,
794
- _headerClass = '';
795
-
796
- for (var i = 1; i <= options.daysToShow; i++) {
797
- for (var j = 0; j < uLength; j++) {
798
- _headerClass = [];
799
- if (j == 0) {
800
- _headerClass.push('wc-day-column-first');
801
- }
802
- if (j == uLength - 1) {
803
- _headerClass.push('wc-day-column-last');
804
- }
805
- if (!_headerClass.length) {
806
- _headerClass = 'wc-day-column-middle';
807
- }
808
- else {
809
- _headerClass = _headerClass.join(' ');
810
- }
811
- calendarHeaderHtml += '<td class=\"' + _headerClass + ' wc-user-header wc-day-' + i + ' wc-user-' + self._getUserIdFromIndex(j) + '\">';
812
- // calendarHeaderHtml+= "<div class=\"wc-user-header wc-day-" + i + " wc-user-" + self._getUserIdFromIndex(j) +"\" >";
813
- calendarHeaderHtml += self._getUserName(j);
814
- // calendarHeaderHtml+= "</div>";
815
- calendarHeaderHtml += '</td>';
816
- }
817
- }
818
- calendarHeaderHtml += '</tr>';
819
- }
820
- //close the header
821
- calendarHeaderHtml += '</tbody></table></div>';
822
-
823
- $(calendarHeaderHtml).appendTo($calendarContainer);
824
- },
825
-
826
- /**
827
- * render the calendar body.
828
- * Calendar body is composed of several distinct parts.
829
- * Each part is displayed in a separated row to ease rendering.
830
- * for further explanations, see each part rendering function.
831
- */
832
- _renderCalendarBody: function($calendarContainer) {
833
- var self = this, options = this.options,
834
- showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
835
- $calendarBody, $calendarTableTbody;
836
- // create the structure
837
- $calendarBody = '<div class=\"wc-scrollable-grid\">';
838
- $calendarBody += '<table class=\"wc-time-slots\">';
839
- $calendarBody += '<tbody>';
840
- $calendarBody += '</tbody>';
841
- $calendarBody += '</table>';
842
- $calendarBody += '</div>';
843
- $calendarBody = $($calendarBody);
844
- $calendarTableTbody = $calendarBody.find('tbody');
845
-
846
- self._renderCalendarBodyTimeSlots($calendarTableTbody);
847
- self._renderCalendarBodyOddEven($calendarTableTbody);
848
- self._renderCalendarBodyFreeBusy($calendarTableTbody);
849
- self._renderCalendarBodyEvents($calendarTableTbody);
850
-
851
- $calendarBody.appendTo($calendarContainer);
852
-
853
- //set the column height
854
- $calendarContainer.find('.wc-full-height-column').height(options.timeslotHeight * options.timeslotsPerDay);
855
- //set the timeslot height
856
- $calendarContainer.find('.wc-time-slot').height(options.timeslotHeight - 1); //account for border
857
- //init the time row header height
858
- /**
859
- TODO if total height for an hour is less than 11px, there is a display problem.
860
- Find a way to handle it
861
- */
862
- $calendarContainer.find('.wc-time-header-cell').css({
863
- height: (options.timeslotHeight * options.timeslotsPerHour) - 11,
864
- padding: 5
865
- });
866
- //add the user data to every impacted column
867
- if (showAsSeparatedUser) {
868
- for (var i = 0, uLength = options.users.length; i < uLength; i++) {
869
- $calendarContainer.find('.wc-user-' + self._getUserIdFromIndex(i))
870
- .data('wcUser', options.users[i])
871
- .data('wcUserIndex', i)
872
- .data('wcUserId', self._getUserIdFromIndex(i));
873
- }
874
- }
875
- },
876
-
877
- /**
878
- * render the timeslots separation
879
- */
880
- _renderCalendarBodyTimeSlots: function($calendarTableTbody) {
881
- var options = this.options,
882
- renderRow, i, j,
883
- showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
884
- start = (options.businessHours.limitDisplay ? options.businessHours.start : 0),
885
- end = (options.businessHours.limitDisplay ? options.businessHours.end : 24),
886
- rowspan = 1;
887
-
888
- //calculate the rowspan
889
- if (options.displayOddEven) { rowspan += 1; }
890
- if (options.displayFreeBusys) { rowspan += 1; }
891
- if (rowspan > 1) {
892
- rowspan = ' rowspan=\"' + rowspan + '\"';
893
- }
894
- else {
895
- rowspan = '';
896
- }
897
-
898
- renderRow = '<tr class=\"wc-grid-row-timeslot\">';
899
- renderRow += '<td class=\"wc-grid-timeslot-header\"' + rowspan + '></td>';
900
- renderRow += '<td colspan=\"' + options.daysToShow * (showAsSeparatedUser ? options.users.length : 1) + '\">';
901
- renderRow += '<div class=\"wc-no-height-wrapper wc-time-slot-wrapper\">';
902
- renderRow += '<div class=\"wc-time-slots\">';
903
-
904
- for (i = start; i < end; i++) {
905
- for (j = 0; j < options.timeslotsPerHour - 1; j++) {
906
- renderRow += '<div class=\"wc-time-slot\"></div>';
907
- }
908
- renderRow += '<div class=\"wc-time-slot wc-hour-end\"></div>';
909
- }
910
-
911
- renderRow += '</div>';
912
- renderRow += '</div>';
913
- renderRow += '</td>';
914
- renderRow += '</tr>';
915
-
916
- $(renderRow).appendTo($calendarTableTbody);
917
- },
918
-
919
- /**
920
- * render the odd even columns
921
- */
922
- _renderCalendarBodyOddEven: function($calendarTableTbody) {
923
- if (this.options.displayOddEven) {
924
- var options = this.options,
925
- renderRow = '<tr class=\"wc-grid-row-oddeven\">',
926
- showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
927
- oddEven,
928
- // let's take advantage of the jquery ui framework
929
- oddEvenClasses = {'odd': 'wc-column-odd', 'even': 'ui-state-hover wc-column-even'};
930
-
931
- //now let's display oddEven placeholders
932
- for (var i = 1; i <= options.daysToShow; i++) {
933
- if (!showAsSeparatedUser) {
934
- oddEven = (oddEven == 'odd' ? 'even' : 'odd');
935
- renderRow += '<td class=\"wc-day-column day-' + i + '\">';
936
- renderRow += '<div class=\"wc-no-height-wrapper wc-oddeven-wrapper\">';
937
- renderRow += '<div class=\"wc-full-height-column ' + oddEvenClasses[oddEven] + '\"></div>';
938
- renderRow += '</div>';
939
- renderRow += '</td>';
940
- }
941
- else {
942
- var uLength = options.users.length;
943
- for (var j = 0; j < uLength; j++) {
944
- oddEven = (oddEven == 'odd' ? 'even' : 'odd');
945
- renderRow += '<td class=\"wc-day-column day-' + i + '\">';
946
- renderRow += '<div class=\"wc-no-height-wrapper wc-oddeven-wrapper\">';
947
- renderRow += '<div class=\"wc-full-height-column ' + oddEvenClasses[oddEven] + '\" ></div>';
948
- renderRow += '</div>';
949
- renderRow += '</td>';
950
- }
951
- }
952
- }
953
- renderRow += '</tr>';
954
-
955
- $(renderRow).appendTo($calendarTableTbody);
956
- }
957
- },
958
-
959
- /**
960
- * render the freebusy placeholders
961
- */
962
- _renderCalendarBodyFreeBusy: function($calendarTableTbody) {
963
- if (this.options.displayFreeBusys) {
964
- var self = this, options = this.options,
965
- renderRow = '<tr class=\"wc-grid-row-freebusy\">',
966
- showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
967
- renderRow += '</td>';
968
-
969
- //now let's display freebusy placeholders
970
- for (var i = 1; i <= options.daysToShow; i++) {
971
- if (options.displayFreeBusys) {
972
- if (!showAsSeparatedUser) {
973
- renderRow += '<td class=\"wc-day-column day-' + i + '\">';
974
- renderRow += '<div class=\"wc-no-height-wrapper wc-freebusy-wrapper\">';
975
- renderRow += '<div class=\"wc-full-height-column wc-column-freebusy wc-day-' + i + '\"></div>';
976
- renderRow += '</div>';
977
- renderRow += '</td>';
978
- }
979
- else {
980
- var uLength = options.users.length;
981
- for (var j = 0; j < uLength; j++) {
982
- renderRow += '<td class=\"wc-day-column day-' + i + '\">';
983
- renderRow += '<div class=\"wc-no-height-wrapper wc-freebusy-wrapper\">';
984
- renderRow += '<div class=\"wc-full-height-column wc-column-freebusy wc-day-' + i;
985
- renderRow += ' wc-user-' + self._getUserIdFromIndex(j) + '\">';
986
- renderRow += '</div>';
987
- renderRow += '</div>';
988
- renderRow += '</td>';
989
- }
990
- }
991
- }
992
- }
993
-
994
- renderRow += '</tr>';
995
-
996
- $(renderRow).appendTo($calendarTableTbody);
997
- }
998
- },
999
-
1000
- /**
1001
- * render the calendar body for event placeholders
1002
- */
1003
- _renderCalendarBodyEvents: function($calendarTableTbody) {
1004
- var self = this, options = this.options,
1005
- renderRow,
1006
- showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
1007
- start = (options.businessHours.limitDisplay ? options.businessHours.start : 0),
1008
- end = (options.businessHours.limitDisplay ? options.businessHours.end : 24);
1009
- renderRow = '<tr class=\"wc-grid-row-events\">';
1010
- renderRow += '<td class=\"wc-grid-timeslot-header\">';
1011
- for (var i = start; i < end; i++) {
1012
- var bhClass = (options.businessHours.start <= i && options.businessHours.end > i) ? 'ui-state-active wc-business-hours' : 'ui-state-default';
1013
- renderRow += '<div class=\"wc-hour-header ' + bhClass + '\">';
1014
- if (options.use24Hour) {
1015
- renderRow += '<div class=\"wc-time-header-cell\">' + self._24HourForIndex(i) + '</div>';
1016
- }
1017
- else {
1018
- renderRow += '<div class=\"wc-time-header-cell\">' + self._hourForIndex(i) + '<span class=\"wc-am-pm\">' + self._amOrPm(i) + '</span></div>';
1019
- }
1020
- renderRow += '</div>';
1021
- }
1022
- renderRow += '</td>';
1023
-
1024
- //now let's display events placeholders
1025
- var _columnBaseClass = 'ui-state-default wc-day-column';
1026
- for (var i = 1; i <= options.daysToShow; i++) {
1027
- if (!showAsSeparatedUser) {
1028
- renderRow += '<td class=\"' + _columnBaseClass + ' wc-day-column-first wc-day-column-last day-' + i + '\">';
1029
- renderRow += '<div class=\"wc-full-height-column wc-day-column-inner day-' + i + '\"></div>';
1030
- renderRow += '</td>';
1031
- }
1032
- else {
1033
- var uLength = options.users.length;
1034
- var columnclass;
1035
- for (var j = 0; j < uLength; j++) {
1036
- columnclass = [];
1037
- if (j == 0) {
1038
- columnclass.push('wc-day-column-first');
1039
- }
1040
- if (j == uLength - 1) {
1041
- columnclass.push('wc-day-column-last');
1042
- }
1043
- if (!columnclass.length) {
1044
- columnclass = 'wc-day-column-middle';
1045
- }
1046
- else {
1047
- columnclass = columnclass.join(' ');
1048
- }
1049
- renderRow += '<td class=\"' + _columnBaseClass + ' ' + columnclass + ' day-' + i + '\">';
1050
- renderRow += '<div class=\"wc-full-height-column wc-day-column-inner day-' + i;
1051
- renderRow += ' wc-user-' + self._getUserIdFromIndex(j) + '\">';
1052
- renderRow += '</div>';
1053
- renderRow += '</td>';
1054
- }
1055
- }
1056
- }
1057
-
1058
- renderRow += '</tr>';
1059
-
1060
- $(renderRow).appendTo($calendarTableTbody);
1061
- },
1062
-
1063
- /*
1064
- * setup mouse events for capturing new events
1065
- */
1066
- _setupEventCreationForWeekDay: function($weekDay) {
1067
- var self = this;
1068
- var options = this.options;
1069
- $weekDay.mousedown(function(event) {
1070
- var $target = $(event.target);
1071
- if ($target.hasClass('wc-day-column-inner')) {
1072
-
1073
- var $newEvent = $('<div class=\"wc-cal-event wc-new-cal-event wc-new-cal-event-creating\"></div>');
1074
-
1075
- $newEvent.css({lineHeight: (options.timeslotHeight - 2) + 'px', fontSize: (options.timeslotHeight / 2) + 'px'});
1076
- $target.append($newEvent);
1077
-
1078
- var columnOffset = $target.offset().top;
1079
- var clickY = event.pageY - columnOffset;
1080
- var clickYRounded = (clickY - (clickY % options.timeslotHeight)) / options.timeslotHeight;
1081
- var topPosition = clickYRounded * options.timeslotHeight;
1082
- $newEvent.css({top: topPosition});
1083
-
1084
- if (!options.preventDragOnEventCreation) {
1085
- $target.bind('mousemove.newevent', function(event) {
1086
- $newEvent.show();
1087
- $newEvent.addClass('ui-resizable-resizing');
1088
- var height = Math.round(event.pageY - columnOffset - topPosition);
1089
- var remainder = height % options.timeslotHeight;
1090
- //snap to closest timeslot
1091
- if (remainder < 0) {
1092
- var useHeight = height - remainder;
1093
- $newEvent.css('height', useHeight < options.timeslotHeight ? options.timeslotHeight : useHeight);
1094
- } else {
1095
- $newEvent.css('height', height + (options.timeslotHeight - remainder));
1096
- }
1097
- }).mouseup(function() {
1098
- $target.unbind('mousemove.newevent');
1099
- $newEvent.addClass('ui-corner-all');
1100
- });
1101
- }
1102
- }
1103
-
1104
- }).mouseup(function(event) {
1105
- var $target = $(event.target);
1106
-
1107
- var $weekDay = $target.closest('.wc-day-column-inner');
1108
- var $newEvent = $weekDay.find('.wc-new-cal-event-creating');
1109
-
1110
- if ($newEvent.length) {
1111
- var createdFromSingleClick = !$newEvent.hasClass('ui-resizable-resizing');
1112
-
1113
- //if even created from a single click only, default height
1114
- if (createdFromSingleClick) {
1115
- $newEvent.css({height: options.timeslotHeight * options.defaultEventLength}).show();
1116
- }
1117
- var top = parseInt($newEvent.css('top'));
1118
- var eventDuration = self._getEventDurationFromPositionedEventElement($weekDay, $newEvent, top);
1119
-
1120
- $newEvent.remove();
1121
- var newCalEvent = {start: eventDuration.start, end: eventDuration.end, title: options.newEventText};
1122
- var showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
1123
-
1124
- if (showAsSeparatedUser) {
1125
- newCalEvent = self._setEventUserId(newCalEvent, $weekDay.data('wcUserId'));
1126
- }
1127
- else if (!options.showAsSeparateUsers && options.users && options.users.length == 1) {
1128
- newCalEvent = self._setEventUserId(newCalEvent, self._getUserIdFromIndex(0));
1129
- }
1130
-
1131
- var freeBusyManager = self.getFreeBusyManagerForEvent(newCalEvent);
1132
-
1133
- var $renderedCalEvent = self._renderEvent(newCalEvent, $weekDay);
1134
-
1135
- if (!options.allowCalEventOverlap) {
1136
- self._adjustForEventCollisions($weekDay, $renderedCalEvent, newCalEvent, newCalEvent);
1137
- self._positionEvent($weekDay, $renderedCalEvent);
1138
- } else {
1139
- self._adjustOverlappingEvents($weekDay);
1140
- }
1141
-
1142
- var proceed = self._trigger('beforeEventNew', event, {
1143
- 'calEvent': newCalEvent,
1144
- 'createdFromSingleClick': createdFromSingleClick,
1145
- 'calendar': self.element
1146
- });
1147
- if (proceed) {
1148
- options.eventNew(newCalEvent, $renderedCalEvent, freeBusyManager, self.element, event);
1149
- }
1150
- else {
1151
- $($renderedCalEvent).remove();
1152
- }
1153
- }
1154
- });
1155
- },
1156
-
1157
- /*
1158
- * load calendar events for the week based on the date provided
1159
- */
1160
- _loadCalEvents: function(dateWithinWeek) {
1161
-
1162
- var date, weekStartDate, weekEndDate, $weekDayColumns;
1163
- var self = this;
1164
- var options = this.options;
1165
- date = this._fixMinMaxDate(dateWithinWeek || options.date);
1166
- // if date is not provided
1167
- // or was not set
1168
- // or is different than old one
1169
- if ((!date || !date.getTime) ||
1170
- (!options.date || !options.date.getTime) ||
1171
- date.getTime() != options.date.getTime()
1172
- ) {
1173
- // trigger the changedate event
1174
- this._trigger('changedate', this.element, date);
1175
- }
1176
- this.options.date = date;
1177
- weekStartDate = self._dateFirstDayOfWeek(date);
1178
- weekEndDate = self._dateLastMilliOfWeek(date);
1179
-
1180
- options.calendarBeforeLoad(self.element);
1181
-
1182
- self.element.data('startDate', weekStartDate);
1183
- self.element.data('endDate', weekEndDate);
1184
-
1185
- $weekDayColumns = self.element.find('.wc-day-column-inner');
1186
-
1187
- self._updateDayColumnHeader($weekDayColumns);
1188
-
1189
- //load events by chosen means
1190
- if (typeof options.data == 'string') {
1191
- if (options.loading) {
1192
- options.loading(true);
1193
- }
1194
- if (_currentAjaxCall) {
1195
- // first abort current request.
1196
- if (!_jQuery14OrLower) {
1197
- _currentAjaxCall.abort();
1198
- } else {
1199
- // due to the fact that jquery 1.4 does not detect a request was
1200
- // aborted, we need to replace the onreadystatechange and
1201
- // execute the "complete" callback.
1202
- _currentAjaxCall.onreadystatechange = null;
1203
- _currentAjaxCall.abort();
1204
- _currentAjaxCall = null;
1205
- if (options.loading) {
1206
- options.loading(false);
1207
- }
1208
- }
1209
- }
1210
- var jsonOptions = self._getJsonOptions();
1211
- jsonOptions[options.startParam || 'start'] = Math.round(weekStartDate.getTime() / 1000);
1212
- jsonOptions[options.endParam || 'end'] = Math.round(weekEndDate.getTime() / 1000);
1213
- _currentAjaxCall = $.ajax({
1214
- url: options.data,
1215
- data: jsonOptions,
1216
- dataType: 'json',
1217
- error: function(XMLHttpRequest, textStatus, errorThrown) {
1218
- // only prevent error with jQuery 1.5
1219
- // see issue #34. thanks to dapplebeforedawn
1220
- // (https://github.com/themouette/jquery-week-calendar/issues#issue/34)
1221
- // for 1.5+, aborted request mean errorThrown == 'abort'
1222
- // for prior version it means !errorThrown && !XMLHttpRequest.status
1223
- // fixes #55
1224
- if (errorThrown != 'abort' && XMLHttpRequest.status != 0) {
1225
- alert('unable to get data, error:' + textStatus);
1226
- }
1227
- },
1228
- success: function(data) {
1229
- self._renderEvents(data, $weekDayColumns);
1230
- },
1231
- complete: function() {
1232
- _currentAjaxCall = null;
1233
- if (options.loading) {
1234
- options.loading(false);
1235
- }
1236
- }
1237
- });
1238
- }
1239
- else if ($.isFunction(options.data)) {
1240
- options.data(weekStartDate, weekEndDate,
1241
- function(data) {
1242
- self._renderEvents(data, $weekDayColumns);
1243
- if ($('.wc-hourline', self.element).length) {
1244
- self._scrollToHour(new Date().getHours() + 1, true);
1245
- } else {
1246
- if (data.events.length) {
1247
- self._scrollToHour(data.events[0].start.getHours() + 1, true);
1248
- } else if (data.freebusys.length) {
1249
- self._scrollToHour(data.freebusys[0].start.getHours() + 1, true);
1250
- }
1251
- }
1252
- });
1253
- }
1254
- else if (options.data) {
1255
- self._renderEvents(options.data, $weekDayColumns);
1256
- }
1257
-
1258
- self._disableTextSelect($weekDayColumns);
1259
- },
1260
-
1261
- /**
1262
- * Draws a thin line which indicates the current time.
1263
- */
1264
- _drawCurrentHourLine: function() {
1265
- var d = new Date(),
1266
- options = this.options,
1267
- businessHours = options.businessHours;
1268
-
1269
- // first, we remove the old hourline if it exists
1270
- $('.wc-hourline', this.element).remove();
1271
-
1272
- // the line does not need to be displayed
1273
- if (businessHours.limitDisplay && d.getHours() > businessHours.end) {
1274
- return;
1275
- }
1276
-
1277
- // then we recreate it
1278
- var paddingStart = businessHours.limitDisplay ? businessHours.start : 0;
1279
- var nbHours = d.getHours() - paddingStart + d.getMinutes() / 60;
1280
- var positionTop = nbHours * options.timeslotHeight * options.timeslotsPerHour;
1281
- var lineWidth = $('.wc-scrollable-grid .wc-today', this.element).width() + 3;
1282
-
1283
- $('.wc-scrollable-grid .wc-today', this.element).append(
1284
- $('<div>', {
1285
- 'class': 'wc-hourline',
1286
- style: 'top: ' + positionTop + 'px; width: ' + lineWidth + 'px'
1287
- })
1288
- );
1289
- },
1290
-
1291
- /*
1292
- * update the display of each day column header based on the calendar week
1293
- */
1294
- _updateDayColumnHeader: function($weekDayColumns) {
1295
- var self = this;
1296
- var options = this.options;
1297
- var currentDay = self._cloneDate(self.element.data('startDate'));
1298
- var showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
1299
- var todayClass = 'ui-state-active wc-today';
1300
-
1301
- self.element.find('.wc-header td.wc-day-column-header').each(function(i, val) {
1302
- $(this).html(self._getHeaderDate(currentDay));
1303
- if (self._isToday(currentDay)) {
1304
- $(this).addClass(todayClass);
1305
- } else {
1306
- $(this).removeClass(todayClass);
1307
- }
1308
- currentDay = self._addDays(currentDay, 1);
1309
-
1310
- });
1311
-
1312
- currentDay = self._cloneDate(self.element.data('startDate'));
1313
- if (showAsSeparatedUser)
1314
- {
1315
- self.element.find('.wc-header td.wc-user-header').each(function(i, val) {
1316
- if (self._isToday(currentDay)) {
1317
- $(this).addClass(todayClass);
1318
- } else {
1319
- $(this).removeClass(todayClass);
1320
- }
1321
- currentDay = ((i + 1) % options.users.length) ? currentDay : self._addDays(currentDay, 1);
1322
- });
1323
- }
1324
-
1325
- currentDay = self._cloneDate(self.element.data('startDate'));
1326
-
1327
- $weekDayColumns.each(function(i, val) {
1328
-
1329
- $(this).data('startDate', self._cloneDate(currentDay));
1330
- $(this).data('endDate', new Date(currentDay.getTime() + (MILLIS_IN_DAY)));
1331
- if (self._isToday(currentDay)) {
1332
- $(this).parent()
1333
- .addClass(todayClass)
1334
- .removeClass('ui-state-default');
1335
- } else {
1336
- $(this).parent()
1337
- .removeClass(todayClass)
1338
- .addClass('ui-state-default');
1339
- }
1340
-
1341
- if (!showAsSeparatedUser || !((i + 1) % options.users.length)) {
1342
- currentDay = self._addDays(currentDay, 1);
1343
- }
1344
- });
1345
-
1346
- //now update the freeBusy placeholders
1347
- if (options.displayFreeBusys) {
1348
- currentDay = self._cloneDate(self.element.data('startDate'));
1349
- self.element.find('.wc-grid-row-freebusy .wc-column-freebusy').each(function(i, val) {
1350
- $(this).data('startDate', self._cloneDate(currentDay));
1351
- $(this).data('endDate', new Date(currentDay.getTime() + (MILLIS_IN_DAY)));
1352
- if (!showAsSeparatedUser || !((i + 1) % options.users.length)) {
1353
- currentDay = self._addDays(currentDay, 1);
1354
- }
1355
- });
1356
- }
1357
-
1358
- // now update the calendar title
1359
- if (this.options.title) {
1360
- var date = this.options.date,
1361
- start = self._cloneDate(self.element.data('startDate')),
1362
- end = self._dateLastDayOfWeek(new Date(this._cloneDate(self.element.data('endDate')).getTime() - (MILLIS_IN_DAY))),
1363
- title = this._getCalendarTitle(),
1364
- date_format = options.dateFormat;
1365
-
1366
- // replace the placeholders contained in the title
1367
- title = title.replace('%start%', self._formatDate(start, date_format));
1368
- title = title.replace('%end%', self._formatDate(end, date_format));
1369
- title = title.replace('%date%', self._formatDate(date, date_format));
1370
-
1371
- $('.wc-toolbar .wc-title', self.element).html(title);
1372
- }
1373
- //self._clearFreeBusys();
1374
- },
1375
-
1376
- /**
1377
- * Gets the calendar raw title.
1378
- */
1379
- _getCalendarTitle: function() {
1380
- if ($.isFunction(this.options.title)) {
1381
- return this.options.title(this.options.daysToShow);
1382
- }
1383
-
1384
- return this.options.title || '';
1385
- },
1386
-
1387
- /**
1388
- * Render the events into the calendar
1389
- */
1390
- _renderEvents: function(data, $weekDayColumns) {
1391
- var self = this;
1392
- var options = this.options;
1393
- var eventsToRender, nbRenderedEvents = 0;
1394
-
1395
- if (data.options) {
1396
- var updateLayout = false;
1397
- // update options
1398
- $.each(data.options, function(key, value) {
1399
- if (value !== options[key]) {
1400
- options[key] = value;
1401
- updateLayout = updateLayout || $.ui.weekCalendar.updateLayoutOptions[key];
1402
- }
1403
- });
1404
-
1405
- self._computeOptions();
1406
-
1407
- if (updateLayout) {
1408
- var hour = self._getCurrentScrollHour();
1409
- self.element.empty();
1410
- self._renderCalendar();
1411
- $weekDayColumns = self.element.find('.wc-time-slots .wc-day-column-inner');
1412
- self._updateDayColumnHeader($weekDayColumns);
1413
- self._resizeCalendar();
1414
- self._scrollToHour(hour, false);
1415
- }
1416
- }
1417
- this._clearCalendar();
1418
-
1419
- if ($.isArray(data)) {
1420
- eventsToRender = self._cleanEvents(data);
1421
- } else if (data.events) {
1422
- eventsToRender = self._cleanEvents(data.events);
1423
- self._renderFreeBusys(data);
1424
- }
1425
-
1426
- $.each(eventsToRender, function(i, calEvent) {
1427
- // render a multi day event as various event :
1428
- // thanks to http://github.com/fbeauchamp/jquery-week-calendar
1429
- var initialStart = new Date(calEvent.start);
1430
- var initialEnd = new Date(calEvent.end);
1431
- var maxHour = self.options.businessHours.limitDisplay ? self.options.businessHours.end : 24;
1432
- var minHour = self.options.businessHours.limitDisplay ? self.options.businessHours.start : 0;
1433
- var start = new Date(initialStart);
1434
- var startDate = self._formatDate(start, 'Ymd');
1435
- var endDate = self._formatDate(initialEnd, 'Ymd');
1436
- var $weekDay;
1437
- var isMultiday = false;
1438
-
1439
- while (startDate < endDate) {
1440
- calEvent.start = start;
1441
-
1442
- // end of this virual calEvent is set to the end of the day
1443
- calEvent.end.setFullYear(start.getFullYear());
1444
- calEvent.end.setDate(start.getDate());
1445
- calEvent.end.setMonth(start.getMonth());
1446
- calEvent.end.setHours(maxHour, 0, 0);
1447
-
1448
- if (($weekDay = self._findWeekDayForEvent(calEvent, $weekDayColumns))) {
1449
- self._renderEvent(calEvent, $weekDay);
1450
- nbRenderedEvents += 1;
1451
- }
1452
-
1453
- // start is set to the begin of the new day
1454
- start.setDate(start.getDate() + 1);
1455
- start.setHours(minHour, 0, 0);
1456
-
1457
- startDate = self._formatDate(start, 'Ymd');
1458
- isMultiday = true;
1459
- }
1460
-
1461
- if (start <= initialEnd) {
1462
- calEvent.start = start;
1463
- calEvent.end = initialEnd;
1464
-
1465
- if (((isMultiday && calEvent.start.getTime() != calEvent.end.getTime()) || !isMultiday) && ($weekDay = self._findWeekDayForEvent(calEvent, $weekDayColumns))) {
1466
- self._renderEvent(calEvent, $weekDay);
1467
- nbRenderedEvents += 1;
1468
- }
1469
- }
1470
-
1471
- // put back the initial start date
1472
- calEvent.start = initialStart;
1473
- });
1474
-
1475
- $weekDayColumns.each(function() {
1476
- self._adjustOverlappingEvents($(this));
1477
- });
1478
-
1479
- options.calendarAfterLoad(self.element);
1480
-
1481
- _hourLineTimeout && clearInterval(_hourLineTimeout);
1482
-
1483
- if (options.hourLine) {
1484
- self._drawCurrentHourLine();
1485
-
1486
- _hourLineTimeout = setInterval(function() {
1487
- self._drawCurrentHourLine();
1488
- }, 60 * 1000); // redraw the line each minute
1489
- }
1490
-
1491
- !nbRenderedEvents && options.noEvents();
1492
- },
1493
-
1494
- /*
1495
- * Render a specific event into the day provided. Assumes correct
1496
- * day for calEvent date
1497
- */
1498
- _renderEvent: function(calEvent, $weekDay) {
1499
- var self = this;
1500
- var options = this.options;
1501
- if (calEvent.start.getTime() > calEvent.end.getTime()) {
1502
- return; // can't render a negative height
1503
- }
1504
-
1505
- var eventClass, eventHtml, $calEventList, $modifiedEvent;
1506
-
1507
- eventClass = calEvent.id ? 'wc-cal-event' : 'wc-cal-event wc-new-cal-event';
1508
- eventHtml = '<div class=\"' + eventClass + ' ui-corner-all\">';
1509
- eventHtml += '<div class=\"wc-time ui-corner-top\"></div>';
1510
- eventHtml += '<div class=\"wc-title\"></div></div>';
1511
-
1512
- $weekDay.each(function() {
1513
- var $calEvent = $(eventHtml);
1514
- $modifiedEvent = options.eventRender(calEvent, $calEvent);
1515
- $calEvent = $modifiedEvent ? $modifiedEvent.appendTo($(this)) : $calEvent.appendTo($(this));
1516
- $calEvent.css({lineHeight: (options.textSize + 2) + 'px', fontSize: options.textSize + 'px'});
1517
-
1518
- self._refreshEventDetails(calEvent, $calEvent);
1519
- self._positionEvent($(this), $calEvent);
1520
-
1521
- //add to event list
1522
- if ($calEventList) {
1523
- $calEventList = $calEventList.add($calEvent);
1524
- }
1525
- else {
1526
- $calEventList = $calEvent;
1527
- }
1528
- });
1529
- $calEventList.show();
1530
-
1531
- if (!options.readonly && options.resizable(calEvent, $calEventList)) {
1532
- self._addResizableToCalEvent(calEvent, $calEventList, $weekDay);
1533
- }
1534
- if (!options.readonly && options.draggable(calEvent, $calEventList)) {
1535
- self._addDraggableToCalEvent(calEvent, $calEventList);
1536
- }
1537
- options.eventAfterRender(calEvent, $calEventList);
1538
-
1539
- return $calEventList;
1540
-
1541
- },
1542
- addEvent: function() {
1543
- return this._renderEvent.apply(this, arguments);
1544
- },
1545
-
1546
- _adjustOverlappingEvents: function($weekDay) {
1547
- var self = this;
1548
- if (self.options.allowCalEventOverlap) {
1549
- var groupsList = self._groupOverlappingEventElements($weekDay);
1550
- $.each(groupsList, function() {
1551
- var curGroups = this;
1552
- $.each(curGroups, function(groupIndex) {
1553
- var curGroup = this;
1554
-
1555
- // do we want events to be displayed as overlapping
1556
- if (self.options.overlapEventsSeparate) {
1557
- var newWidth = self.options.totalEventsWidthPercentInOneColumn / curGroups.length;
1558
- var newLeft = groupIndex * newWidth;
1559
- } else {
1560
- // TODO what happens when the group has more than 10 elements
1561
- var newWidth = self.options.totalEventsWidthPercentInOneColumn - ((curGroups.length - 1) * 10);
1562
- var newLeft = groupIndex * 10;
1563
- }
1564
- $.each(curGroup, function() {
1565
- // bring mouseovered event to the front
1566
- if (!self.options.overlapEventsSeparate) {
1567
- $(this).bind('mouseover.z-index', function() {
1568
- var $elem = $(this);
1569
- $.each(curGroup, function() {
1570
- $(this).css({'z-index': '1'});
1571
- });
1572
- $elem.css({'z-index': '3'});
1573
- });
1574
- }
1575
- $(this).css({width: newWidth + '%', left: newLeft + '%', right: 0});
1576
- });
1577
- });
1578
- });
1579
- }
1580
- },
1581
-
1582
-
1583
- /*
1584
- * Find groups of overlapping events
1585
- */
1586
- _groupOverlappingEventElements: function($weekDay) {
1587
- var $events = $weekDay.find('.wc-cal-event:visible');
1588
- var sortedEvents = $events.sort(function(a, b) {
1589
- return $(a).data('calEvent').start.getTime() - $(b).data('calEvent').start.getTime();
1590
- });
1591
-
1592
- var lastEndTime = new Date(0, 0, 0);
1593
- var groups = [];
1594
- var curGroups = [];
1595
- var $curEvent;
1596
- $.each(sortedEvents, function() {
1597
- $curEvent = $(this);
1598
- //checks, if the current group list is not empty, if the overlapping is finished
1599
- if (curGroups.length > 0) {
1600
- if (lastEndTime.getTime() <= $curEvent.data('calEvent').start.getTime()) {
1601
- //finishes the current group list by adding it to the resulting list of groups and cleans it
1602
-
1603
- groups.push(curGroups);
1604
- curGroups = [];
1605
- }
1606
- }
1607
-
1608
- //finds the first group to fill with the event
1609
- for (var groupIndex = 0; groupIndex < curGroups.length; groupIndex++) {
1610
- if (curGroups[groupIndex].length > 0) {
1611
- //checks if the event starts after the end of the last event of the group
1612
- if (curGroups[groupIndex][curGroups[groupIndex].length - 1].data('calEvent').end.getTime() <= $curEvent.data('calEvent').start.getTime()) {
1613
- curGroups[groupIndex].push($curEvent);
1614
- if (lastEndTime.getTime() < $curEvent.data('calEvent').end.getTime()) {
1615
- lastEndTime = $curEvent.data('calEvent').end;
1616
- }
1617
- return;
1618
- }
1619
- }
1620
- }
1621
- //if not found, creates a new group
1622
- curGroups.push([$curEvent]);
1623
- if (lastEndTime.getTime() < $curEvent.data('calEvent').end.getTime()) {
1624
- lastEndTime = $curEvent.data('calEvent').end;
1625
- }
1626
- });
1627
- //adds the last groups in result
1628
- if (curGroups.length > 0) {
1629
- groups.push(curGroups);
1630
- }
1631
- return groups;
1632
- },
1633
-
1634
-
1635
- /*
1636
- * find the weekday in the current calendar that the calEvent falls within
1637
- */
1638
- _findWeekDayForEvent: function(calEvent, $weekDayColumns) {
1639
-
1640
- var $weekDay,
1641
- options = this.options,
1642
- showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
1643
- user_ids = this._getEventUserId(calEvent);
1644
-
1645
- if (!$.isArray(user_ids)) {
1646
- user_ids = [user_ids];
1647
- }
1648
-
1649
- $weekDayColumns.each(function(index, curDay) {
1650
- if ($(this).data('startDate').getTime() <= calEvent.start.getTime() &&
1651
- $(this).data('endDate').getTime() >= calEvent.end.getTime() &&
1652
- (!showAsSeparatedUser || $.inArray($(this).data('wcUserId'), user_ids) !== -1)
1653
- ) {
1654
- if ($weekDay) {
1655
- $weekDay = $weekDay.add($(curDay));
1656
- }
1657
- else {
1658
- $weekDay = $(curDay);
1659
- }
1660
- }
1661
- });
1662
-
1663
- return $weekDay;
1664
- },
1665
-
1666
- /*
1667
- * update the events rendering in the calendar. Add if does not yet exist.
1668
- */
1669
- _updateEventInCalendar: function(calEvent) {
1670
- var self = this;
1671
- self._cleanEvent(calEvent);
1672
-
1673
- if (calEvent.id) {
1674
- self.element.find('.wc-cal-event').each(function() {
1675
- if ($(this).data('calEvent').id === calEvent.id || $(this).hasClass('wc-new-cal-event')) {
1676
- $(this).remove();
1677
- // return false;
1678
- }
1679
- });
1680
- }
1681
-
1682
- var $weekDays = self._findWeekDayForEvent(calEvent, self.element.find('.wc-grid-row-events .wc-day-column-inner'));
1683
- if ($weekDays) {
1684
- $weekDays.each(function(index, weekDay) {
1685
- var $weekDay = $(weekDay);
1686
- var $calEvent = self._renderEvent(calEvent, $weekDay);
1687
- self._adjustForEventCollisions($weekDay, $calEvent, calEvent, calEvent);
1688
- // self._refreshEventDetails(calEvent, $calEvent);
1689
- // self._positionEvent($weekDay, $calEvent);
1690
- self._adjustOverlappingEvents($weekDay);
1691
- });
1692
- }
1693
- },
1694
-
1695
- /*
1696
- * Position the event element within the weekday based on it's start / end dates.
1697
- */
1698
- _positionEvent: function($weekDay, $calEvent) {
1699
- var options = this.options;
1700
- var calEvent = $calEvent.data('calEvent');
1701
- var pxPerMillis = $weekDay.height() / options.millisToDisplay;
1702
- var firstHourDisplayed = options.businessHours.limitDisplay ? options.businessHours.start : 0;
1703
- var startMillis = this._getDSTdayShift(calEvent.start).getTime() - this._getDSTdayShift(new Date(calEvent.start.getFullYear(), calEvent.start.getMonth(), calEvent.start.getDate(), firstHourDisplayed)).getTime();
1704
- var eventMillis = this._getDSTdayShift(calEvent.end).getTime() - this._getDSTdayShift(calEvent.start).getTime();
1705
- var pxTop = pxPerMillis * startMillis;
1706
- var pxHeight = pxPerMillis * eventMillis;
1707
- //var pxHeightFallback = pxPerMillis * (60 / options.timeslotsPerHour) * 60 * 1000;
1708
- $calEvent.css({top: pxTop, height: pxHeight || (pxPerMillis * 3600000 / options.timeslotsPerHour)});
1709
- },
1710
-
1711
- /*
1712
- * Determine the actual start and end times of a calevent based on it's
1713
- * relative position within the weekday column and the starting hour of the
1714
- * displayed calendar.
1715
- */
1716
- _getEventDurationFromPositionedEventElement: function($weekDay, $calEvent, top) {
1717
- var options = this.options;
1718
- var startOffsetMillis = options.businessHours.limitDisplay ? options.businessHours.start * 3600000 : 0;
1719
- var start = new Date($weekDay.data('startDate').getTime() + startOffsetMillis + Math.round(top / options.timeslotHeight) * options.millisPerTimeslot);
1720
- var end = new Date(start.getTime() + ($calEvent.height() / options.timeslotHeight) * options.millisPerTimeslot);
1721
- return {start: this._getDSTdayShift(start, -1), end: this._getDSTdayShift(end, -1)};
1722
- },
1723
-
1724
- /*
1725
- * If the calendar does not allow event overlap, adjust the start or end date if necessary to
1726
- * avoid overlapping of events. Typically, shortens the resized / dropped event to it's max possible
1727
- * duration based on the overlap. If no satisfactory adjustment can be made, the event is reverted to
1728
- * it's original location.
1729
- */
1730
- _adjustForEventCollisions: function($weekDay, $calEvent, newCalEvent, oldCalEvent, maintainEventDuration) {
1731
- var options = this.options;
1732
-
1733
- if (options.allowCalEventOverlap) {
1734
- return;
1735
- }
1736
- var adjustedStart, adjustedEnd;
1737
- var self = this;
1738
-
1739
- $weekDay.find('.wc-cal-event').not($calEvent).each(function() {
1740
- var currentCalEvent = $(this).data('calEvent');
1741
-
1742
- //has been dropped onto existing event overlapping the end time
1743
- if (newCalEvent.start.getTime() < currentCalEvent.end.getTime() &&
1744
- newCalEvent.end.getTime() >= currentCalEvent.end.getTime()) {
1745
-
1746
- adjustedStart = currentCalEvent.end;
1747
- }
1748
-
1749
-
1750
- //has been dropped onto existing event overlapping the start time
1751
- if (newCalEvent.end.getTime() > currentCalEvent.start.getTime() &&
1752
- newCalEvent.start.getTime() <= currentCalEvent.start.getTime()) {
1753
-
1754
- adjustedEnd = currentCalEvent.start;
1755
- }
1756
- //has been dropped inside existing event with same or larger duration
1757
- if (oldCalEvent.resizable == false ||
1758
- (newCalEvent.end.getTime() <= currentCalEvent.end.getTime() &&
1759
- newCalEvent.start.getTime() >= currentCalEvent.start.getTime())) {
1760
-
1761
- adjustedStart = oldCalEvent.start;
1762
- adjustedEnd = oldCalEvent.end;
1763
- return false;
1764
- }
1765
-
1766
- });
1767
-
1768
-
1769
- newCalEvent.start = adjustedStart || newCalEvent.start;
1770
-
1771
- if (adjustedStart && maintainEventDuration) {
1772
- newCalEvent.end = new Date(adjustedStart.getTime() + (oldCalEvent.end.getTime() - oldCalEvent.start.getTime()));
1773
- self._adjustForEventCollisions($weekDay, $calEvent, newCalEvent, oldCalEvent);
1774
- } else {
1775
- newCalEvent.end = adjustedEnd || newCalEvent.end;
1776
- }
1777
-
1778
-
1779
- //reset if new cal event has been forced to zero size
1780
- if (newCalEvent.start.getTime() >= newCalEvent.end.getTime()) {
1781
- newCalEvent.start = oldCalEvent.start;
1782
- newCalEvent.end = oldCalEvent.end;
1783
- }
1784
-
1785
- $calEvent.data('calEvent', newCalEvent);
1786
- },
1787
-
1788
- /**
1789
- * Add draggable capabilities to an event
1790
- */
1791
- _addDraggableToCalEvent: function(calEvent, $calEvent) {
1792
- var options = this.options;
1793
-
1794
- $calEvent.draggable({
1795
- handle: '.wc-time',
1796
- containment: 'div.wc-time-slots',
1797
- snap: '.wc-day-column-inner',
1798
- snapMode: 'inner',
1799
- snapTolerance: options.timeslotHeight - 1,
1800
- revert: 'invalid',
1801
- opacity: 0.5,
1802
- grid: [$calEvent.outerWidth() + 1, options.timeslotHeight],
1803
- start: function(event, ui) {
1804
- var $calEvent = ui.draggable || ui.helper;
1805
- options.eventDrag(calEvent, $calEvent);
1806
- }
1807
- });
1808
- },
1809
-
1810
- /*
1811
- * Add droppable capabilites to weekdays to allow dropping of calEvents only
1812
- */
1813
- _addDroppableToWeekDay: function($weekDay) {
1814
- var self = this;
1815
- var options = this.options;
1816
- $weekDay.droppable({
1817
- accept: '.wc-cal-event',
1818
- drop: function(event, ui) {
1819
- var $calEvent = ui.draggable;
1820
- var top = Math.round(parseInt(ui.position.top));
1821
- var eventDuration = self._getEventDurationFromPositionedEventElement($weekDay, $calEvent, top);
1822
- var calEvent = $calEvent.data('calEvent');
1823
- var newCalEvent = $.extend(true, {}, calEvent, {start: eventDuration.start, end: eventDuration.end});
1824
- var showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
1825
- if (showAsSeparatedUser) {
1826
- // we may have dragged the event on column with a new user.
1827
- // nice way to handle that is:
1828
- // - get the newly dragged on user
1829
- // - check if user is part of the event
1830
- // - if yes, nothing changes, if not, find the old owner to remove it and add new one
1831
- var newUserId = $weekDay.data('wcUserId');
1832
- var userIdList = self._getEventUserId(calEvent);
1833
- var oldUserId = $(ui.draggable.parents('.wc-day-column-inner').get(0)).data('wcUserId');
1834
- if (!$.isArray(userIdList)) {
1835
- userIdList = [userIdList];
1836
- }
1837
- if ($.inArray(newUserId, userIdList) == -1) {
1838
- // remove old user
1839
- var _index = $.inArray(oldUserId, userIdList);
1840
- userIdList.splice(_index, 1);
1841
- // add new user ?
1842
- if ($.inArray(newUserId, userIdList) == -1) {
1843
- userIdList.push(newUserId);
1844
- }
1845
- }
1846
- newCalEvent = self._setEventUserId(newCalEvent, ((userIdList.length == 1) ? userIdList[0] : userIdList));
1847
- }
1848
- self._adjustForEventCollisions($weekDay, $calEvent, newCalEvent, calEvent, true);
1849
- var $weekDayColumns = self.element.find('.wc-day-column-inner');
1850
-
1851
- //trigger drop callback
1852
- options.eventDrop(newCalEvent, calEvent, $calEvent);
1853
-
1854
- var $newEvent = self._renderEvent(newCalEvent, self._findWeekDayForEvent(newCalEvent, $weekDayColumns));
1855
- $calEvent.hide();
1856
-
1857
- $calEvent.data('preventClick', true);
1858
-
1859
- var $weekDayOld = self._findWeekDayForEvent($calEvent.data('calEvent'), self.element.find('.wc-time-slots .wc-day-column-inner'));
1860
-
1861
- if ($weekDayOld.data('startDate') != $weekDay.data('startDate')) {
1862
- self._adjustOverlappingEvents($weekDayOld);
1863
- }
1864
- self._adjustOverlappingEvents($weekDay);
1865
-
1866
- setTimeout(function() {
1867
- $calEvent.remove();
1868
- }, 1000);
1869
-
1870
- }
1871
- });
1872
- },
1873
-
1874
- /*
1875
- * Add resizable capabilities to a calEvent
1876
- */
1877
- _addResizableToCalEvent: function(calEvent, $calEvent, $weekDay) {
1878
- var self = this;
1879
- var options = this.options;
1880
- $calEvent.resizable({
1881
- grid: options.timeslotHeight,
1882
- containment: $weekDay,
1883
- handles: 's',
1884
- minHeight: options.timeslotHeight,
1885
- stop: function(event, ui) {
1886
- var $calEvent = ui.element;
1887
- var newEnd = new Date($calEvent.data('calEvent').start.getTime() + Math.max(1, Math.round(ui.size.height / options.timeslotHeight)) * options.millisPerTimeslot);
1888
- if (self._needDSTdayShift($calEvent.data('calEvent').start, newEnd))
1889
- newEnd = self._getDSTdayShift(newEnd, -1);
1890
- var newCalEvent = $.extend(true, {}, calEvent, {start: calEvent.start, end: newEnd});
1891
- self._adjustForEventCollisions($weekDay, $calEvent, newCalEvent, calEvent);
1892
-
1893
- //trigger resize callback
1894
- options.eventResize(newCalEvent, calEvent, $calEvent);
1895
- self._refreshEventDetails(newCalEvent, $calEvent);
1896
- self._positionEvent($weekDay, $calEvent);
1897
- self._adjustOverlappingEvents($weekDay);
1898
- $calEvent.data('preventClick', true);
1899
- setTimeout(function() {
1900
- $calEvent.removeData('preventClick');
1901
- }, 500);
1902
- }
1903
- });
1904
- $('.ui-resizable-handle', $calEvent).text('=');
1905
- },
1906
-
1907
- /*
1908
- * Refresh the displayed details of a calEvent in the calendar
1909
- */
1910
- _refreshEventDetails: function(calEvent, $calEvent) {
1911
- var prefix = '';
1912
- if (!this.options.readonly &&
1913
- this.options.allowEventDelete &&
1914
- this.options.deletable(calEvent,$calEvent)) {
1915
- prefix = '<div class="wc-cal-event-delete ui-icon ui-icon-close"></div>';
1916
- }
1917
- $calEvent.find('.wc-time').html(prefix + this.options.eventHeader(calEvent, this.element));
1918
- $calEvent.find('.wc-title').html(this.options.eventBody(calEvent, this.element));
1919
-
1920
- $calEvent.data('calEvent', calEvent);
1921
- this.options.eventRefresh(calEvent, $calEvent);
1922
- },
1923
-
1924
- /*
1925
- * Clear all cal events from the calendar
1926
- */
1927
- _clearCalendar: function() {
1928
- this.element.find('.wc-day-column-inner div').remove();
1929
- this._clearFreeBusys();
1930
- },
1931
-
1932
- /*
1933
- * Scroll the calendar to a specific hour
1934
- */
1935
- _scrollToHour: function(hour, animate) {
1936
- var self = this;
1937
- var options = this.options;
1938
- var $scrollable = this.element.find('.wc-scrollable-grid');
1939
- var slot = hour;
1940
- if (self.options.businessHours.limitDisplay) {
1941
- if (hour <= self.options.businessHours.start) {
1942
- slot = 0;
1943
- } else if (hour >= self.options.businessHours.end) {
1944
- slot = self.options.businessHours.end - self.options.businessHours.start - 1;
1945
- } else {
1946
- slot = hour - self.options.businessHours.start;
1947
- }
1948
- }
1949
-
1950
- var $target = this.element.find('.wc-grid-timeslot-header .wc-hour-header:eq(' + slot + ')');
1951
-
1952
- $scrollable.animate({scrollTop: 0}, 0, function() {
1953
- var targetOffset = $target.offset().top;
1954
- var scroll = targetOffset - $scrollable.offset().top - $target.outerHeight();
1955
- if (animate) {
1956
- $scrollable.animate({scrollTop: scroll}, options.scrollToHourMillis);
1957
- }
1958
- else {
1959
- $scrollable.animate({scrollTop: scroll}, 0);
1960
- }
1961
- });
1962
- },
1963
-
1964
- /*
1965
- * find the hour (12 hour day) for a given hour index
1966
- */
1967
- _hourForIndex: function(index) {
1968
- if (index === 0) { //midnight
1969
- return 12;
1970
- } else if (index < 13) { //am
1971
- return index;
1972
- } else { //pm
1973
- return index - 12;
1974
- }
1975
- },
1976
-
1977
- _24HourForIndex: function(index) {
1978
- if (index === 0) { //midnight
1979
- return '00';
1980
- } else if (index < 10) {
1981
- return '0' + index;
1982
- } else {
1983
- return index;
1984
- }
1985
- },
1986
-
1987
- _amOrPm: function(hourOfDay) {
1988
- return hourOfDay < 12 ? BooklyL10n['AM'] : BooklyL10n['PM'];
1989
- },
1990
-
1991
- _isToday: function(date) {
1992
- var clonedDate = this._cloneDate(date);
1993
- this._clearTime(clonedDate);
1994
- var today = new Date();
1995
- this._clearTime(today);
1996
- return today.getTime() === clonedDate.getTime();
1997
- },
1998
-
1999
- /*
2000
- * Clean events to ensure correct format
2001
- */
2002
- _cleanEvents: function(events) {
2003
- var self = this;
2004
- $.each(events, function(i, event) {
2005
- self._cleanEvent(event);
2006
- });
2007
- return events;
2008
- },
2009
-
2010
- /*
2011
- * Clean specific event
2012
- */
2013
- _cleanEvent: function(event) {
2014
- if (event.date) {
2015
- event.start = event.date;
2016
- }
2017
- event.start = this._cleanDate(event.start);
2018
- event.end = this._cleanDate(event.end);
2019
- if (!event.end) {
2020
- event.end = this._addDays(this._cloneDate(event.start), 1);
2021
- }
2022
- },
2023
-
2024
- /*
2025
- * Disable text selection of the elements in different browsers
2026
- */
2027
- _disableTextSelect: function($elements) {
2028
- $elements.each(function() {
2029
- if ($.browser.mozilla) {//Firefox
2030
- $(this).css('MozUserSelect', 'none');
2031
- } else if ($.browser.msie) {//IE
2032
- $(this).bind('selectstart', function() {
2033
- return false;
2034
- });
2035
- } else {//Opera, etc.
2036
- $(this).mousedown(function() {
2037
- return false;
2038
- });
2039
- }
2040
- });
2041
- },
2042
-
2043
- /*
2044
- * returns the date on the first millisecond of the week
2045
- */
2046
- _dateFirstDayOfWeek: function(date) {
2047
- var self = this;
2048
- var midnightCurrentDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
2049
- var adjustedDate = new Date(midnightCurrentDate);
2050
- adjustedDate.setDate(adjustedDate.getDate() - self._getAdjustedDayIndex(midnightCurrentDate));
2051
-
2052
- return adjustedDate;
2053
- },
2054
-
2055
- /*
2056
- * returns the date on the first millisecond of the last day of the week
2057
- */
2058
- _dateLastDayOfWeek: function(date) {
2059
- var self = this;
2060
- var midnightCurrentDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
2061
- var adjustedDate = new Date(midnightCurrentDate);
2062
- var daysToAdd = (self.options.daysToShow - 1 - self._getAdjustedDayIndex(midnightCurrentDate));
2063
- adjustedDate.setDate(adjustedDate.getDate() + daysToAdd);
2064
-
2065
- return adjustedDate;
2066
- },
2067
-
2068
- /**
2069
- * fix the date if it is not within given options
2070
- * minDate and maxDate
2071
- */
2072
- _fixMinMaxDate: function(date) {
2073
- var minDate, maxDate;
2074
- date = this._cleanDate(date);
2075
-
2076
- // not less than minDate
2077
- if (this.options.minDate) {
2078
- minDate = this._cleanDate(this.options.minDate);
2079
- // midnight on minDate
2080
- minDate = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate());
2081
- if (date.getTime() < minDate.getTime()) {
2082
- this._trigger('reachedmindate', this.element, date);
2083
- }
2084
- date = this._cleanDate(Math.max(date.getTime(), minDate.getTime()));
2085
- }
2086
-
2087
- // not more than maxDate
2088
- if (this.options.maxDate) {
2089
- maxDate = this._cleanDate(this.options.maxDate);
2090
- // apply correction for max date if not startOnFirstDayOfWeek
2091
- // to make sure no further date is displayed.
2092
- // otherwise, the complement will still be shown
2093
- if (!this._startOnFirstDayOfWeek()) {
2094
- var day = maxDate.getDate() - this.options.daysToShow + 1;
2095
- maxDate = new Date(maxDate.getFullYear(), maxDate.getMonth(), day);
2096
- }
2097
- // microsecond before midnight on maxDate
2098
- maxDate = new Date(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate(), 23, 59, 59, 999);
2099
- if (date.getTime() > maxDate.getTime()) {
2100
- this._trigger('reachedmaxdate', this.element, date);
2101
- }
2102
- date = this._cleanDate(Math.min(date.getTime(), maxDate.getTime()));
2103
- }
2104
-
2105
- return date;
2106
- },
2107
-
2108
- /*
2109
- * gets the index of the current day adjusted based on options
2110
- */
2111
- _getAdjustedDayIndex: function(date) {
2112
- if (!this._startOnFirstDayOfWeek()) {
2113
- return 0;
2114
- }
2115
-
2116
- var midnightCurrentDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
2117
- var currentDayOfStandardWeek = midnightCurrentDate.getDay();
2118
- var days = [0, 1, 2, 3, 4, 5, 6];
2119
- this._rotate(days, this._firstDayOfWeek());
2120
- return days[currentDayOfStandardWeek];
2121
- },
2122
-
2123
- _firstDayOfWeek: function() {
2124
- if ($.isFunction(this.options.firstDayOfWeek)) {
2125
- return this.options.firstDayOfWeek(this.element);
2126
- }
2127
- return this.options.firstDayOfWeek;
2128
- },
2129
-
2130
- /*
2131
- * returns the date on the last millisecond of the week
2132
- */
2133
- _dateLastMilliOfWeek: function(date) {
2134
- var lastDayOfWeek = this._dateLastDayOfWeek(date);
2135
- lastDayOfWeek = this._cloneDate(lastDayOfWeek);
2136
- lastDayOfWeek.setDate(lastDayOfWeek.getDate() + 1);
2137
- return lastDayOfWeek;
2138
-
2139
- },
2140
-
2141
- /*
2142
- * Clear the time components of a date leaving the date
2143
- * of the first milli of day
2144
- */
2145
- _clearTime: function(d) {
2146
- d.setHours(0);
2147
- d.setMinutes(0);
2148
- d.setSeconds(0);
2149
- d.setMilliseconds(0);
2150
- return d;
2151
- },
2152
-
2153
- /*
2154
- * add specific number of days to date
2155
- */
2156
- _addDays: function(d, n, keepTime) {
2157
- d.setDate(d.getDate() + n);
2158
- if (keepTime) {
2159
- return d;
2160
- }
2161
- return this._clearTime(d);
2162
- },
2163
-
2164
- /*
2165
- * Rotate an array by specified number of places.
2166
- */
2167
- _rotate: function(a /*array*/, p /* integer, positive integer rotate to the right, negative to the left... */) {
2168
- for (var l = a.length, p = (Math.abs(p) >= l && (p %= l), p < 0 && (p += l), p), i, x; p; p = (Math.ceil(l / p) - 1) * p - l + (l = p)) {
2169
- for (i = l; i > p; x = a[--i], a[i] = a[i - p], a[i - p] = x) {}
2170
- }
2171
- return a;
2172
- },
2173
-
2174
- _cloneDate: function(d) {
2175
- return new Date(d.getTime());
2176
- },
2177
-
2178
- /**
2179
- * Return a Date instance for different representations.
2180
- * Valid representations are:
2181
- * * timestamps
2182
- * * Date objects
2183
- * * textual representations (only these accepted by the Date
2184
- * constructor)
2185
- *
2186
- * @return {Date} The clean date object.
2187
- */
2188
- _cleanDate: function(d) {
2189
- if (typeof d === 'string') {
2190
- // if is numeric
2191
- if (!isNaN(Number(d))) {
2192
- return this._cleanDate(parseInt(d, 10));
2193
- }
2194
-
2195
- // this is a human readable date
2196
- return Date.parse(d) || new Date(d);
2197
- }
2198
-
2199
- if (typeof d == 'number') {
2200
- return new Date(d);
2201
- }
2202
-
2203
- return d;
2204
- },
2205
-
2206
- /*
2207
- * date formatting is adapted from
2208
- * http://jacwright.com/projects/javascript/date_format
2209
- */
2210
- _formatDate: function(date, format) {
2211
- var returnStr = '';
2212
- for (var i = 0; i < format.length; i++) {
2213
- var curChar = format.charAt(i);
2214
- if (i != 0 && format.charAt(i - 1) == '\\') {
2215
- returnStr += curChar;
2216
- }
2217
- else if (this._replaceChars[curChar]) {
2218
- returnStr += this._replaceChars[curChar](date, this);
2219
- } else if (curChar != '\\') {
2220
- returnStr += curChar;
2221
- }
2222
- }
2223
- return returnStr;
2224
- },
2225
-
2226
- _replaceChars: {
2227
- // Day
2228
- d: function(date) { return (date.getDate() < 10 ? '0' : '') + date.getDate(); },
2229
- D: function(date, calendar) { return calendar.options.shortDays[date.getDay()]; },
2230
- j: function(date) { return date.getDate(); },
2231
- l: function(date, calendar) { return calendar.options.longDays[date.getDay()]; },
2232
- N: function(date) { var _d = date.getDay(); return _d ? _d : 7; },
2233
- S: function(date) { return (date.getDate() % 10 == 1 && date.getDate() != 11 ? 'st' : (date.getDate() % 10 == 2 && date.getDate() != 12 ? 'nd' : (date.getDate() % 10 == 3 && date.getDate() != 13 ? 'rd' : 'th'))); },
2234
- w: function(date) { return date.getDay(); },
2235
- z: function(date) { var d = new Date(date.getFullYear(), 0, 1); return Math.ceil((date - d) / 86400000); }, // Fixed now
2236
- // Week
2237
- W: function(date) { var d = new Date(date.getFullYear(), 0, 1); return Math.ceil((((date - d) / 86400000) + d.getDay() + 1) / 7); }, // Fixed now
2238
- // Month
2239
- F: function(date, calendar) { return calendar.options.longMonths[date.getMonth()]; },
2240
- m: function(date) { return (date.getMonth() < 9 ? '0' : '') + (date.getMonth() + 1); },
2241
- M: function(date, calendar) { return calendar.options.shortMonths[date.getMonth()]; },
2242
- n: function(date) { return date.getMonth() + 1; },
2243
- t: function(date) { var d = date; return new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate() }, // Fixed now, gets #days of date
2244
- // Year
2245
- L: function(date) { var year = date.getFullYear(); return (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)); }, // Fixed now
2246
- o: function(date) { var d = new Date(date.valueOf()); d.setDate(d.getDate() - ((date.getDay() + 6) % 7) + 3); return d.getFullYear();}, //Fixed now
2247
- Y: function(date) { return date.getFullYear(); },
2248
- y: function(date) { return ('' + date.getFullYear()).substr(2); },
2249
- // Time
2250
- a: function(date) { return date.getHours() < 12 ? BooklyL10n['AM'] : BooklyL10n['PM']; },
2251
- A: function(date) { return date.getHours() < 12 ? BooklyL10n['AM'] : BooklyL10n['PM']; },
2252
- B: function(date) { return Math.floor((((date.getUTCHours() + 1) % 24) + date.getUTCMinutes() / 60 + date.getUTCSeconds() / 3600) * 1000 / 24); }, // Fixed now
2253
- g: function(date) { return date.getHours() % 12 || 12; },
2254
- G: function(date) { return date.getHours(); },
2255
- h: function(date) { return ((date.getHours() % 12 || 12) < 10 ? '0' : '') + (date.getHours() % 12 || 12); },
2256
- H: function(date) { return (date.getHours() < 10 ? '0' : '') + date.getHours(); },
2257
- i: function(date) { return (date.getMinutes() < 10 ? '0' : '') + date.getMinutes(); },
2258
- s: function(date) { return (date.getSeconds() < 10 ? '0' : '') + date.getSeconds(); },
2259
- u: function(date) { var m = date.getMilliseconds(); return (m < 10 ? '00' : (m < 100 ? '0' : '')) + m; },
2260
- // Timezone
2261
- e: function(date) { return 'Not Yet Supported'; },
2262
- I: function(date) { return 'Not Yet Supported'; },
2263
- O: function(date) { return (-date.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(date.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(date.getTimezoneOffset() / 60)) + '00'; },
2264
- P: function(date) { return (-date.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(date.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(date.getTimezoneOffset() / 60)) + ':00'; }, // Fixed now
2265
- T: function(date) { var m = date.getMonth(); date.setMonth(0); var result = date.toTimeString().replace(/^.+ \(?([^\)]+)\)?$/, '$1'); date.setMonth(m); return result;},
2266
- Z: function(date) { return -date.getTimezoneOffset() * 60; },
2267
- // Full Date/Time
2268
- c: function(date, calendar) { return calendar._formatDate(date, 'Y-m-d\\TH:i:sP'); }, // Fixed now
2269
- r: function(date, calendar) { return calendar._formatDate(date, 'D, d M Y H:i:s O'); },
2270
- U: function(date) { return date.getTime() / 1000; }
2271
- },
2272
-
2273
- /* USER MANAGEMENT FUNCTIONS */
2274
-
2275
- getUserForId: function(id) {
2276
- return $.extend({}, this.options.users[this._getUserIndexFromId(id)]);
2277
- },
2278
-
2279
- /**
2280
- * return the user name for header
2281
- */
2282
- _getUserName: function(index) {
2283
- var self = this;
2284
- var options = this.options;
2285
- var user = options.users[index];
2286
- if ($.isFunction(options.getUserName)) {
2287
- return options.getUserName(user, index, self.element);
2288
- }
2289
- else {
2290
- return user;
2291
- }
2292
- },
2293
- /**
2294
- * return the user id for given index
2295
- */
2296
- _getUserIdFromIndex: function(index) {
2297
- var self = this;
2298
- var options = this.options;
2299
- if ($.isFunction(options.getUserId)) {
2300
- return options.getUserId(options.users[index], index, self.element);
2301
- }
2302
- return index;
2303
- },
2304
- /**
2305
- * returns the associated user index for given ID
2306
- */
2307
- _getUserIndexFromId: function(id) {
2308
- var self = this;
2309
- var options = this.options;
2310
- for (var i = 0; i < options.users.length; i++) {
2311
- if (self._getUserIdFromIndex(i) == id) {
2312
- return i;
2313
- }
2314
- }
2315
- return 0;
2316
- },
2317
- /**
2318
- * return the user ids for given calEvent.
2319
- * default is calEvent.userId field.
2320
- */
2321
- _getEventUserId: function(calEvent) {
2322
- var self = this;
2323
- var options = this.options;
2324
- if (options.showAsSeparateUsers && options.users && options.users.length) {
2325
- if ($.isFunction(options.getEventUserId)) {
2326
- return options.getEventUserId(calEvent, self.element);
2327
- }
2328
- return calEvent.userId;
2329
- }
2330
- return [];
2331
- },
2332
- /**
2333
- * sets the event user id on given calEvent
2334
- * default is calEvent.userId field.
2335
- */
2336
- _setEventUserId: function(calEvent, userId) {
2337
- var self = this;
2338
- var options = this.options;
2339
- if ($.isFunction(options.setEventUserId)) {
2340
- return options.setEventUserId(userId, calEvent, self.element);
2341
- }
2342
- calEvent.userId = userId;
2343
- return calEvent;
2344
- },
2345
- /**
2346
- * return the user ids for given freeBusy.
2347
- * default is freeBusy.userId field.
2348
- */
2349
- _getFreeBusyUserId: function(freeBusy) {
2350
- var self = this;
2351
- var options = this.options;
2352
- if ($.isFunction(options.getFreeBusyUserId)) {
2353
- return options.getFreeBusyUserId(freeBusy.getOption(), self.element);
2354
- }
2355
- return freeBusy.getOption('userId');
2356
- },
2357
-
2358
- /* FREEBUSY MANAGEMENT */
2359
-
2360
- /**
2361
- * ckean the free busy managers and remove all the freeBusy
2362
- */
2363
- _clearFreeBusys: function() {
2364
- if (this.options.displayFreeBusys) {
2365
- var self = this,
2366
- options = this.options,
2367
- $freeBusyPlaceholders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy');
2368
- $freeBusyPlaceholders.each(function() {
2369
- $(this).data('wcFreeBusyManager', new FreeBusyManager({
2370
- start: self._cloneDate($(this).data('startDate')),
2371
- end: self._cloneDate($(this).data('endDate')),
2372
- defaultFreeBusy: options.defaultFreeBusy || {}
2373
- }));
2374
- });
2375
- self.element.find('.wc-grid-row-freebusy .wc-freebusy').remove();
2376
- }
2377
- },
2378
- /**
2379
- * retrieve placeholders for given freebusy
2380
- */
2381
- _findWeekDaysForFreeBusy: function(freeBusy, $weekDays) {
2382
- var $returnWeekDays,
2383
- options = this.options,
2384
- showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
2385
- self = this,
2386
- userList = self._getFreeBusyUserId(freeBusy);
2387
- if (!$.isArray(userList)) {
2388
- userList = userList != 'undefined' ? [userList] : [];
2389
- }
2390
- if (!$weekDays) {
2391
- $weekDays = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy');
2392
- }
2393
- $weekDays.each(function() {
2394
- var manager = $(this).data('wcFreeBusyManager'),
2395
- has_overlap = manager.isWithin(freeBusy.getStart()) ||
2396
- manager.isWithin(freeBusy.getEnd()) ||
2397
- freeBusy.isWithin(manager.getStart()) ||
2398
- freeBusy.isWithin(manager.getEnd()),
2399
- userId = $(this).data('wcUserId');
2400
- if (has_overlap && (!showAsSeparatedUser || ($.inArray(userId, userList) != -1))) {
2401
- $returnWeekDays = $returnWeekDays ? $returnWeekDays.add($(this)) : $(this);
2402
- }
2403
- });
2404
- return $returnWeekDays;
2405
- },
2406
-
2407
- /**
2408
- * used to render all freeBusys
2409
- */
2410
- _renderFreeBusys: function(freeBusys) {
2411
- if (this.options.displayFreeBusys) {
2412
- var self = this,
2413
- $freeBusyPlaceholders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy'),
2414
- freebusysToRender;
2415
- //insert freebusys to dedicated placeholders freebusy managers
2416
- if ($.isArray(freeBusys)) {
2417
- freebusysToRender = self._cleanFreeBusys(freeBusys);
2418
- } else if (freeBusys.freebusys) {
2419
- freebusysToRender = self._cleanFreeBusys(freeBusys.freebusys);
2420
- }
2421
- else {
2422
- freebusysToRender = [];
2423
- }
2424
-
2425
- $.each(freebusysToRender, function(index, freebusy) {
2426
- var $placeholders = self._findWeekDaysForFreeBusy(freebusy, $freeBusyPlaceholders);
2427
- if ($placeholders) {
2428
- $placeholders.each(function() {
2429
- var manager = $(this).data('wcFreeBusyManager');
2430
- manager.insertFreeBusy(new FreeBusy(freebusy.getOption()));
2431
- $(this).data('wcFreeBusyManager', manager);
2432
- });
2433
- }
2434
- });
2435
-
2436
- //now display freebusys on place holders
2437
- self._refreshFreeBusys($freeBusyPlaceholders);
2438
- }
2439
- },
2440
- /**
2441
- * refresh freebusys for given placeholders
2442
- */
2443
- _refreshFreeBusys: function($freeBusyPlaceholders) {
2444
- if (this.options.displayFreeBusys && $freeBusyPlaceholders) {
2445
- var self = this,
2446
- options = this.options,
2447
- start = (options.businessHours.limitDisplay ? options.businessHours.start : 0),
2448
- end = (options.businessHours.limitDisplay ? options.businessHours.end : 24);
2449
-
2450
- $freeBusyPlaceholders.each(function() {
2451
- var $placehoder = $(this);
2452
- var s = self._cloneDate($placehoder.data('startDate')),
2453
- e = self._cloneDate(s);
2454
- s.setHours(start);
2455
- e.setHours(end);
2456
- $placehoder.find('.wc-freebusy').remove();
2457
- $.each($placehoder.data('wcFreeBusyManager').getFreeBusys(s, e), function() {
2458
- self._renderFreeBusy(this, $placehoder);
2459
- });
2460
- });
2461
- }
2462
- },
2463
- /**
2464
- * render a freebusy item on dedicated placeholders
2465
- */
2466
- _renderFreeBusy: function(freeBusy, $freeBusyPlaceholder) {
2467
- if (this.options.displayFreeBusys) {
2468
- var self = this,
2469
- options = this.options,
2470
- freeBusyHtml = '<div class="wc-freebusy"></div>';
2471
-
2472
- var $fb = $(freeBusyHtml);
2473
- $fb.data('wcFreeBusy', new FreeBusy(freeBusy.getOption()));
2474
- this._positionFreeBusy($freeBusyPlaceholder, $fb);
2475
- $fb = options.freeBusyRender(freeBusy.getOption(), $fb, self.element);
2476
- if ($fb) {
2477
- $fb.appendTo($freeBusyPlaceholder);
2478
- }
2479
- }
2480
- },
2481
- /*
2482
- * Position the freebusy element within the weekday based on it's start / end dates.
2483
- */
2484
- _positionFreeBusy: function($placeholder, $freeBusy) {
2485
- var options = this.options;
2486
- var freeBusy = $freeBusy.data('wcFreeBusy');
2487
- var pxPerMillis = $placeholder.height() / options.millisToDisplay;
2488
- var firstHourDisplayed = options.businessHours.limitDisplay ? options.businessHours.start : 0;
2489
- var startMillis = freeBusy.getStart().getTime() - new Date(freeBusy.getStart().getFullYear(), freeBusy.getStart().getMonth(), freeBusy.getStart().getDate(), firstHourDisplayed).getTime();
2490
- var eventMillis = freeBusy.getEnd().getTime() - freeBusy.getStart().getTime();
2491
- var pxTop = pxPerMillis * startMillis;
2492
- var pxHeight = pxPerMillis * eventMillis;
2493
- $freeBusy.css({top: pxTop, height: pxHeight});
2494
- },
2495
- /*
2496
- * Clean freebusys to ensure correct format
2497
- */
2498
- _cleanFreeBusys: function(freebusys) {
2499
- var self = this,
2500
- freeBusyToReturn = [];
2501
- if (!$.isArray(freebusys)) {
2502
- var freebusys = [freebusys];
2503
- }
2504
- $.each(freebusys, function(i, freebusy) {
2505
- freeBusyToReturn.push(new FreeBusy(self._cleanFreeBusy(freebusy)));
2506
- });
2507
- return freeBusyToReturn;
2508
- },
2509
-
2510
- /*
2511
- * Clean specific freebusy
2512
- */
2513
- _cleanFreeBusy: function(freebusy) {
2514
- if (freebusy.date) {
2515
- freebusy.start = freebusy.date;
2516
- }
2517
- freebusy.start = this._cleanDate(freebusy.start);
2518
- freebusy.end = this._cleanDate(freebusy.end);
2519
- return freebusy;
2520
- },
2521
-
2522
- /**
2523
- * retrives the first freebusy manager matching demand.
2524
- */
2525
- getFreeBusyManagersFor: function(date, users) {
2526
- var calEvent = {
2527
- start: date,
2528
- end: date
2529
- };
2530
- this._setEventUserId(calEvent, users);
2531
- return this.getFreeBusyManagerForEvent(calEvent);
2532
- },
2533
- /**
2534
- * retrives the first freebusy manager for given event.
2535
- */
2536
- getFreeBusyManagerForEvent: function(newCalEvent) {
2537
- var self = this,
2538
- options = this.options,
2539
- freeBusyManager;
2540
- if (options.displayFreeBusys) {
2541
- var $freeBusyPlaceHoders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy'),
2542
- freeBusy = new FreeBusy({start: newCalEvent.start, end: newCalEvent.end}),
2543
- showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
2544
- userId = showAsSeparatedUser ? self._getEventUserId(newCalEvent) : null;
2545
- if (!$.isArray(userId)) {
2546
- userId = [userId];
2547
- }
2548
- $freeBusyPlaceHoders.each(function() {
2549
- var manager = $(this).data('wcFreeBusyManager'),
2550
- has_overlap = manager.isWithin(freeBusy.getEnd()) ||
2551
- manager.isWithin(freeBusy.getEnd()) ||
2552
- freeBusy.isWithin(manager.getStart()) ||
2553
- freeBusy.isWithin(manager.getEnd());
2554
- if (has_overlap && (!showAsSeparatedUser || $.inArray($(this).data('wcUserId'), userId) != -1)) {
2555
- freeBusyManager = $(this).data('wcFreeBusyManager');
2556
- return false;
2557
- }
2558
- });
2559
- }
2560
- return freeBusyManager;
2561
- },
2562
- /**
2563
- * appends the freebusys to replace the old ones.
2564
- * @param {array|object} freeBusys freebusy(s) to apply.
2565
- */
2566
- updateFreeBusy: function(freeBusys) {
2567
- var self = this,
2568
- options = this.options;
2569
- if (options.displayFreeBusys) {
2570
- var $toRender,
2571
- $freeBusyPlaceHoders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy'),
2572
- _freeBusys = self._cleanFreeBusys(freeBusys);
2573
-
2574
- $.each(_freeBusys, function(index, _freeBusy) {
2575
-
2576
- var $weekdays = self._findWeekDaysForFreeBusy(_freeBusy, $freeBusyPlaceHoders);
2577
- //if freebusy has a placeholder
2578
- if ($weekdays && $weekdays.length) {
2579
- $weekdays.each(function(index, day) {
2580
- var manager = $(day).data('wcFreeBusyManager');
2581
- manager.insertFreeBusy(_freeBusy);
2582
- $(day).data('wcFreeBusyManager', manager);
2583
- });
2584
- $toRender = $toRender ? $toRender.add($weekdays) : $weekdays;
2585
- }
2586
- });
2587
- self._refreshFreeBusys($toRender);
2588
- }
2589
- },
2590
-
2591
- /* NEW OPTIONS MANAGEMENT */
2592
-
2593
- /**
2594
- * checks wether or not the calendar should be displayed starting on first day of week
2595
- */
2596
- _startOnFirstDayOfWeek: function() {
2597
- return jQuery.isFunction(this.options.startOnFirstDayOfWeek) ? this.options.startOnFirstDayOfWeek(this.element) : this.options.startOnFirstDayOfWeek;
2598
- },
2599
-
2600
- /**
2601
- * finds out the current scroll to apply it when changing the view
2602
- */
2603
- _getCurrentScrollHour: function() {
2604
- var self = this;
2605
- var options = this.options;
2606
- var $scrollable = this.element.find('.wc-scrollable-grid');
2607
- var scroll = $scrollable.scrollTop();
2608
- if (self.options.businessHours.limitDisplay) {
2609
- scroll = scroll + options.businessHours.start * options.timeslotHeight * options.timeslotsPerHour;
2610
- }
2611
- return Math.round(scroll / (options.timeslotHeight * options.timeslotsPerHour)) + 1;
2612
- },
2613
- _getJsonOptions: function() {
2614
- if ($.isFunction(this.options.jsonOptions)) {
2615
- return $.extend({}, this.options.jsonOptions(this.element));
2616
- }
2617
- if ($.isPlainObject(this.options.jsonOptions)) {
2618
- return $.extend({}, this.options.jsonOptions);
2619
- }
2620
- return {};
2621
- },
2622
- _getHeaderDate: function(date) {
2623
- var options = this.options;
2624
- if (options.getHeaderDate && $.isFunction(options.getHeaderDate))
2625
- {
2626
- return options.getHeaderDate(date, this.element);
2627
- }
2628
- var dayName = options.useShortDayNames ? options.shortDays[date.getDay()] : options.longDays[date.getDay()];
2629
- return dayName + (options.headerSeparator) + this._formatDate(date, options.dateFormat);
2630
- },
2631
-
2632
-
2633
-
2634
- /**
2635
- * returns corrected date related to DST problem
2636
- */
2637
- _getDSTdayShift: function(date, shift) {
2638
- var start = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0);
2639
- var offset1 = start.getTimezoneOffset();
2640
- var offset2 = date.getTimezoneOffset();
2641
- if (offset1 == offset2)
2642
- return date;
2643
- shift = shift ? shift : 1;
2644
- return new Date(date.getTime() - shift * (offset1 > offset2 ? -1 : 1) * (Math.max(offset1, offset2) - Math.min(offset1, offset2)) * 60000);
2645
- },
2646
- _needDSTdayShift: function(date1, date2) {
2647
- return date1.getTimezoneOffset() != date2.getTimezoneOffset();
2648
- }
2649
-
2650
-
2651
-
2652
- }; // end of widget function return
2653
- })() //end of widget function closure execution
2654
- ); // end of $.widget("ui.weekCalendar"...
2655
-
2656
- $.extend($.ui.weekCalendar, {
2657
- version: '2.0-dev',
2658
- updateLayoutOptions: {
2659
- startOnFirstDayOfWeek: true,
2660
- firstDayOfWeek: true,
2661
- daysToShow: true,
2662
- displayOddEven: true,
2663
- timeFormat: true,
2664
- dateFormat: true,
2665
- use24Hour: true,
2666
- useShortDayNames: true,
2667
- businessHours: true,
2668
- timeslotHeight: true,
2669
- timeslotsPerHour: true,
2670
- buttonText: true,
2671
- height: true,
2672
- shortMonths: true,
2673
- longMonths: true,
2674
- shortDays: true,
2675
- longDays: true,
2676
- textSize: true,
2677
- users: true,
2678
- showAsSeparateUsers: true,
2679
- displayFreeBusys: true
2680
- }
2681
- });
2682
-
2683
- var MILLIS_IN_DAY = 86400000;
2684
- var MILLIS_IN_WEEK = MILLIS_IN_DAY * 7;
2685
-
2686
- /* FREE BUSY MANAGERS */
2687
- var FreeBusyProto = {
2688
- getStart: function() {return this.getOption('start')},
2689
- getEnd: function() {return this.getOption('end')},
2690
- getOption: function() {
2691
- if (!arguments.length) { return this.options }
2692
- if (typeof(this.options[arguments[0]]) !== 'undefined') {
2693
- return this.options[arguments[0]];
2694
- }
2695
- else if (typeof(arguments[1]) !== 'undefined') {
2696
- return arguments[1];
2697
- }
2698
- return null;
2699
- },
2700
- setOption: function(key, value) {
2701
- if (arguments.length == 1) {
2702
- $.extend(this.options, arguments[0]);
2703
- return this;
2704
- }
2705
- this.options[key] = value;
2706
- return this;
2707
- },
2708
- isWithin: function(dateTime) {return Math.floor(dateTime.getTime() / 1000) >= Math.floor(this.getStart().getTime() / 1000) && Math.floor(dateTime.getTime() / 1000) <= Math.floor(this.getEnd().getTime() / 1000)},
2709
- isValid: function() {return this.getStart().getTime() < this.getEnd().getTime()}
2710
- };
2711
-
2712
- /**
2713
- * @constructor
2714
- * single user freebusy manager.
2715
- */
2716
- var FreeBusy = function(options) {
2717
- this.options = $.extend({}, options || {});
2718
- };
2719
- $.extend(FreeBusy.prototype, FreeBusyProto);
2720
-
2721
- var FreeBusyManager = function(options) {
2722
- this.options = $.extend({
2723
- defaultFreeBusy: {}
2724
- }, options || {});
2725
- this.freeBusys = [];
2726
- this.freeBusys.push(new FreeBusy($.extend({
2727
- start: this.getStart(),
2728
- end: this.getEnd()
2729
- }, this.options.defaultFreeBusy)));
2730
- };
2731
- $.extend(FreeBusyManager.prototype, FreeBusyProto, {
2732
- /**
2733
- * return matching freeBusys.
2734
- * if you do not pass any argument, returns all freebusys.
2735
- * if you only pass a start date, only matchinf freebusy will be returned.
2736
- * if you pass 2 arguments, then all freebusys available within the time period will be returned
2737
- * @param {Date} start [optionnal] if you do not pass end date, will return the freeBusy within which this date falls.
2738
- * @param {Date} end [optionnal] the date where to stop the search.
2739
- * @return {Array} an array of FreeBusy matching arguments.
2740
- */
2741
- getFreeBusys: function() {
2742
- switch (arguments.length) {
2743
- case 0:
2744
- return this.freeBusys;
2745
- case 1:
2746
- var freeBusy = [];
2747
- var start = arguments[0];
2748
- if (!this.isWithin(start)) {
2749
- return freeBusy;
2750
- }
2751
- $.each(this.freeBusys, function() {
2752
- if (this.isWithin(start)) {
2753
- freeBusy.push(this);
2754
- }
2755
- if (Math.floor(this.getEnd().getTime() / 1000) > Math.floor(start.getTime() / 1000)) {
2756
- return false;
2757
- }
2758
- });
2759
- return freeBusy;
2760
- default:
2761
- //we assume only 2 first args are revealants
2762
- var freeBusy = [];
2763
- var start = arguments[0], end = arguments[1];
2764
- var tmpFreeBusy = new FreeBusy({start: start, end: end});
2765
- if (end.getTime() < start.getTime() || this.getStart().getTime() > end.getTime() || this.getEnd().getTime() < start.getTime()) {
2766
- return freeBusy;
2767
- }
2768
- $.each(this.freeBusys, function() {
2769
- if (this.getStart().getTime() >= end.getTime()) {
2770
- return false;
2771
- }
2772
- if (tmpFreeBusy.isWithin(this.getStart()) && tmpFreeBusy.isWithin(this.getEnd())) {
2773
- freeBusy.push(this);
2774
- }
2775
- else if (this.isWithin(tmpFreeBusy.getStart()) && this.isWithin(tmpFreeBusy.getEnd())) {
2776
- var _f = new FreeBusy(this.getOption());
2777
- _f.setOption('end', tmpFreeBusy.getEnd());
2778
- _f.setOption('start', tmpFreeBusy.getStart());
2779
- freeBusy.push(_f);
2780
- }
2781
- else if (this.isWithin(tmpFreeBusy.getStart()) && this.getStart().getTime() < start.getTime()) {
2782
- var _f = new FreeBusy(this.getOption());
2783
- _f.setOption('start', tmpFreeBusy.getStart());
2784
- freeBusy.push(_f);
2785
- }
2786
- else if (this.isWithin(tmpFreeBusy.getEnd()) && this.getEnd().getTime() > end.getTime()) {
2787
- var _f = new FreeBusy(this.getOption());
2788
- _f.setOption('end', tmpFreeBusy.getEnd());
2789
- freeBusy.push(_f);
2790
- }
2791
- });
2792
- return freeBusy;
2793
- }
2794
- },
2795
- insertFreeBusy: function(freeBusy) {
2796
- var freeBusy = new FreeBusy(freeBusy.getOption());
2797
- //first, if inserted freebusy is bigger than manager
2798
- if (freeBusy.getStart().getTime() < this.getStart().getTime()) {
2799
- freeBusy.setOption('start', this.getStart());
2800
- }
2801
- if (freeBusy.getEnd().getTime() > this.getEnd().getTime()) {
2802
- freeBusy.setOption('end', this.getEnd());
2803
- }
2804
- var start = freeBusy.getStart(), end = freeBusy.getEnd(),
2805
- startIndex = 0, endIndex = this.freeBusys.length - 1,
2806
- newFreeBusys = [];
2807
- var pushNewFreeBusy = function(_f) {if (_f.isValid()) newFreeBusys.push(_f);};
2808
-
2809
- $.each(this.freeBusys, function(index) {
2810
- //within the loop, we have following vars:
2811
- // curFreeBusyItem: the current iteration freeBusy, part of manager freeBusys list
2812
- // start: the insterted freeBusy start
2813
- // end: the inserted freebusy end
2814
- var curFreeBusyItem = this;
2815
- if (curFreeBusyItem.isWithin(start) && curFreeBusyItem.isWithin(end)) {
2816
- /*
2817
- we are in case where inserted freebusy fits in curFreeBusyItem:
2818
- curFreeBusyItem: *-----------------------------*
2819
- freeBusy: *-------------*
2820
- obviously, start and end indexes are this item.
2821
- */
2822
- startIndex = index;
2823
- endIndex = index;
2824
- if (start.getTime() == curFreeBusyItem.getStart().getTime() && end.getTime() == curFreeBusyItem.getEnd().getTime()) {
2825
- /*
2826
- in this case, inserted freebusy is exactly curFreeBusyItem:
2827
- curFreeBusyItem: *-----------------------------*
2828
- freeBusy: *-----------------------------*
2829
-
2830
- just replace curFreeBusyItem with freeBusy.
2831
- */
2832
- var _f1 = new FreeBusy(freeBusy.getOption());
2833
- pushNewFreeBusy(_f1);
2834
- }
2835
- else if (start.getTime() == curFreeBusyItem.getStart().getTime()) {
2836
- /*
2837
- in this case inserted freebusy starts with curFreeBusyItem:
2838
- curFreeBusyItem: *-----------------------------*
2839
- freeBusy: *--------------*
2840
-
2841
- just replace curFreeBusyItem with freeBusy AND the rest.
2842
- */
2843
- var _f1 = new FreeBusy(freeBusy.getOption());
2844
- var _f2 = new FreeBusy(curFreeBusyItem.getOption());
2845
- _f2.setOption('start', end);
2846
- pushNewFreeBusy(_f1);
2847
- pushNewFreeBusy(_f2);
2848
- }
2849
- else if (end.getTime() == curFreeBusyItem.getEnd().getTime()) {
2850
- /*
2851
- in this case inserted freebusy ends with curFreeBusyItem:
2852
- curFreeBusyItem: *-----------------------------*
2853
- freeBusy: *--------------*
2854
-
2855
- just replace curFreeBusyItem with before part AND freeBusy.
2856
- */
2857
- var _f1 = new FreeBusy(curFreeBusyItem.getOption());
2858
- _f1.setOption('end', start);
2859
- var _f2 = new FreeBusy(freeBusy.getOption());
2860
- pushNewFreeBusy(_f1);
2861
- pushNewFreeBusy(_f2);
2862
- }
2863
- else {
2864
- /*
2865
- in this case inserted freebusy is within curFreeBusyItem:
2866
- curFreeBusyItem: *-----------------------------*
2867
- freeBusy: *--------------*
2868
-
2869
- just replace curFreeBusyItem with before part AND freeBusy AND the rest.
2870
- */
2871
- var _f1 = new FreeBusy(curFreeBusyItem.getOption());
2872
- var _f2 = new FreeBusy(freeBusy.getOption());
2873
- var _f3 = new FreeBusy(curFreeBusyItem.getOption());
2874
- _f1.setOption('end', start);
2875
- _f3.setOption('start', end);
2876
- pushNewFreeBusy(_f1);
2877
- pushNewFreeBusy(_f2);
2878
- pushNewFreeBusy(_f3);
2879
- }
2880
- /*
2881
- as work is done, no need to go further.
2882
- return false
2883
- */
2884
- return false;
2885
- }
2886
- else if (curFreeBusyItem.isWithin(start) && curFreeBusyItem.getEnd().getTime() != start.getTime()) {
2887
- /*
2888
- in this case, inserted freebusy starts within curFreeBusyItem:
2889
- curFreeBusyItem: *----------*
2890
- freeBusy: *-------------------*
2891
-
2892
- set start index AND insert before part, we'll insert freebusy later
2893
- */
2894
- if (curFreeBusyItem.getStart().getTime() != start.getTime()) {
2895
- var _f1 = new FreeBusy(curFreeBusyItem.getOption());
2896
- _f1.setOption('end', start);
2897
- pushNewFreeBusy(_f1);
2898
- }
2899
- startIndex = index;
2900
- }
2901
- else if (curFreeBusyItem.isWithin(end) && curFreeBusyItem.getStart().getTime() != end.getTime()) {
2902
- /*
2903
- in this case, inserted freebusy starts within curFreeBusyItem:
2904
- curFreeBusyItem: *----------*
2905
- freeBusy: *-------------------*
2906
-
2907
- set end index AND insert freebusy AND insert after part if needed
2908
- */
2909
- pushNewFreeBusy(new FreeBusy(freeBusy.getOption()));
2910
- if (end.getTime() < curFreeBusyItem.getEnd().getTime()) {
2911
- var _f1 = new FreeBusy(curFreeBusyItem.getOption());
2912
- _f1.setOption('start', end);
2913
- pushNewFreeBusy(_f1);
2914
- }
2915
- endIndex = index;
2916
- return false;
2917
- }
2918
- });
2919
- //now compute arguments
2920
- var tmpFB = this.freeBusys;
2921
- this.freeBusys = [];
2922
-
2923
- if (startIndex) {
2924
- this.freeBusys = this.freeBusys.concat(tmpFB.slice(0, startIndex));
2925
- }
2926
- this.freeBusys = this.freeBusys.concat(newFreeBusys);
2927
- if (endIndex < tmpFB.length) {
2928
- this.freeBusys = this.freeBusys.concat(tmpFB.slice(endIndex + 1));
2929
- }
2930
- /* if(start.getDate() == 1){
2931
- console.info('insert from '+freeBusy.getStart() +' to '+freeBusy.getEnd());
2932
- console.log('index from '+ startIndex + ' to ' + endIndex);
2933
- var str = [];
2934
- $.each(tmpFB, function(i){str.push(i + ": " + this.getStart().getHours() + ' > ' + this.getEnd().getHours() + ' ' + (this.getOption('free') ? 'free' : 'busy'))});
2935
- console.log(str.join('\n'));
2936
-
2937
- console.log('insert');
2938
- var str = [];
2939
- $.each(newFreeBusys, function(i){str.push(this.getStart().getHours() + ' > ' + this.getEnd().getHours())});
2940
- console.log(str.join(', '));
2941
-
2942
- console.log('results');
2943
- var str = [];
2944
- $.each(this.freeBusys, function(i){str.push(i + ": " + this.getStart().getHours() + ' > ' + this.getEnd().getHours() + ' ' + (this.getOption('free') ? 'free' :'busy'))});
2945
- console.log(str.join('\n'));
2946
- }*/
2947
- return this;
2948
- }
2949
- });
2950
-
2951
- })(jQuery);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/resources/js/ng-app.js DELETED
@@ -1,379 +0,0 @@
1
- ;(function() {
2
-
3
- var module = angular.module('appointmentForm', ['ui', '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
- time : [],
14
- time_interval : 900
15
- },
16
- form : {
17
- id : null,
18
- staff : null,
19
- service : null,
20
- date : null,
21
- start_time : null,
22
- end_time : null,
23
- customer : null,
24
- notes : null
25
- },
26
- loadData : function() {
27
- var deferred = $q.defer();
28
- jQuery.get(
29
- ajaxurl,
30
- { action : 'ab_get_data_for_appointment_form' },
31
- function(data) {
32
- ds.data = data;
33
- if (data.staff.length) {
34
- ds.form.staff = data.staff[0];
35
- }
36
- ds.form.start_time = data.time[0];
37
- ds.form.end_time = data.time[1];
38
- $rootScope.$apply(deferred.resolve);
39
- },
40
- 'json'
41
- );
42
- return deferred.promise;
43
- },
44
- findStaff : function(id) {
45
- var result = null;
46
- jQuery.each(ds.data.staff, function(key, item) {
47
- if (item.id == id) {
48
- result = item;
49
- return false;
50
- }
51
- });
52
- return result;
53
- },
54
- findService : function(staff_id, id) {
55
- var result = null;
56
- var staff = ds.findStaff(staff_id);
57
- if (staff !== null) {
58
- jQuery.each(staff.services, function(key, item) {
59
- if (item.id == id) {
60
- result = item;
61
- return false;
62
- }
63
- });
64
- }
65
- return result;
66
- },
67
- findTime : function(date) {
68
- var result = null;
69
- var value_to_find = $filter('date')(date, 'HH:mm');
70
- jQuery.each(ds.data.time, function(key, item) {
71
- if (item.value === value_to_find) {
72
- result = item;
73
- return false;
74
- }
75
- });
76
- return result;
77
- },
78
- findCustomer : function(id) {
79
- var result = null;
80
- jQuery.each(ds.data.customers, function(key, item) {
81
- if (item.id == id) {
82
- result = item;
83
- return false;
84
- }
85
- });
86
- return result;
87
- },
88
- getDataForEndTime : function() {
89
- var result = [];
90
- jQuery.each(ds.data.time, function(key, item) {
91
- if (
92
- ds.form.start_time === null ||
93
- item.value > ds.form.start_time.value
94
- ) {
95
- result.push(item);
96
- }
97
- });
98
- return result;
99
- },
100
- setEndTimeBasedOnService : function() {
101
- var i = jQuery.inArray(ds.form.start_time, ds.data.time);
102
- var d = ds.form.service ? ds.form.service.duration : ds.data.time_interval;
103
- if (i !== -1) {
104
- for (; i < ds.data.time.length; ++ i) {
105
- d -= ds.data.time_interval;
106
- if (d < 0) {
107
- break;
108
- }
109
- }
110
- ds.form.end_time = ds.data.time[i];
111
- }
112
- },
113
- getStartAndEndDates : function() {
114
- var date = $filter('date')(ds.form.date, 'yyyy-MM-dd');
115
- return {
116
- start_date : date + ' ' + ds.form.start_time.value,
117
- end_date : date + ' ' + ds.form.end_time.value
118
- };
119
- }
120
- };
121
-
122
- return ds;
123
- });
124
-
125
- /**
126
- * Controller for "create/edit appointment" dialog form.
127
- */
128
- module.controller('appointmentDialogCtrl', function($scope, $element, dataSource) {
129
- // Set up initial data.
130
- $scope.loading = true;
131
- $scope.$week_calendar = null;
132
- $scope.calendar_mode = 'week';
133
- // Set up data source.
134
- $scope.dataSource = dataSource;
135
- $scope.form = dataSource.form; // shortcut
136
- // Populate data source.
137
- dataSource.loadData().then(function() {
138
- $scope.loading = false;
139
- });
140
- // Error messages.
141
- $scope.errors = {};
142
- // Id of the staff whos events are currently being edited/created.
143
- var current_staff_id = null;
144
-
145
- /**
146
- * Prepare the form for new event.
147
- *
148
- * @param Date start_date
149
- */
150
- $scope.configureNewForm = function(staff_id, start_date) {
151
- jQuery.extend($scope.form, {
152
- id : null,
153
- staff : dataSource.findStaff(staff_id),
154
- service : null,
155
- date : start_date,
156
- start_time : dataSource.findTime(start_date),
157
- end_time : null,
158
- customer : null,
159
- notes : null
160
- });
161
- $scope.errors = {};
162
- dataSource.setEndTimeBasedOnService();
163
- current_staff_id = staff_id;
164
- };
165
-
166
- /**
167
- * Prepare the form for editing event.
168
- */
169
- $scope.configureEditForm = function(appointment_id, staff_id, start_date, end_date) {
170
- $scope.loading = true;
171
- jQuery.post(
172
- ajaxurl,
173
- { action : 'ab_get_data_for_appointment', id : appointment_id },
174
- function(response) {
175
- $scope.$apply(function($scope) {
176
- if (response.status === 'ok') {
177
- jQuery.extend($scope.form, {
178
- id : appointment_id,
179
- staff : $scope.dataSource.findStaff(staff_id),
180
- service : $scope.dataSource.findService(staff_id, response.data.service_id),
181
- date : start_date,
182
- start_time : $scope.dataSource.findTime(start_date),
183
- end_time : $scope.dataSource.findTime(end_date),
184
- customer : $scope.dataSource.findCustomer(response.data.customer_id),
185
- notes : response.data.notes
186
- });
187
- }
188
- $scope.loading = false;
189
- });
190
- },
191
- 'json'
192
- );
193
- $scope.errors = {};
194
- current_staff_id = staff_id;
195
- };
196
-
197
- var checkTimeInterval = function() {
198
- var dates = $scope.dataSource.getStartAndEndDates();
199
- jQuery.get(
200
- ajaxurl,
201
- {
202
- action : 'ab_check_appointment_date_selection',
203
- start_date : dates.start_date,
204
- end_date : dates.end_date,
205
- appointment_id : $scope.form.id,
206
- staff_id : $scope.form.staff ? $scope.form.staff.id : null,
207
- service_id : $scope.form.service ? $scope.form.service.id : null
208
- },
209
- function(response){
210
- $scope.$apply(function($scope) {
211
- $scope.errors = response;
212
- });
213
- },
214
- 'json'
215
- );
216
- };
217
-
218
- $scope.onServiceChange = function() {
219
- $scope.dataSource.setEndTimeBasedOnService();
220
- checkTimeInterval();
221
- };
222
-
223
- $scope.onStartTimeChange = function() {
224
- $scope.dataSource.setEndTimeBasedOnService();
225
- checkTimeInterval();
226
- };
227
-
228
- $scope.onEndTimeChange = function() {
229
- checkTimeInterval();
230
- };
231
-
232
- $scope.processForm = function() {
233
- $scope.loading = true;
234
- var dates = $scope.dataSource.getStartAndEndDates();
235
- jQuery.post(
236
- ajaxurl,
237
- {
238
- action : 'ab_save_appointment_form',
239
- id : $scope.form.id,
240
- staff_id : $scope.form.staff ? $scope.form.staff.id : null,
241
- service_id : $scope.form.service ? $scope.form.service.id : null,
242
- start_date : dates.start_date,
243
- end_date : dates.end_date,
244
- customer_id : $scope.form.customer ? $scope.form.customer.id : null,
245
- notes : $scope.form.notes
246
- },
247
- function (response) {
248
- $scope.$apply(function($scope) {
249
- if (response.status === 'ok') {
250
- if ($scope.$week_calendar) {
251
- if ($scope.calendar_mode === 'day' || current_staff_id === response.data.userId) {
252
- // Update/create event in current calendar when:
253
- // - current view mode is "day"
254
- // OR
255
- // - ID of event owner matches the ID of active staff ("week" mode)
256
- $scope.$week_calendar.weekCalendar('updateEvent', response.data);
257
- } else {
258
- // Else switch to the event owner tab ("week" mode).
259
- jQuery('li.ab-staff-tab-' + response.data.userId).click();
260
- }
261
- }
262
- // Close the dialog.
263
- $element.dialog('close');
264
- } else {
265
- $scope.errors = response.errors;
266
- }
267
- $scope.loading = false;
268
- });
269
- },
270
- 'json'
271
- );
272
- };
273
-
274
- // On 'Cancel' button click.
275
- $scope.closeDialog = function() {
276
- // Close the dialog.
277
- $element.dialog('close');
278
- };
279
-
280
- $scope.createCustomer = function(customer) {
281
- // Add new customer to the list.
282
- n_customer = {id : customer.id, name : customer.name};
283
- if (customer.email && customer.phone){
284
- n_customer.name += ' (' + customer.email + ', ' + customer.phone + ')';
285
- }else if(customer.phone){
286
- n_customer.name += ' (' + customer.phone + ')';
287
- }else if(customer.email){
288
- n_customer.name += ' (' + customer.email + ')';
289
- }
290
-
291
- dataSource.data.customers.push(n_customer);
292
- // Make it selected.
293
- dataSource.form.customer = n_customer;
294
- };
295
-
296
- $scope.dateOptions = {
297
- dateFormat : 'M, dd yy',
298
- dayNamesMin : BooklyL10n['shortDays'],
299
- monthNames : BooklyL10n['longMonths'],
300
- monthNamesShort : BooklyL10n['shortMonths']
301
- };
302
- });
303
-
304
- /**
305
- * Directive for slide up/down.
306
- */
307
- module.directive('mySlideUp', function() {
308
- return function(scope, element, attrs) {
309
- element.hide();
310
- // watch the expression, and update the UI on change.
311
- scope.$watch(attrs.mySlideUp, function(value) {
312
- if (value) {
313
- element.delay(0).slideDown();
314
- } else {
315
- element.slideUp();
316
- }
317
- });
318
- };
319
- });
320
-
321
- module.directive('chosen',function(){
322
- var linker = function(scope,element,attrs) {
323
- scope.$watch(attrs['chosen'], function() {
324
- element.trigger("chosen:updated");
325
- });
326
-
327
- scope.$watch(attrs['ngModel'], function() {
328
- element.trigger("chosen:updated");
329
- });
330
-
331
- element.chosen({
332
- search_contains : true,
333
- width : '400px'
334
- });
335
- };
336
-
337
- return {
338
- restrict:'A',
339
- link: linker
340
- }
341
- });
342
-
343
- /**
344
- * Directive for Popover jQuery plugin.
345
- */
346
- module.directive('popover', function() {
347
- return function(scope, element, attrs) {
348
- element.popover({
349
- trigger : 'hover',
350
- content : attrs.popover,
351
- html : true
352
- });
353
- };
354
- });
355
-
356
- })();
357
-
358
- var showAppointmentDialog = function(appointment_id, staff_id, start_date, end_date, calendar, mode) {
359
- var $scope = angular.element(document.getElementById('ab_appointment_dialog')).scope();
360
- var title = null;
361
- $scope.$apply(function($scope){
362
- $scope.$week_calendar = calendar;
363
- $scope.calendar_mode = mode;
364
- if (appointment_id) {
365
- $scope.configureEditForm(appointment_id, staff_id, start_date, end_date);
366
- title = BooklyL10n['edit_appointment'];
367
- } else {
368
- $scope.configureNewForm(staff_id, start_date);
369
- title = BooklyL10n['new_appointment'];
370
- }
371
- });
372
- jQuery('#ab_appointment_dialog').dialog({
373
- width: 700,
374
- position: ['center', 150],
375
- modal: true,
376
- dialogClass: 'ab-appointment-popup',
377
- title: title
378
- });
379
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/templates/_appointment_form.php ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/appointment_form.php DELETED
@@ -1,93 +0,0 @@
1
- <div ng-controller=appointmentDialogCtrl id=ab_appointment_dialog style="display: none">
2
-
3
- <div ng-hide=loading class=dialog-content>
4
- <form ng-submit=processForm() class=form-horizontal>
5
-
6
- <div class=control-group>
7
- <label class=control-label><?php _e('Provider', 'ab') ?></label>
8
- <div class=controls>
9
- <select class="field" ng-model=form.staff ng-options="s.full_name for s in dataSource.data.staff"></select>
10
- </div>
11
- </div>
12
-
13
- <div class=control-group>
14
- <label class=control-label><?php _e('Service', 'ab') ?></label>
15
- <div class=controls>
16
- <select class="field" ng-model=form.service ng-options="s.title for s in form.staff.services" ng-change=onServiceChange()>
17
- <option value=""><?php _e('-- Select a service --', 'ab') ?></option>
18
- </select>
19
- </div>
20
- </div>
21
-
22
- <div class=control-group>
23
- <label class=control-label><?php _e('Date', 'ab') ?></label>
24
- <div class=controls>
25
- <input class="field" type=text ng-model=form.date ui-date="dateOptions" />
26
- </div>
27
- </div>
28
-
29
- <div class=control-group>
30
- <label class=control-label><?php _e('Period', 'ab') ?></label>
31
- <div class=controls>
32
- <div my-slide-up=errors.date_interval_not_available id=date_interval_not_available_msg>
33
- <?php _e( 'The selected period is occupied by another appointment!', 'ab' ) ?>
34
- </div>
35
- <select class="field-col-2" ng-model=form.start_time ng-options="t.title for t in dataSource.data.time" ng-change=onStartTimeChange()></select>
36
- <span><?php _e( ' to ', 'ab' ) ?></span>
37
- <select class="field-col-2" ng-model=form.end_time ng-options="t.title for t in dataSource.getDataForEndTime()" ng-change=onEndTimeChange()></select>
38
- <div my-slide-up=errors.date_interval_warning id=date_interval_warning_msg>
39
- <?php _e( 'The selected period does\'t match default duration for the selected service!', 'ab' ) ?>
40
- </div>
41
- </div>
42
- </div>
43
-
44
- <div class=control-group>
45
- <label class=control-label><?php _e('Customer', 'ab') ?></label>
46
- <div class=controls>
47
- <select class="field" data-placeholder="<?php _e('-- Select a customer --', 'ab') ?>" class="chzn-select" chosen="dataSource.data.customers"
48
- ng-model="form.customer" ng-options="c.name for c in dataSource.data.customers">
49
- </select>
50
- <div new-customer-dialog=createCustomer(customer) backdrop=false btn-class=""></div>
51
- </div>
52
- </div>
53
-
54
- <div class=control-group>
55
- <label class=control-label><?php _e('Notes', 'ab') ?></label>
56
- <div class=controls>
57
- <textarea class="field" ng-model=form.notes></textarea>
58
- </div>
59
- </div>
60
-
61
- <div class=control-group>
62
- <label class=control-label></label>
63
- <div class=controls>
64
- <input style="margin-top: 0" type="checkbox" id="email_notification" /> <?php _e('Send email notifications', 'ab') ?>
65
- <img
66
- src="<?php echo plugins_url( 'resources/images/help.png', dirname(__FILE__).'/../../../AB_Backend.php' ) ?>"
67
- alt=""
68
- class="ab-help-info"
69
- popover="<?php echo esc_attr(__('If email 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.', 'ab')) ?>"
70
- style="width:16px;margin-left:0;"
71
- />
72
- <div id="email_notification_text" style="display: none; margin-top: 10px;"><?php _e('This function is disabled in the light verison of Bookly. If you find the plugin useful for your business please consider buying a licence for the full version. It costs just $35 and for this money you will get many useful functions, lifetime free update and excellent support! More information can be found here'); ?>: <a href="http://bookly.ladela.com" target="_blank">http://bookly.ladela.com</a></div>
73
- </div>
74
- </div>
75
-
76
- <div class=control-group>
77
- <label class=control-label></label>
78
- <div class=controls>
79
- <div class=dialog-button-wrapper>
80
- <input type=submit class="btn btn-info ab-update-button" value="<?php _e('Save') ?>" />
81
- <a ng-click=closeDialog() class=ab-reset-form href=""><?php _e('Cancel') ?></a>
82
- </div>
83
- </div>
84
- </div>
85
-
86
- </form>
87
- </div>
88
-
89
- <div ng-show=loading class=loading-indicator>
90
- <img src="<?php echo plugins_url('resources/images/ajax_loader_32x32.gif', dirname(__FILE__) . '/../../../AB_Backend.php') ?>" alt="" />
91
- </div>
92
-
93
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/templates/calendar.php CHANGED
@@ -1,127 +1,62 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
- // wp start day
4
- $week_start_day = get_option('start_of_week', 1);
5
  ?>
6
- <div ng-app=appointmentForm class="wrap">
7
- <div id="ab_calendar_header">
8
- <h2><?php _e('Calendar', 'ab') ?></h2>
9
- <div class="ab-nav-calendar">
10
- <div class="btn-group right-margin left">
11
- <button class="btn btn-info ab-calendar-switch-view ab-calendar-day"><?php _e('Day','ab') ?></button>
12
- <button class="btn btn-info ab-calendar-switch-view ab-calendar-week ab-button-active"><?php _e('Week','ab') ?></button>
13
- </div>
14
- <button class="btn btn-info ab-calendar-today right-margin left"><?php _e('Today','ab') ?></button>
15
- <div id="week-calendar-picker" class="ab-week-picker-wrapper left right-margin" data-first_day="<?php echo $week_start_day ?>">
16
- <div class="input-prepend input-append">
17
- <span class="ab-week-picker-arrow prev add-on col-arrow">&#9668;</span>
18
- <input class="span2 ab-date-calendar" readonly="readonly" id="appendedPrependedInput" size="16" type="text" value="" />
19
- <span class="ab-week-picker-arrow next add-on col-arrow">&#9658;</span>
20
- </div>
21
- <div class="ab-week-picker"></div>
22
- </div>
23
- <div id="day-calendar-picker" class="ab-week-picker-wrapper left right-margin" style="display: none;" data-first_day="<?php echo $week_start_day ?>">
24
- <div class="pagination left">
25
- <ul>
26
- <li><a href="#" class="ab-week-picker-arrow-prev">&#9668;</a></li>
27
- <li><a style="padding: 0" href="#"></a></li>
28
- </ul>
29
- </div>
30
- <div class="input-append left" style="margin-right:-1px">
31
- <input style="width:131px;margin-left:-2px;border-radius:0" class="span2" id="appendedInput" size="16" type="text" value="" /><span style="border-radius:0" class="add-on col-arrow">▼</span>
32
- </div>
33
- <div class="pagination left">
34
- <ul>
35
- <?php for ( $i = 1; $i <= 7; ++ $i ) : ?>
36
- <li>
37
- <a href="#" class="ab-day-of-month" <?php if ( 1 == $i ) : ?> style="border-radius:0"<?php endif; ?>></a>
38
  </li>
39
- <?php endfor; ?>
40
- <li><a href="#" class="ab-week-picker-arrow-next">&#9658;</a></li>
41
- </ul>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  </div>
43
- </div>
44
- <!--div class="btn-group right right-margin">
45
- <a class="btn btn-info" href="#"><i class="icon-user icon-white"></i><?php _e(' All services','ab') ?></a>
46
- <a class="btn btn-info dropdown-toggle" data-toggle="dropdown" href="#"><span class="caret"></span></a>
47
- <ul class="dropdown-menu">
48
- <li>
49
- <a href="javascript:void(0)">
50
- <input style="margin-right: 5px;" type="checkbox" id="" class="all-staff left">
51
- <label for=""><?php _e('All staff','ab') ?></label>
52
- </a>
53
- </li>
54
- </ul>
55
- </div-->
56
- <div class="btn-group pull-right">
57
- <a class="btn btn-info ab-staff-filter-button" href="javascript:void(0)">
58
- <i class="icon-user icon-white"></i>
59
- <span id="ab-staff-button">
60
- <?php
61
- $staff_numb = count($collection);
62
- if ($staff_numb == 0) {
63
- _e(' No staff selected','ab');
64
- } else if ($staff_numb == 1) {
65
- echo $collection[0]->full_name;
66
- } else {
67
- echo $staff_numb . ' '. __('staff members','ab');
68
- }
69
- ?>
70
- </span>
71
- </a>
72
- <a class="btn btn-info dropdown-toggle ab-staff-filter-button" href="javascript:void(0)"><span class="caret"></span></a>
73
- <ul class="dropdown-menu pull-right">
74
- <li>
75
- <a href="javascript:void(0)">
76
- <input style="margin-right: 5px;" type="checkbox" checked="checked" id="ab-filter-all-staff" class="left">
77
- <label for="ab-filter-all-staff"><?php _e('All staff','ab') ?></label>
78
- </a>
79
- <?php foreach ($collection as $staff) : ?>
80
- <a style="padding-left: 35px;" href="javascript:void(0)">
81
- <input style="margin-right: 5px;" type="checkbox" checked="checked" id="ab-filter-staff-<?php echo $staff->id ?>" value="<?php echo $staff->id ?>" class="ab-staff-option left">
82
- <label style="padding-right: 15px;" for="ab-filter-staff-<?php echo $staff->id ?>"><?php echo $staff->full_name ?></label>
83
- </a>
84
- <?php endforeach ?>
85
- </li>
86
- </ul>
87
- </div>
88
  </div>
89
  </div>
90
- <?php if ( $collection ) : ?>
91
- <?php
92
- $user_names = array();
93
- $user_ids = array();
94
- ?>
95
- <div id="week_calendar_wrapper">
96
- <div class="tabbable" style="margin-top: 20px;">
97
- <ul class="nav nav-tabs" style="margin-bottom:0;border-bottom: 6px solid #1f6a8c">
98
- <?php foreach ($collection as $i => $staff) : ?>
99
- <li class="ab-staff-tab-<?php echo $staff->id ?> ab-calendar-tab<?php echo 0 == $i ? ' active' : '' ?>" data-staff-id="<?php echo $staff->id ?>">
100
- <a href="#" data-toggle="tab"><?php echo $staff->full_name ?></a>
101
- </li>
102
- <?php
103
- $user_names[] = $staff->full_name;
104
- $user_ids[] = $staff->id;
105
- ?>
106
- <?php endforeach ?>
107
- </ul>
108
- </div>
109
- <div class="ab-calendar-element-container">
110
- <div class="ab-calendar-element"></div>
111
- </div>
112
- </div>
113
- <div id="day_calendar_wrapper" style="display: none">
114
- <div class="ab-calendar-element-container">
115
- <div class="ab-calendar-element"></div>
116
- </div>
117
- </div>
118
- <?php include 'appointment_form.php' ?>
119
- </div>
120
- <span id="staff_ids" style="display: none"><?php echo json_encode($user_ids) ?></span>
121
- <span id="ab_calendar_data_holder" style="display: none">
122
- <span class="ab-calendar-first-day"><?php echo $week_start_day ?></span>
123
- <span class="ab-calendar-time-format"><?php echo get_option( 'time_format' ) ?></span>
124
- <span class="ab-calendar-users"><?php echo implode( '|', $user_names ) ?></span>
125
- </span>
126
- <?php endif; ?>
127
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ $user_names = array();
3
+ $user_ids = array();
 
4
  ?>
5
+ <style>
6
+ .fc-slats tr { height: <?php echo max( 21, intval( 620 / (1440 / get_option( 'ab_settings_time_slot_length' ) ) ) ) ?>px; }
7
+ </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>
backend/modules/coupons/AB_CouponsController.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/resources/css/coupons.css ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ jQuery(function($) {
2
+ $('.add-coupon, .delete').on('click', function(){
3
+ $('#lite_notice').modal('show');
4
+ });
5
+ });
backend/modules/coupons/templates/_list.php ADDED
@@ -0,0 +1,4 @@
 
 
 
 
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/index.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 -->
backend/modules/custom_fields/AB_CustomFieldsController.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/resources/css/custom_fields.css ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ /**
11
+ * Build initial fields.
12
+ */
13
+ restoreFields();
14
+
15
+ /**
16
+ * On "Add new field" button click.
17
+ */
18
+ $('#ab-add-fields').on('click', 'button', function() {
19
+ addField($(this).data('type'));
20
+ });
21
+
22
+ /**
23
+ * On "Add new item" button click.
24
+ */
25
+ $fields.on('click', 'button', function() {
26
+ addItem($(this).prev('ul'), $(this).data('type'));
27
+ });
28
+
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
+
36
+ /**
37
+ * Submit custom fields form.
38
+ */
39
+ $('#ajax-send-custom-fields').on('click', function(e) {
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
+ });
62
+
63
+ var ladda = Ladda.create(this);
64
+ ladda.start();
65
+ $.ajax({
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
+ });
75
+
76
+ /**
77
+ * On 'Reset' click.
78
+ */
79
+ $('button[type=reset]').on('click', function() {
80
+ $fields.empty();
81
+ restoreFields();
82
+ });
83
+
84
+ /**
85
+ * Add new field.
86
+ *
87
+ * @param type
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') {
97
+ id = Math.floor((Math.random() * 100000) + 1);
98
+ }
99
+ if (typeof label == 'undefined') {
100
+ label = '';
101
+ }
102
+ if (typeof required == 'undefined') {
103
+ required = false;
104
+ }
105
+ $new_field
106
+ .hide()
107
+ .data('ab-field-id', id)
108
+ .find('.ab-required').prop({
109
+ id : 'required-' + id,
110
+ checked : required
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();
125
+
126
+ return $new_field;
127
+ }
128
+
129
+ /**
130
+ * Add new checkbox/radio button/drop-down option.
131
+ *
132
+ * @param $ul
133
+ * @param type
134
+ * @param value
135
+ * @return {*|jQuery}
136
+ */
137
+ function addItem($ul, type, value) {
138
+ var $new_item = $('ul#ab-templates > li[data-type=' + type + ']').clone();
139
+ if (typeof value != 'undefined') {
140
+ $new_item.find('input').val(value);
141
+ }
142
+ $new_item.hide().appendTo($ul).fadeIn('fast').find('input').focus();
143
+
144
+ return $new_item;
145
+ }
146
+
147
+ /**
148
+ * Restore fields from BooklyL10n.custom_fields.
149
+ */
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
+ });
backend/modules/custom_fields/templates/index.php ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>
backend/modules/customer/AB_CustomerController.php CHANGED
@@ -1,156 +1,193 @@
1
- <?php
2
 
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
 
5
- include 'forms/AB_CustomerForm.php';
6
 
7
- class AB_CustomerController extends AB_Controller {
 
 
 
 
 
 
8
 
9
- public function index() {
10
- if (count($this->getPost())){
 
11
  $this->importCustomers();
12
  }
13
 
14
- $path = dirname( dirname(__FILE__) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- wp_enqueue_style( 'ab-style', plugins_url( 'resources/css/ab_style.css', $path ) );
17
- wp_enqueue_style( 'ab-bootstrap', plugins_url( 'resources/bootstrap/css/bootstrap.min.css', $path ) );
18
- wp_enqueue_script( 'ab-bootstrap', plugins_url( 'resources/bootstrap/js/bootstrap.min.js', $path ), array( 'jquery' ) );
19
- wp_enqueue_script( 'ab-angularjs', plugins_url( 'resources/js/angular-1.0.6.min.js', $path ) );
20
- wp_enqueue_script( 'ab-angularui', plugins_url( 'resources/js/angular-ui-0.4.0.min.js', $path ) );
21
- wp_enqueue_script( 'ab-ng-sanitize', plugins_url( 'resources/js/angular-sanitize-1.0.6.min.js', $path ) );
22
- wp_enqueue_script( 'ab-ng-app', plugins_url( 'resources/js/ng-app.js', __FILE__ ), array( 'jquery', 'ab-angularjs', 'ab-angularui', 'ab-ng-sanitize' ) );
23
- wp_enqueue_script( 'ab-ng-new_customer_dialog', plugins_url( 'resources/js/ng-new_customer_dialog.js', dirname(__FILE__) . '/../../AB_Backend.php' ), array( 'jquery', 'ab-angularjs' ) );
24
 
25
- $this->render('index');
26
  }
27
 
28
- /**
29
- * Get Amount of Payments via PayPal and Last and Total Appointments
30
- *
31
- * @param string $query
32
- * @return mixed $query
33
- */
34
- public function getCustomerData( $query ) {
35
- $wpdb = $this->getWpdb();
36
- $query = $wpdb->get_results( $query );
37
- if ( count( $query ) ) {
38
- foreach( $query as $num => $customer_data ) {
39
- // get Total Appointments
40
- $query[ $num ]->total_appointments = $wpdb->get_var("
41
- SELECT COUNT(a.id) as total_appointments
42
- FROM ab_appointment a
43
- WHERE a.customer_id = {$customer_data->id}
44
- ");
45
- // get Last Appointment
46
- $query[ $num ]->last_appointment = $wpdb->get_var("
47
- SELECT MAX(a.start_date) as last_appointment
48
- FROM ab_appointment a
49
- WHERE a.customer_id = {$customer_data->id}
50
- ");
51
- $query[ $num ]->last_appointment = AB_CommonUtils::getFormattedDateTime(
52
- $query[ $num ]->last_appointment
53
- );
54
-
55
- $query[ $num ]->payments = 0;
56
- }
57
- }
58
- return $query;
59
- } // getCustomerData
60
-
61
  /**
62
  * Get list of customers.
63
  */
64
- public function executeGetCustomers() {
 
 
 
65
  $response = array(
66
- 'status' => 'ok',
67
- 'data' => array(
68
- 'customers' => array(),
69
- 'total' => 0,
70
- 'pages' => 0,
71
- 'active_page' => 0,
72
- )
73
  );
74
 
75
- $page = (int) ( $this->_post[ 'page' ] ? $this->_post[ 'page' ] : 1 );
76
- $sort = in_array( $this->_post[ 'sort' ], array( 'name', 'phone', 'email' ) ) ? $this->_post[ 'sort' ] : 'name';
77
- $order = in_array( $this->_post[ 'order' ], array( 'asc', 'desc' )) ? $this->_post[ 'order' ] : 'asc';
78
- $filter = $this->_post[ 'filter' ] ? $this->_post[ 'filter' ] : '';
 
 
 
 
 
 
 
 
 
 
79
 
80
- $items_per_page = 20;
81
- $total = $this->getWpdb()->get_var( 'SELECT COUNT(*) FROM `ab_customer`' );
82
  $pages = ceil( $total / $items_per_page );
83
  if ( $page < 1 || $page > $pages ) {
84
  $page = 1;
85
  }
86
 
87
- if ( $total ) {
88
- $query = 'SELECT * FROM `ab_customer`';
89
-
90
- // WHERE
91
- if ( $filter !== '' ) {
92
- $query .= " WHERE name LIKE '%{$filter}%' OR phone LIKE '%{$filter}%' OR email LIKE '%{$filter}%'";
 
 
 
 
 
 
 
 
 
 
 
 
93
  }
94
- // ORDER BY
95
- $query .= " ORDER BY {$sort} {$order}";
96
- // LIMIT
97
- $start = ( $page - 1) * $items_per_page;
98
- $query .= " LIMIT {$start}, {$items_per_page}";
99
- $customer_data = self::getCustomerData( $query );
100
- // Populate response.
101
- $response[ 'data' ][ 'customers' ] = $customer_data;
102
- $response[ 'data' ][ 'total' ] = $total;
103
- $response[ 'data' ][ 'pages' ] = $pages;
104
- $response[ 'data' ][ 'active_page' ] = $page;
105
- }
106
 
107
- echo json_encode( $response );
108
- exit ( 0 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  }
110
 
111
  /**
112
  * Create or edit a customer.
113
  */
114
- public function executeSaveCustomer() {
 
115
  $response = array();
116
  $form = new AB_CustomerForm();
117
 
118
  do {
119
- if ( $this->_post[ 'name' ] !== '' ) {
120
- $form->bind( $this->getPost() );
121
  /** @var AB_Customer $customer */
122
  $customer = $form->save();
123
  if ( $customer ) {
124
- $response[ 'status' ] = 'ok';
125
  $response[ 'customer' ] = array(
126
  'id' => $customer->id,
127
  'name' => $customer->name,
 
128
  'phone' => $customer->phone,
129
  'email' => $customer->email,
130
  'notes' => $customer->notes,
131
  'jsonString' => json_encode( array(
132
- 'name' => $customer->name,
133
- 'phone' => $customer->phone,
134
- 'email' => $customer->email,
135
  'notes' => $customer->notes
136
  ) )
137
  );
138
  break;
139
  }
140
  }
141
- $response[ 'status' ] = 'error';
142
- $response[ 'errors' ] = array( 'name' => array( 'required' ) );
143
  } while ( 0 );
144
 
145
- echo json_encode( $response );
146
- exit ( 0 );
147
  }
148
 
149
  /**
150
  * Import customers from CSV.
151
  */
152
- private function importCustomers() {
153
- $csv_mimetypes = array(
 
 
 
154
  'text/csv',
155
  'application/csv',
156
  'text/comma-separated-values',
@@ -159,17 +196,17 @@ class AB_CustomerController extends AB_Controller {
159
  'application/vnd.msexcel'
160
  );
161
 
162
- if (in_array($this->_files['importCustomers']['type'], $csv_mimetypes)) {
163
- $file = fopen($this->_files['importCustomers']['tmp_name'], "r");
164
- while ($line = fgetcsv($file)){
165
- if (!empty($line[0])){
166
  $customer = new AB_Customer();
167
- $customer->set('name', $line[0]);
168
- if (isset($line[1])){
169
- $customer->set('phone', $line[1]);
170
  }
171
- if (isset($line[2])){
172
- $customer->set('email', $line[2]);
173
  }
174
  $customer->save();
175
  }
@@ -180,27 +217,38 @@ class AB_CustomerController extends AB_Controller {
180
  /**
181
  * Get angulars template for new customer dialog.
182
  */
183
- public function executeGetNgNewCustomerDialogTemplate() {
184
- $this->render( 'ng-new_customer_dialog' );
 
 
 
 
 
185
  exit ( 0 );
186
  }
187
 
188
  /**
189
  * Delete a customer.
190
  */
191
- public function executeDeleteCustomer() {
192
- if ( $this->_post[ 'id' ] !== '' ) {
193
- $this->getWpdb()->query($this->getWpdb()->prepare("DELETE FROM ab_customer WHERE id = %d",
194
- $this->_post[ 'id' ]
195
- ));
 
196
  }
 
197
  }
198
 
199
  /**
200
  * Override parent method to add 'wp_ajax_ab_' prefix
201
  * so current 'execute*' methods look nicer.
 
 
202
  */
203
- protected function registerWpActions( $prefix = '' ) {
 
204
  parent::registerWpActions( 'wp_ajax_ab_' );
205
  }
 
206
  }
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',
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
  }
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 CHANGED
@@ -1,8 +1,4 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
-
5
- include dirname(__FILE__) . '/../../../../lib/entities/AB_Customer.php';
6
 
7
  /**
8
  * Class AB_CustomerForm
@@ -12,17 +8,21 @@ class AB_CustomerForm extends AB_Form {
12
  /**
13
  * Constructor.
14
  */
15
- public function __construct() {
 
16
  parent::$entity_class = 'AB_Customer';
17
  parent::__construct();
18
  }
19
 
20
- public function configure() {
21
- $this->setFields(array(
 
22
  'name',
 
23
  'phone',
24
  'email',
25
  'notes'
26
- ));
27
  }
 
28
  }
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
 
 
2
 
3
  /**
4
  * Class AB_CustomerForm
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 ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
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 CHANGED
@@ -1,226 +1,356 @@
1
  ;(function() {
 
2
 
3
- var module = angular.module('customers', ['ui', 'newCustomerDialog', 'ngSanitize']);
4
-
5
- module.factory('dataSource', function($q, $rootScope) {
6
- var ds = {
7
- customers : [],
8
- total : 0,
9
- pages : [],
10
- form : {
11
- new_customer : {
12
- name : 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.status === 'ok') {
27
- ds.customers = response.data.customers;
28
- ds.total = response.data.total;
29
- ds.pages = [];
30
- for (var i = 0; i < response.data.pages; ++ i) {
31
- ds.pages.push({
32
- number : i + 1,
33
- active : response.data.active_page == i + 1
34
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  }
36
- }
37
- $rootScope.$apply(deferred.resolve);
38
- },
39
- error : function() {
40
- ds.customers = [];
41
- ds.total = 0;
42
- $rootScope.$apply(deferred.resolve);
43
- }
44
- });
45
- return deferred.promise;
46
- }
47
- };
48
-
49
- return ds;
50
- });
51
-
52
- module.controller('customersCtrl', function($scope, dataSource) {
53
- // Set up initial data.
54
- var params = {
55
- page : 1,
56
- sort : 'name',
57
- order : 'asc',
58
- filter : ''
59
- };
60
- $scope.loading = true;
61
- $scope.css_class = {
62
- name : 'asc',
63
- phone : '',
64
- email : '',
65
- notes : '',
66
- last_appointment : '',
67
- total_appointments : '',
68
- payments : ''
69
- };
70
- // Set up data source (data will be loaded in reload function).
71
- $scope.dataSource = dataSource;
72
-
73
- $scope.reload = function( opt ) {
74
- $scope.loading = true;
75
- if (opt !== undefined) {
76
- if (opt.sort !== undefined) {
77
- if (params.sort === opt.sort) {
78
- // Toggle order when sorting by the same field.
79
- params.order = params.order === 'asc' ? 'desc' : 'asc';
80
- } else {
81
- params.order = 'asc';
82
- }
 
 
83
  $scope.css_class = {
84
- name : '',
85
- phone : '',
86
- email : '',
87
- notes : '',
88
- last_appointment : '',
 
89
  total_appointments : '',
90
- payments : ''
91
  };
92
- $scope.css_class[opt.sort] = params.order;
93
- }
94
- jQuery.extend(params, opt);
95
- }
96
- dataSource.loadData(params).then(function() {
97
- $scope.loading = false;
98
- });
99
- };
100
-
101
- var filter_delay = null;
102
- $scope.$watch('filter', function() {
103
- if (filter_delay !== null) {
104
- clearTimeout(filter_delay);
105
- }
106
- filter_delay = setTimeout(function() {
107
- filter_delay = null;
108
- $scope.$apply(function($scope) {
109
- $scope.reload({filter: $scope.filter});
110
- });
111
- }, 400);
112
- });
113
-
114
- /**
115
- * Edit customer.
116
- *
117
- * @param object customer
118
- * @param object params
119
- */
120
- $scope.saveCustomer = function(customer, params) {
121
- customer.edit_name = false;
122
- customer.edit_phone = false;
123
- customer.edit_email = false;
124
- customer.edit_notes = false;
125
- customer.errors = {};
126
-
127
- $scope.loading = true;
128
- jQuery.ajax({
129
- url : ajaxurl,
130
- type : 'POST',
131
- data : {
132
- action : 'ab_save_customer',
133
- id : customer.id,
134
- name : customer.name,
135
- phone : customer.phone,
136
- email : customer.email,
137
- notes : customer.notes
138
- },
139
- dataType : 'json',
140
- success : function(response) {
141
- $scope.$apply(function($scope) {
142
- if (response.status === 'error') {
143
- jQuery.each(response.errors, function(field, errors) {
144
- customer.errors[field] = {};
145
- customer['edit_' + field] = true;
146
- jQuery.each(errors, function(key, error) {
147
- customer.errors[field][error] = true;
148
- });
149
  });
150
- }
151
- $scope.loading = false;
152
- });
153
- },
154
- error : function(response) {
155
- $scope.$apply(function($scope) {
156
- $scope.loading = false;
 
 
 
 
 
 
157
  });
158
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  });
160
- };
161
-
162
- /**
163
- * Callback for creating new customer.
164
- *
165
- * @param object customer
166
- */
167
- $scope.createCustomer = function(customer) {
168
- dataSource.customers.push(customer);
169
- $scope.reload(params.page);
170
- };
171
-
172
- /**
173
- * Delete customer.
174
- *
175
- * @param customer
176
- */
177
- $scope.deleteCustomer = function(customer) {
178
- if (confirm('Are you sure ?')) {
179
- jQuery.ajax({
180
- url : ajaxurl,
181
- type : 'POST',
182
- data : {
183
- action : 'ab_delete_customer',
184
- id : customer.id
185
- },
186
- dataType : 'json',
187
- success : function(response) {
188
- $scope.$apply(function($scope) {
189
- jQuery.each(dataSource.customers, function(index, value) {
190
- if(value.id == customer.id) {
191
- dataSource.customers.splice(index, 1);
192
- $scope.reload(params.page);
193
- return false;
194
- }
195
  });
196
- });
197
- }
198
- });
199
- }
200
- };
201
- });
202
-
203
- /**
204
- * Directive for setting focus to element.
205
- */
206
- module.directive('focusMe', function($timeout) {
207
- return {
208
- link: function(scope, element, attrs) {
209
- scope.$watch(attrs.focusMe, function(value) {
210
- if (value) {
211
- $timeout(function() {
212
- element[0].focus();
213
- });
214
- }
215
- });
216
- }
217
- };
218
- });
219
-
220
- module.filter('nl2br', function() {
221
- return function(input) {
222
- return ('' + input).split('\n').join('<br>');
223
- };
224
- });
 
 
 
 
 
225
 
226
  })();
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 ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 CHANGED
@@ -1,106 +1,136 @@
1
- <div class="ab-title"><?php _e( 'Customers', 'ab' ); ?></div>
2
- <div ng-app=customers ng-controller=customersCtrl style="min-width: 800px;" class="form-horizontal ng-cloak">
3
-
4
- <div class="control-group left">
5
- <label class=control-label><?php _e( 'Quick search customer', 'ab' ) ?></label>
6
- <div class=controls>
7
- <input type=text ng-model=filter />
8
  </div>
9
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- <div style="margin-left: 50%;">
12
- <div style="display: inline;" new-customer-dialog=createCustomer(customer) backdrop=true btn-class="btn btn-info"></div>
13
- <div style="display: inline;" btn-class="btn btn-info">
14
- <a href=#ab_import_customers_dialog class="btn btn-info" data-toggle=modal><?php _e( 'Import' , 'ab' ) ?></a>
15
- <div id=ab_import_customers_dialog class="modal hide fade" tabindex=-1 role=dialog aria-labelledby=myModalLabel aria-hidden=true>
16
- <div class=dialog-content>
17
- <form class=form-horizontal enctype="multipart/form-data" action="?page=ab-system-customers" method="POST">
18
- <div class=modal-header>
19
- <button type=button class=close data-dismiss=modal aria-hidden=true>×</button>
20
- <h3 id=myModalLabel><?php _e( 'Import', 'ab' ) ?></h3>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  </div>
22
- <div class=modal-body>
23
- <div class=control-group>
24
- <label class=control-label>Note: </label>
25
- <div class=controls>
26
- You may import list of clients in CSV format. The file needs to have three columns: Name, Phone and Email.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  </div>
28
- </div>
29
- <div class=control-group>
30
- <label class=control-label><?php _e( 'Select file:' , 'ab' ) ?></label>
31
- <div class=controls>
32
- <input name="importCustomers" type="file" id="importCustomers">
33
  </div>
34
- </div>
35
  </div>
36
- <div class=modal-footer>
37
- <div class=ab-modal-button>
38
- <input type="hidden" name="import">
39
- <input type="submit" class="btn btn-info ab-popup-save ab-update-button" value="<?php _e( 'Import' , 'ab' ) ?>" />
40
- <button class=ab-reset-form data-dismiss=modal aria-hidden=true><?php _e( 'Cancel' , 'ab' ) ?></button>
41
- </div>
42
  </div>
43
- </form>
44
  </div>
45
- </div>
46
  </div>
47
- </div>
48
 
49
- <table id=ab_customers_list class="table table-striped" cellspacing=0 cellpadding=0 border=0 style="clear: both;">
50
- <thead>
51
- <tr>
52
- <th width=150 ng-class=css_class.name><a href="" ng-click=reload({sort:'name'})><?php _e( 'Name', 'ab' ); ?></a></th>
53
- <th width=100 ng-class=css_class.phone><a href="" ng-click=reload({sort:'phone'})><?php _e( 'Phone', 'ab' ); ?></a></th>
54
- <th width=150 ng-class=css_class.email><a href="" ng-click=reload({sort:'email'})><?php _e( 'Email', 'ab' ); ?></a></th>
55
- <th width=150 ng-class=css_class.notes><a href="" ><?php _e( 'Notes', 'ab' ); ?></a></th>
56
- <th width=150 ng-class=css_class.last_appointment><a href="" ng-click=reload({sort:'last_appointment'})><?php _e( 'Last appointment', 'ab' ); ?></a></th>
57
- <th width=150 ng-class=css_class.total_appointments><a href="" ng-click=reload({sort:'total_appointments'})><?php _e( 'Total appointments', 'ab'); ?></a></th>
58
- <th width=150 ng-class=css_class.payments><a href="" ng-click=reload({sort:'payments'})><?php _e( 'Payments', 'ab'); ?></a></th>
59
- </tr>
60
- </thead>
61
- <tbody>
62
- <tr ng-repeat="customer in dataSource.customers">
63
- <td>
64
- <div ng-click="customer.edit_name = true" ng-hide=customer.edit_name class=displayed-value>{{customer.name}}</div>
65
- <span ng-show=customer.errors.name.required><?php _e( 'Required', 'ab' ) ?></span>
66
- <input class=ab-value ng-model=customer.name ui-event="{blur:'saveCustomer(customer)'}" ng-show=customer.edit_name focus-me=customer.edit_name required />
67
- </td>
68
- <td class="ab-phone">
69
- <div ng-click="customer.edit_phone = true" ng-hide=customer.edit_phone class=displayed-value>{{customer.phone}}</div>
70
- <input class=ab-value ng-model=customer.phone ui-event="{blur:'saveCustomer(customer)'}" ng-show=customer.edit_phone focus-me=customer.edit_phone />
71
- </td>
72
- <td>
73
- <div ng-click="customer.edit_email = true" ng-hide=customer.edit_email class=displayed-value>{{customer.email}}</div>
74
- <input class=ab-value ng-model=customer.email ui-event="{blur:'saveCustomer(customer)'}" ng-show=customer.edit_email focus-me=customer.edit_email />
75
- </td>
76
- <td>
77
- <div ng-click="customer.edit_notes = true" ng-hide=customer.edit_notes class=displayed-value ng-bind-html="customer.notes | nl2br"></div>
78
- <textarea class=ab-value ng-model="customer.notes" ui-event="{blur:'saveCustomer(customer)'}" ng-show=customer.edit_notes focus-me=customer.edit_notes></textarea>
79
- </td>
80
- <td>
81
- <div ng-model=customer.last_appointment class=displayed-value>{{customer.last_appointment}}</div>
82
- </td>
83
- <td>
84
- <div ng-model=customer.total_appointments class=displayed-value>{{customer.total_appointments}}</div>
85
- </td>
86
- <td>
87
- <div ng-model=customer.payments class=displayed-value>{{customer.payments}}</div>
88
- </td>
89
- <td><a href="" ng-click="deleteCustomer(customer)" role="button" class="btn btn-danger" id="{{customer.id}}" name="customer_delete"><?php _e( 'Delete', 'ab' ) ?></a></td>
90
- </tr>
91
- <tr ng-hide="dataSource.customers.length || loading"><td colspan=6><?php _e( 'No customers', 'ab' ); ?></td></tr>
92
- </tbody>
93
- </table>
94
- <div class="btn-toolbar" ng-hide="dataSource.pages.length == 1">
95
- <div class="btn-group">
96
- <button ng-click=reload({page:page.number}) class="btn" ng-repeat="page in dataSource.pages" ng-switch on=page.active>
97
- <span ng-switch-when=true>{{page.number}}</span>
98
- <a href="" ng-switch-default>{{page.number}}</a>
99
- </button>
100
  </div>
101
- </div>
102
- <div ng-show=loading class=loading-indicator>
103
- <img src="<?php echo plugins_url('resources/images/ajax_loader_32x32.gif', dirname(__FILE__) . '/../../../AB_Backend.php') ?>" alt="" />
104
- </div>
105
-
106
  </div>
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 CHANGED
@@ -1,49 +1,110 @@
 
1
  <div>
2
- <a href=#ab_new_customer_dialog class="{{btn_class}}" data-toggle=modal data-backdrop={{backdrop}}><?php _e( 'New customer' , 'ab' ) ?></a>
3
- <div id=ab_new_customer_dialog class="modal hide fade" tabindex=-1 role=dialog aria-labelledby=myModalLabel aria-hidden=true>
4
- <div class=dialog-content>
5
- <form class=form-horizontal ng-hide=loading>
6
- <div class=modal-header>
7
- <button type=button class=close data-dismiss=modal aria-hidden=true>×</button>
8
- <h3 id=myModalLabel><?php _e( 'New Customer', 'ab' ) ?></h3>
9
- </div>
10
- <div class=modal-body>
11
- <div class=control-group>
12
- <label class=control-label><?php _e( 'Name' , 'ab' ) ?></label>
13
- <div class=controls>
14
- <input type=text ng-model=form.name required />
15
- <span style="font-size: 11px;color: red" ng-show=errors.name.required><?php _e( 'Required' , 'ab' ) ?></span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  </div>
17
- </div>
18
- <div class=control-group>
19
- <label class=control-label><?php _e( 'Phone' , 'ab' ) ?></label>
20
- <div class=controls>
21
- <input type=text ng-model=form.phone />
22
- </div>
23
- </div>
24
- <div class=control-group>
25
- <label class=control-label><?php _e( 'Email' , 'ab' ) ?></label>
26
- <div class=controls>
27
- <input type=text ng-model=form.email />
28
- </div>
29
- </div>
30
- <div class=control-group>
31
- <label class=control-label><?php _e( 'Notes' , 'ab' ) ?></label>
32
- <div class=controls>
33
- <textarea ng-model=form.notes></textarea>
34
- </div>
35
- </div>
36
- </div>
37
- <div class=modal-footer>
38
- <div class=ab-modal-button>
39
- <button ng-click=processForm() class="btn btn-info ab-popup-save ab-update-button"><?php _e( 'Save customer' , 'ab' ) ?></button>
40
- <button class=ab-reset-form data-dismiss=modal aria-hidden=true><?php _e( 'Cancel' , 'ab' ) ?></button>
41
- </div>
42
- </div>
43
- </form>
44
- <div ng-show=loading class=loading-indicator>
45
- <img src="<?php echo plugins_url( 'resources/images/ajax_loader_32x32.gif', dirname(__FILE__) . '/../../../AB_Backend.php' ) ?>" alt="" />
46
- </div>
47
- </div>
48
- </div>
49
  </div>
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/export/AB_ExportController.php DELETED
@@ -1,123 +0,0 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
-
5
- /**
6
- * Class AB_ExportController
7
- */
8
- class AB_ExportController extends AB_Controller {
9
-
10
- public function index() {
11
- $path = dirname( dirname( __FILE__ ) );
12
- wp_enqueue_style( 'ab-style', plugins_url( 'resources/css/ab_style.css', $path ) );
13
- wp_enqueue_style( 'ab-bootstrap', plugins_url( 'resources/bootstrap/css/bootstrap.min.css', $path ) );
14
- wp_enqueue_script( 'ab-bootstrap', plugins_url( 'resources/bootstrap/js/bootstrap.min.js', $path ), array( 'jquery' ) );
15
- wp_enqueue_script( 'ab-date', plugins_url( 'resources/js/date.js', $path ), array( 'jquery' ) );
16
- wp_enqueue_script( 'ab-daterangepicker-js', plugins_url( 'resources/js/daterangepicker.js', $path ), array( 'jquery' ) );
17
- wp_enqueue_style( 'ab-daterangepicker-css', plugins_url( 'resources/css/daterangepicker.css', $path ) );
18
- wp_enqueue_script( 'ab-bootstrap-select-js', plugins_url( 'resources/js/bootstrap-select.min.js', $path ));
19
- wp_enqueue_style( 'ab-bootstrap-select-css', plugins_url( 'resources/css/bootstrap-select.min.css', $path ));
20
- wp_localize_script( 'ab-daterangepicker-js', 'BooklyL10n', array(
21
- 'today' => __( 'Today', 'ab' ),
22
- 'yesterday' => __( 'Yesterday', 'ab' ),
23
- 'last_7' => __( 'Last 7 Days', 'ab' ),
24
- 'last_30' => __( 'Last 30 Days', 'ab' ),
25
- 'this_month' => __( 'This Month', 'ab' ),
26
- 'last_month' => __( 'Last Month', 'ab' ),
27
- 'custom_range' => __( 'Custom Range', 'ab' ),
28
- 'apply' => __( 'Apply', 'ab' ),
29
- 'clear' => __( 'Clear', 'ab' ),
30
- 'to' => __( 'To', 'ab' ),
31
- 'from' => __( 'From', 'ab' ),
32
- 'month' => array(
33
- 0 => __( 'January', 'ab' ),
34
- 1 => __( 'February', 'ab' ),
35
- 2 => __( 'March', 'ab' ),
36
- 3 => __( 'April', 'ab' ),
37
- 4 => __( 'May', 'ab' ),
38
- 5 => __( 'June', 'ab' ),
39
- 6 => __( 'July', 'ab' ),
40
- 7 => __( 'August', 'ab' ),
41
- 8 => __( 'September', 'ab' ),
42
- 9 => __( 'October', 'ab' ),
43
- 10 => __( 'November', 'ab' ),
44
- 11 => __( 'December', 'ab' )
45
- ),
46
- 'day' => array(
47
- 'Mon' => __( 'Mon', 'ab' ),
48
- 'Tue' => __( 'Tue', 'ab' ),
49
- 'Wed' => __( 'Wed', 'ab' ),
50
- 'Thu' => __( 'Thu', 'ab' ),
51
- 'Fri' => __( 'Fri', 'ab' ),
52
- 'Sat' => __( 'Sat', 'ab' ),
53
- 'Sun' => __( 'Sun', 'ab' )
54
- )
55
- ));
56
- $this->render( 'index' );
57
- } // index
58
-
59
-
60
- /**
61
- * Export Appointment to CSV
62
- */
63
- public function executeExportToCSV ( ) {
64
- $_post = $this->getPost();
65
- $start_date = new DateTime($_post['date_start']);
66
- $start_date = $start_date->format('Y-m-d H:i:s');
67
- $end_date = new DateTime($_post['date_end']);
68
- $end_date->modify( '+1 day' );
69
- $end_date = $end_date->format('Y-m-d H:i:s');
70
-
71
- header('Content-Type: text/csv; charset=' . (WPLANG == 'ru_RU') ? 'windows-1251': 'utf-8');
72
- header('Content-Disposition: attachment; filename=appointment.csv');
73
-
74
- if (WPLANG == 'ru_RU'){
75
- $headings = array(
76
- iconv('utf-8', 'windows-1251', __('Customer name', 'ab')),
77
- iconv('utf-8', 'windows-1251', __('Service title', 'ab')),
78
- iconv('utf-8', 'windows-1251', __('Start date', 'ab')),
79
- iconv('utf-8', 'windows-1251', __('End date', 'ab')),
80
- iconv('utf-8', 'windows-1251', __('Notes', 'ab')));
81
- }else{
82
- $headings = array(
83
- __('Customer name', 'ab'),
84
- __('Service title', 'ab'),
85
- __('Start date', 'ab'),
86
- __('End date', 'ab'),
87
- __('Notes', 'ab'));
88
- }
89
- $output = fopen('php://output', 'w');
90
- fputcsv($output, $headings);
91
-
92
- $appointments = $this->getWpdb()->get_results("
93
- SELECT c.name AS customer_name,
94
- s.title AS service_title,
95
- a.start_date AS start_date,
96
- a.end_date AS end_date,
97
- a.notes AS notes
98
- FROM ab_appointment a
99
- LEFT JOIN ab_service s ON a.service_id = s.id
100
- LEFT JOIN ab_customer c ON a.customer_id = c.id
101
- WHERE a.start_date between '{$start_date}' AND '{$end_date}'
102
- ORDER BY a.start_date DESC
103
- ", ARRAY_A);
104
-
105
- foreach( $appointments as $appointment ) {
106
- if (WPLANG == 'ru_RU'){
107
- $appointment['customer_name'] = iconv('utf-8', 'windows-1251', $appointment['customer_name']);
108
- $appointment['notes'] = iconv('utf-8', 'windows-1251', $appointment['notes']);
109
- $appointment['service_title'] = iconv('utf-8', 'windows-1251', $appointment['service_title']);
110
- }
111
- fputcsv($output, $appointment);
112
- }
113
- fclose($output);
114
- exit();
115
- }
116
- /**
117
- * Override parent method to add 'wp_ajax_ab_' prefix
118
- * so current 'execute*' methods look nicer.
119
- */
120
- protected function registerWpActions( $prefix = '' ) {
121
- parent::registerWpActions( 'wp_ajax_ab_' );
122
- }
123
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/export/templates/index.php DELETED
@@ -1,47 +0,0 @@
1
- <div class="ab-title"><?php _e( 'Export appointments', 'ab' ); ?></div>
2
- <div class=ab-nav-payment>
3
- <form action="<?php echo get_admin_url(); ?>admin-ajax.php?action=ab_export_to_csv" method="post" style="margin: 0">
4
- <div id=reportrange class="pull-left ab-reportrange" style="margin-bottom: 10px">
5
-
6
- <i class="icon-calendar icon-large"></i>
7
- <span data-date="<?php echo date( 'F j, Y', strtotime( '-30 day' ) ) ?> - <?php echo date( 'F j, Y' ) ?>"><?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>
8
-
9
- </div>
10
- <input type="hidden" id="date_start" name="date_start" value="<?php echo date_i18n( get_option( 'date_format' ) ) ?>"/>
11
- <input type="hidden" id="date_end" name="date_end" value="<?php echo date_i18n( get_option( 'date_format' ) ) ?>"/>
12
- <input type="submit" class="btn btn-info right-margin left" value="<?php _e('Export to CSV','ab') ?>">
13
- </form>
14
-
15
- </div>
16
- <script type="text/javascript">
17
- jQuery(function($) {
18
- var data = {},
19
- $report_range = $('#reportrange span'),
20
- picker_ranges = {},
21
- l10nRanges = {
22
- response: function(start, end) {
23
- return $.post(ajaxurl, {action: 'ab_l10n_ranges', start: start, end: end});
24
- },
25
- l10n: function(start, end) {
26
- this.response(start, end).done(function(response) {
27
- var ranges = JSON.parse(response);
28
- $report_range.data('date', start.toString('MMMM d, yyyy') + ' - ' + end.toString('MMMM d, yyyy'));
29
- $report_range.html(ranges.start + ' - ' + ranges.end);
30
- });
31
- }
32
- };
33
-
34
- picker_ranges[BooklyL10n.today] = ['today', 'today'];
35
- picker_ranges[BooklyL10n.yesterday] = ['yesterday', 'yesterday'];
36
- picker_ranges[BooklyL10n.last_7] = [Date.today().add({ days: -6 }), 'today'];
37
- picker_ranges[BooklyL10n.last_30] = [Date.today().add({ days: -30 }), 'today','selected'];
38
- picker_ranges[BooklyL10n.this_month] = [Date.today().moveToFirstDayOfMonth(), Date.today().moveToLastDayOfMonth()];
39
- picker_ranges[BooklyL10n.last_month] = [Date.today().moveToFirstDayOfMonth().add({ months: -1 }), Date.today().moveToFirstDayOfMonth().add({ days: -1 })];
40
-
41
- $('#reportrange').daterangepicker({ranges: picker_ranges}, function(start, end) {
42
- l10nRanges.l10n(start, end);
43
- $('#date_start').val(start);
44
- $('#date_end').val(end);
45
- });
46
- });
47
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/notifications/AB_NotificationsController.php CHANGED
@@ -1,53 +1,57 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
 
5
  class AB_NotificationsController extends AB_Controller {
6
 
7
- public function index() {
8
- wp_enqueue_style( 'ab-style', plugins_url( 'resources/css/ab_style.css', dirname(__FILE__).'/../../AB_Backend.php' ) );
9
- wp_enqueue_style( 'ab-bootstrap', plugins_url( 'resources/bootstrap/css/bootstrap.min.css', dirname(__FILE__).'/../../AB_Backend.php' ) );
10
- wp_enqueue_script( 'ab-bootstrap', plugins_url( 'resources/bootstrap/js/bootstrap.min.js', dirname(__FILE__).'/../../AB_Backend.php' ), array( 'jquery' ) );
11
-
12
- $this->notifications = array(
13
- 'client_info' => array(
14
- 'name' => __( 'Notification to Customer about Appointment details', 'ab' ),
15
- 'subject' => __( 'Your appointment information', 'ab' ),
16
- 'message' => wpautop( __("Dear [[CLIENT_NAME]].\n\nThis is confirmation that you have booked [[SERVICE_NAME]].\n\nWe are waiting you at [[COMPANY_ADDRESS]] on [[APPOINTMENT_DATE]] at [[APPOINTMENT_TIME]].\n\nThank you for choosing our company.\n\n[[COMPANY_NAME]]\n[[COMPANY_PHONE]]\n[[COMPANY_WEBSITE]]", 'ab' ) ),
17
- ),
18
- 'provider_info' => array(
19
- 'name' => __( 'Notification to Staff Member about Appointment details', 'ab' ),
20
- 'subject' => __( 'New booking information', 'ab' ),
21
- 'message' => wpautop( __( "Hello.\n\nYou have new booking.\n\nService: [[SERVICE_NAME]]\nDate: [[APPOINTMENT_DATE]]\nTime: [[APPOINTMENT_TIME]]\nClient name: [[CLIENT_NAME]]\nClient phone: [[CLIENT_PHONE]]\nClient email: [[CLIENT_EMAIL]]\nClient notes: [[CLIENT_NOTES]]", 'ab' ) ),
22
- ),
23
- 'evening_next_day' => array(
24
- 'name' => __( 'Evening reminder to Customer about next day Appointment', 'ab' ),
25
- 'subject' => __( 'Your appointment at [[COMPANY_NAME]]', 'ab' ),
26
- 'message' => wpautop( __( "Dear [[CLIENT_NAME]].\n\nWe would like to remind you that you have booked [[SERVICE_NAME]] tomorrow on [[APPOINTMENT_TIME]]. We are waiting you at [[COMPANY_ADDRESS]].\n\nThank you for choosing our company.\n\n[[COMPANY_NAME]]\n[[COMPANY_PHONE]]\n[[COMPANY_WEBSITE]]", 'ab' ) ),
27
- ),
28
- 'evening_after' => array(
29
- 'name' => __( 'Follow-up message on the day after Appointment', 'ab' ),
30
- 'subject' => __( 'Your visit to [[COMPANY_NAME]]', 'ab' ),
31
- 'message' => wpautop( __( "Dear [[CLIENT_NAME]].\n\nThank you for choosing [[COMPANY_NAME]]. We hope you were satisfied with your [[SERVICE_NAME]].\n\nThank you and we look forward to seeing you again soon.\n\n[[COMPANY_NAME]]\n[[COMPANY_PHONE]]\n[[COMPANY_WEBSITE]]", 'ab' ) ),
32
- ),
33
- 'event_next_day' => array(
34
- 'name' => __( 'Evening notification with the next day agenda to Staff Member', 'ab' ),
35
- 'subject' => __( 'Your agenda for [[TOMORROW_DATE]]', 'ab' ),
36
- 'message' => wpautop( __( "Hello.\n\nYour agenda for tomorrow is:\n\n[[NEXT_DAY_AGENDA]]", 'ab' ) ),
37
- )
38
- );
39
-
40
- $this->settings = array(
41
- 'media_buttons' => false,
42
- 'tinymce' => array(
43
- 'theme_advanced_buttons1' => 'formatselect,|,bold,italic,underline,|,' .
44
- 'bullist,blockquote,|,justifyleft,justifycenter' .
45
- ',justifyright,justifyfull,|,link,unlink,|' .
46
- ',spellchecker,wp_fullscreen,wp_adv'
47
- )
48
- );
49
-
50
- $this->render( 'index' );
 
 
 
 
 
 
51
  }
52
 
53
  // Protected methods.
@@ -55,8 +59,12 @@ class AB_NotificationsController extends AB_Controller {
55
  /**
56
  * Override parent method to add 'wp_ajax_ab_' prefix
57
  * so current 'execute*' methods look nicer.
 
 
58
  */
59
- protected function registerWpActions( $prefix = '' ) {
 
60
  parent::registerWpActions( 'wp_ajax_ab_' );
61
  }
 
62
  }
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.
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/forms/AB_NotificationsForm.php ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/resources/css/notifications.css ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/notification.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(function($) {
2
+
3
+ Ladda.bind( 'button[type=submit]' );
4
+
5
+ // menu fix for WP 3.8.1
6
+ $('#toplevel_page_ab-system > ul').css('margin-left', '0px');
7
+
8
+ /* exclude checkboxes in form */
9
+ var $checkboxes = $('.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
+ });
backend/modules/notifications/templates/_codes.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>
backend/modules/notifications/templates/_codes_client_new_wp_user.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
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>
backend/modules/notifications/templates/_codes_staff_agenda.php ADDED
@@ -0,0 +1,4 @@
 
 
 
 
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>
backend/modules/notifications/templates/_tags_client_info.php DELETED
@@ -1,48 +0,0 @@
1
- <tr>
2
- <th><?php _e('Subject', 'ab') ?></th>
3
- <th><?php _e('Message', 'ab') ?></th>
4
- </tr>
5
- <tr>
6
- <td><?php _e('[[COMPANY_NAME]] - name of the company', 'ab') ?></td>
7
- <td><?php _e('[[CLIENT_NAME]] - name of client', 'ab') ?></td>
8
- </tr>
9
- <tr>
10
- <td></td>
11
- <td><?php _e('[[STAFF_NAME]] - name of staff', 'ab') ?></td>
12
- </tr>
13
- <tr>
14
- <td></td>
15
- <td><?php _e('[[APPOINTMENT_DATE]] - date of appointment', 'ab') ?></td>
16
- </tr>
17
- <tr>
18
- <td></td>
19
- <td><?php _e('[[APPOINTMENT_TIME]] - time of appointment', 'ab') ?></td>
20
- </tr>
21
- <tr>
22
- <td></td>
23
- <td><?php _e('[[SERVICE_NAME]] - name of service', 'ab') ?></td>
24
- </tr>
25
- <tr>
26
- <td></td>
27
- <td><?php _e('[[COMPANY_NAME]] - name of the company', 'ab') ?></td>
28
- </tr>
29
- <tr>
30
- <td></td>
31
- <td><?php _e('[[COMPANY_LOGO]] - company logo', 'ab') ?></td>
32
- </tr>
33
- <tr>
34
- <td></td>
35
- <td><?php _e('[[COMPANY_ADDRESS]] - address of your company', 'ab') ?></td>
36
- </tr>
37
- <tr>
38
- <td></td>
39
- <td><?php _e('[[COMPANY_PHONE]] - company phone', 'ab') ?></td>
40
- </tr>
41
- <tr>
42
- <td></td>
43
- <td><?php _e('[[COMPANY_WEBSITE]] - this web-site address', 'ab') ?></td>
44
- </tr>
45
- <tr>
46
- <td></td>
47
- <td><?php _e('[[CANCEL_APPOINTMENT]] - cancel appointment link', 'ab') ?></td>
48
- </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/notifications/templates/_tags_evening_after.php DELETED
@@ -1,40 +0,0 @@
1
- <tr>
2
- <th><?php _e('Subject', 'ab') ?></th>
3
- <th><?php _e('Message', 'ab') ?></th>
4
- </tr>
5
- <tr>
6
- <td><?php _e('[[COMPANY_NAME]] - name of the company', 'ab') ?></td>
7
- <td><?php _e('[[CLIENT_NAME]] - name of client', 'ab') ?></td>
8
- </tr>
9
- <tr>
10
- <td></td>
11
- <td><?php _e('[[APPOINTMENT_DATE]] - date of appointment', 'ab') ?></td>
12
- </tr>
13
- <tr>
14
- <td></td>
15
- <td><?php _e('[[APPOINTMENT_TIME]] - time of appointment', 'ab') ?></td>
16
- </tr>
17
- <tr>
18
- <td></td>
19
- <td><?php _e('[[SERVICE_NAME]] - name of service', 'ab') ?></td>
20
- </tr>
21
- <tr>
22
- <td></td>
23
- <td><?php _e('[[COMPANY_NAME]] - name of the company', 'ab') ?></td>
24
- </tr>
25
- <tr>
26
- <td></td>
27
- <td><?php _e('[[COMPANY_LOGO]] - company logo', 'ab') ?></td>
28
- </tr>
29
- <tr>
30
- <td></td>
31
- <td><?php _e('[[COMPANY_ADDRESS]] - address of your company', 'ab') ?></td>
32
- </tr>
33
- <tr>
34
- <td></td>
35
- <td><?php _e('[[COMPANY_PHONE]] - company phone', 'ab') ?></td>
36
- </tr>
37
- <tr>
38
- <td></td>
39
- <td><?php _e('[[COMPANY_WEBSITE]] - this web-site address', 'ab') ?></td>
40
- </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/notifications/templates/_tags_evening_next_day.php DELETED
@@ -1,40 +0,0 @@
1
- <tr>
2
- <th><?php _e('Subject', 'ab') ?></th>
3
- <th><?php _e('Message', 'ab') ?></th>
4
- </tr>
5
- <tr>
6
- <td><?php _e('[[COMPANY_NAME]] - name of the company', 'ab') ?></td>
7
- <td><?php _e('[[CLIENT_NAME]] - name of client', 'ab') ?></td>
8
- </tr>
9
- <tr>
10
- <td></td>
11
- <td><?php _e('[[APPOINTMENT_DATE]] - date of appointment', 'ab') ?></td>
12
- </tr>
13
- <tr>
14
- <td></td>
15
- <td><?php _e('[[APPOINTMENT_TIME]] - time of appointment', 'ab') ?></td>
16
- </tr>
17
- <tr>
18
- <td></td>
19
- <td><?php _e('[[SERVICE_NAME]] - name of service', 'ab') ?></td>
20
- </tr>
21
- <tr>
22
- <td></td>
23
- <td><?php _e('[[COMPANY_NAME]] - name of the company', 'ab') ?></td>
24
- </tr>
25
- <tr>
26
- <td></td>
27
- <td><?php _e('[[COMPANY_LOGO]] - company logo', 'ab') ?></td>
28
- </tr>
29
- <tr>
30
- <td></td>
31
- <td><?php _e('[[COMPANY_ADDRESS]] - address of your company', 'ab') ?></td>
32
- </tr>
33
- <tr>
34
- <td></td>
35
- <td><?php _e('[[COMPANY_PHONE]] - company phone', 'ab') ?></td>
36
- </tr>
37
- <tr>
38
- <td></td>
39
- <td><?php _e('[[COMPANY_WEBSITE]] - this web-site address', 'ab') ?></td>
40
- </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/notifications/templates/_tags_event_next_day.php DELETED
@@ -1,8 +0,0 @@
1
- <tr>
2
- <th><?php _e('Subject', 'ab') ?></th>
3
- <th><?php _e('Message', 'ab') ?></th>
4
- </tr>
5
- <tr>
6
- <td><?php _e('[[TOMORROW_DATE]] - date of next day', 'ab') ?></td>
7
- <td><?php _e('[[NEXT_DAY_AGENDA]] - staff agenda for next day', 'ab') ?></td>
8
- </tr>
 
 
 
 
 
 
 
 
backend/modules/notifications/templates/_tags_provider_info.php DELETED
@@ -1,60 +0,0 @@
1
- <tr>
2
- <th><?php _e('Subject', 'ab') ?></th>
3
- <th><?php _e('Message', 'ab') ?></th>
4
- </tr>
5
- <tr>
6
- <td><?php _e('[[COMPANY_NAME]] - name of the company', 'ab') ?></td>
7
- <td><?php _e('[[CLIENT_NAME]] - name of client', 'ab') ?></td>
8
- </tr>
9
- <tr>
10
- <td></td>
11
- <td><?php _e('[[CLIENT_PHONE]] - сlient phone', 'ab') ?></td>
12
- </tr>
13
- <tr>
14
- <td></td>
15
- <td><?php _e('[[CLIENT_EMAIL]] - сlient email', 'ab') ?></td>
16
- </tr>
17
- <tr>
18
- <td></td>
19
- <td><?php _e('[[CLIENT_NOTES]] - сlient notes', 'ab') ?></td>
20
- </tr>
21
- <tr>
22
- <td></td>
23
- <td><?php _e('[[STAFF_NAME]] - name of staff', 'ab') ?></td>
24
- </tr>
25
- <tr>
26
- <td></td>
27
- <td><?php _e('[[APPOINTMENT_DATE]] - date of appointment', 'ab') ?></td>
28
- </tr>
29
- <tr>
30
- <td></td>
31
- <td><?php _e('[[APPOINTMENT_TIME]] - time of appointment', 'ab') ?></td>
32
- </tr>
33
- <tr>
34
- <td></td>
35
- <td><?php _e('[[SERVICE_NAME]] - name of service', 'ab') ?></td>
36
- </tr>
37
- <tr>
38
- <td></td>
39
- <td><?php _e('[[COMPANY_NAME]] - name of the company', 'ab') ?></td>
40
- </tr>
41
- <tr>
42
- <td></td>
43
- <td><?php _e('[[COMPANY_LOGO]] - company logo', 'ab') ?></td>
44
- </tr>
45
- <tr>
46
- <td></td>
47
- <td><?php _e('[[COMPANY_ADDRESS]] - address of your company', 'ab') ?></td>
48
- </tr>
49
- <tr>
50
- <td></td>
51
- <td><?php _e('[[COMPANY_PHONE]] - company phone', 'ab') ?></td>
52
- </tr>
53
- <tr>
54
- <td></td>
55
- <td><?php _e('[[COMPANY_WEBSITE]] - this web-site address', 'ab') ?></td>
56
- </tr>
57
- <tr>
58
- <td></td>
59
- <td><?php _e('[[CANCEL_APPOINTMENT]] - cancel appointment link', 'ab') ?></td>
60
- </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/notifications/templates/index.php CHANGED
@@ -1,122 +1,137 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="ab-title"><?php _e('Notifications','ab') ?></div>
3
- <div style="min-width: 800px;margin-top: -20px">
4
- <form method="post">
5
- <div class="ab-notifications">
6
- <?php
7
- $sender_name = get_option( 'ab_settings_sender_name' ) == '' ?
8
  get_option( 'blogname' ) : get_option( 'ab_settings_sender_name' );
9
- $sender_email = get_option( 'ab_settings_sender_email' ) == '' ?
10
  get_option( 'admin_email' ) : get_option( 'ab_settings_sender_email' );
11
- ?>
12
- <!-- sender name -->
13
- <label for="sender_name" style="display: inline;"><?php _e( 'Sender name', 'ab' ); ?></label>
14
- <input id="sender_name" name="sender_name" class="ab-sender" type="text" value="<?php echo $sender_name ; ?>"/><br>
15
- <!-- sender email -->
16
- <label for="sender_email" style="display: inline;"><?php _e( 'Sender email', 'ab' ); ?></label>
17
- <input id="sender_email" name="sender_email" class="ab-sender" type="text" value="<?php echo $sender_email; ?>"/>
18
  </div>
19
- <?php foreach ( $notifications as $k => $slug ): ?>
20
- <div class="ab-notifications">
21
- <div class="ab-toggle-arrow"></div>
22
- <legend id='legend_<?php echo $k; ?>_active'>
23
- <input type=checkbox id="<?php echo $k; ?>_active" type="checkbox" value="1" name=<?php echo $k; ?>[active]"/>
24
- <label for='<?php echo $k; ?>_active'><?php echo $slug['name']; ?></label>
25
- </legend>
26
- <div class="ab-form-field">
27
- <div class="ab-form-row">
28
- <label class='ab-form-label'><?php __( 'Subject','ab'); ?></label><input type='text' size='70' value='<?php echo $slug['subject']; ?>'/>
29
- </div>
30
- <div id="message_editor" class="ab-form-row">
31
- <label class="ab-form-label" style="margin-top: 35px;"><?php _e( 'Message', 'ab' ) ?></label>
32
- <?php wp_editor( $slug['message'], $k . '_message', array_merge(array('name' => $k . '[message]'), $settings) ) ?>
33
- </div>
34
- <?php if ('provider_info' == $k): ?>
35
- <div class='ab-form-row'>
36
- <label class='ab-form-label'></label>
37
- <div class='left'>
38
- <legend>
39
- <input type=checkbox />
40
- <label> <?php _e('Send copy to administrators', 'ab');?></label>
41
- </legend>
42
- </div>
43
- </div>
44
- <?php endif ?>
45
- <div class="ab-form-row">
46
- <label class="ab-form-label"><?php _e( 'Tags ','ab' ) ?></label>
47
- <div class="ab-tags-form left">
48
- <table>
49
- <tbody>
50
- <?php include "_tags_{$k}.php"; ?>
51
- </tbody>
52
- </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  </div>
54
- </div>
55
  </div>
56
- </div>
57
- <?php endforeach; ?>
58
- <div class="ab-notifications" style="border: 0">
59
- <input type="submit" value="<?php _e( 'Save Changes', 'ab' )?>" class="btn btn-info ab-update-button" />
60
- <button class="ab-reset-form" type="reset"><?php _e( 'Reset', 'ab' )?></button>
 
 
 
 
 
61
  </div>
62
- </form>
63
  </div>
64
-
65
- <div class="modal fade" id="light_notice" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
66
- <div class="modal-dialog">
67
- <div class="modal-content">
68
- <div class="modal-header">
69
- <h4 class="modal-title">Notice</h4>
70
- </div>
71
- <div class="modal-body">
72
- <?php _e('This function is disabled in the light verison of Bookly. If you find the plugin useful for your business please consider buying a licence for the full version. It costs just $35 and for this money you will get many useful functions, lifetime free update and excellent support! More information can be found here'); ?>: <a href="http://bookly.ladela.com" target="_blank">http://bookly.ladela.com</a>
73
- </div>
74
- <div class="modal-footer">
75
- <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
76
- </div>
77
- </div><!-- /.modal-content -->
78
- </div><!-- /.modal-dialog -->
79
- </div><!-- /.modal -->
80
-
81
- <script type="text/javascript">
82
- jQuery(function($) {
83
- // menu fix for WP 3.8.1
84
- $('#toplevel_page_ab-system > ul').css('margin-left', '0px');
85
- // Show-hide Notifications
86
- $('input:checkbox[id!=_active]').each(function() {
87
- $(this).change(function() {
88
- if ( $(this).attr('checked') ) {
89
- $(this).parent().next('div.ab-form-field').show(200);
90
- $(this).parents('.ab-notifications').find('.ab-toggle-arrow').css('background','url(<?php echo plugins_url( 'resources/images/notifications-arrow-up.png', dirname(__FILE__).'/../../../AB_Backend.php' ) ?>) 100% 0 no-repeat');
91
- } else {
92
- $(this).parent().next('div.ab-form-field').hide(200);
93
- $(this).parents('.ab-notifications').find('.ab-toggle-arrow').css('background','url(<?php echo plugins_url( 'resources/images/notifications-arrow-down.png', dirname(__FILE__).'/../../../AB_Backend.php' ) ?>) 100% 0 no-repeat');
94
- }
95
- }).change();
96
- });
97
- $('.ab-toggle-arrow').click(function() {
98
- $(this).nextAll('.ab-form-field').toggle(200, function() {
99
- if ( $('.ab-form-field').css('display') == 'block' ) {
100
- $(this).prevAll('.ab-toggle-arrow').css('background','url(<?php echo plugins_url( 'resources/images/notifications-arrow-up.png', dirname(__FILE__).'/../../../AB_Backend.php' ) ?>) 100% 0 no-repeat');
101
- } else {
102
- $(this).prevAll('.ab-toggle-arrow').css('background','url(<?php echo plugins_url( 'resources/images/notifications-arrow-down.png', dirname(__FILE__).'/../../../AB_Backend.php' ) ?>) 100% 0 no-repeat');
103
- }
104
- });
105
- });
106
- // filter sender name and email
107
- var escapeXSS = function (infected) {
108
- var regexp = /([<|(]("[^"]*"|'[^']*'|[^'">])*[>|)])/gi;
109
- return infected.replace(regexp, '');
110
- };
111
- $('input.ab-sender').on('change', function() {
112
- var $val = $(this).val();
113
- $(this).val(escapeXSS($val));
114
- });
115
-
116
- $("input[id$='_active']").change(function(){
117
- if ($(this).is(':checked')){
118
- $('#light_notice').modal('show');
119
- }
120
- });
121
- });
122
- </script>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ $ab_settings_sender_name = get_option( 'ab_settings_sender_name' ) == '' ?
 
 
 
 
 
3
  get_option( 'blogname' ) : get_option( 'ab_settings_sender_name' );
4
+ $ab_settings_sender_email = get_option( 'ab_settings_sender_email' ) == '' ?
5
  get_option( 'admin_email' ) : get_option( 'ab_settings_sender_email' );
6
+ ?>
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 -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/payment/AB_PaymentController.php CHANGED
@@ -1,59 +1,84 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
 
5
  class AB_PaymentController extends AB_Controller {
6
 
7
  public function index() {
8
- $path = dirname( dirname( __FILE__ ) );
9
- wp_enqueue_style( 'ab-style', plugins_url( 'resources/css/ab_style.css', $path ) );
10
- wp_enqueue_style( 'ab-bootstrap', plugins_url( 'resources/bootstrap/css/bootstrap.min.css', $path ) );
11
- wp_enqueue_script( 'ab-bootstrap', plugins_url( 'resources/bootstrap/js/bootstrap.min.js', $path ), array( 'jquery' ) );
12
- wp_enqueue_script( 'ab-date', plugins_url( 'resources/js/date.js', $path ), array( 'jquery' ) );
13
- wp_enqueue_script( 'ab-daterangepicker-js', plugins_url( 'resources/js/daterangepicker.js', $path ), array( 'jquery' ) );
14
- wp_enqueue_style( 'ab-daterangepicker-css', plugins_url( 'resources/css/daterangepicker.css', $path ) );
15
- wp_enqueue_script( 'ab-bootstrap-select-js', plugins_url( 'resources/js/bootstrap-select.min.js', $path ));
16
- wp_enqueue_style( 'ab-bootstrap-select-css', plugins_url( 'resources/css/bootstrap-select.min.css', $path ));
17
- wp_localize_script( 'ab-daterangepicker-js', 'BooklyL10n', array(
18
- 'today' => __( 'Today', 'ab' ),
19
- 'yesterday' => __( 'Yesterday', 'ab' ),
20
- 'last_7' => __( 'Last 7 Days', 'ab' ),
21
- 'last_30' => __( 'Last 30 Days', 'ab' ),
22
- 'this_month' => __( 'This Month', 'ab' ),
23
- 'last_month' => __( 'Last Month', 'ab' ),
24
- 'custom_range' => __( 'Custom Range', 'ab' ),
25
- 'apply' => __( 'Apply', 'ab' ),
26
- 'clear' => __( 'Clear', 'ab' ),
27
- 'to' => __( 'To', 'ab' ),
28
- 'from' => __( 'From', 'ab' ),
29
- 'month' => array(
30
- 0 => __( 'January', 'ab' ),
31
- 1 => __( 'February', 'ab' ),
32
- 2 => __( 'March', 'ab' ),
33
- 3 => __( 'April', 'ab' ),
34
- 4 => __( 'May', 'ab' ),
35
- 5 => __( 'June', 'ab' ),
36
- 6 => __( 'July', 'ab' ),
37
- 7 => __( 'August', 'ab' ),
38
- 8 => __( 'September', 'ab' ),
39
- 9 => __( 'October', 'ab' ),
40
- 10 => __( 'November', 'ab' ),
41
- 11 => __( 'December', 'ab' )
42
- ),
43
- 'day' => array(
44
- 'Mon' => __( 'Mon', 'ab' ),
45
- 'Tue' => __( 'Tue', 'ab' ),
46
- 'Wed' => __( 'Wed', 'ab' ),
47
- 'Thu' => __( 'Thu', 'ab' ),
48
- 'Fri' => __( 'Fri', 'ab' ),
49
- 'Sat' => __( 'Sat', 'ab' ),
50
- 'Sun' => __( 'Sun', 'ab' )
51
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  ));
53
 
54
- $this->types = $this->customers = $this->providers = $this->services = array();
 
 
 
 
 
55
 
56
  $this->render( 'index' );
57
  }
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
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 CHANGED
@@ -1,4 +1,4 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div id="ab_filter_error" class="alert alert-error" >
3
- <?php _e('This function is disabled in the light verison of Bookly. If you find the plugin useful for your business please consider buying a licence for the full version. It costs just $35 and for this money you will get many useful functions, lifetime free update and excellent support! More information can be found here'); ?>: <a href="http://bookly.ladela.com" target="_blank">http://bookly.ladela.com</a>
4
  </div>
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 CHANGED
@@ -1,80 +1,148 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class=ab-title><?php _e( 'Payments','ab' ) ?></div>
3
- <div style="min-width: 800px;margin-right: 15px;">
4
- <div class=ab-nav-payment>
5
- <div class=row-fluid>
6
- <div id=reportrange class="pull-left ab-reportrange" style="margin-bottom: 10px">
7
- <i class="icon-calendar icon-large"></i>
8
- <span data-date="<?php echo date( 'F j, Y', strtotime( '-30 day' ) ) ?> - <?php echo date( 'F j, Y' ) ?>"><?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>
9
- </div>
10
- <div class=pull-left>
11
- <select id=ab-type-filter class=selectpicker>
12
- <option value="-1"><?php _e( 'All payment types', 'ab' ) ?></option>
13
- <?php foreach ( $types as $type ): ?>
14
- <option value="<?php esc_attr_e( $type ) ?>"><?php echo $type == 'paypal' ? 'PayPal' : __( 'Local', 'ab' ) ?></option>
15
- <?php endforeach ?>
16
- </select>
17
- <select id=ab-customer-filter class=selectpicker>
18
- <option value="-1"><?php _e( 'All customers', 'ab' ) ?></option>
19
- <?php foreach ( $customers as $customer ): ?>
20
- <option><?php esc_html_e( $customer ) ?></option>
21
- <?php endforeach ?>
22
- </select>
23
- <select id=ab-provider-filter class=selectpicker>
24
- <option value="-1"><?php _e( 'All providers', 'ab' ) ?></option>
25
- <?php foreach ( $providers as $provider ): ?>
26
- <option><?php esc_html_e( $provider ) ?></option>
27
- <?php endforeach ?>
28
- </select>
29
- <select id=ab-service-filter class=selectpicker>
30
- <option value="-1"><?php _e( 'All services', 'ab' ) ?></option>
31
- <?php foreach ( $services as $service ): ?>
32
- <option><?php esc_html_e( $service ) ?></option>
33
- <?php endforeach ?>
34
- </select>
35
- <a id=ab-filter-submit style="margin:0 0 10px 5px;" href="#" class="btn btn-primary"><?php _e( 'Filter', 'ab' ) ?></a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  </div>
37
  </div>
 
 
 
 
 
38
  </div>
39
- <div id=ab-alert-div class=alert style="display: none"></div>
40
- <table class="table table-bordered" cellspacing=0 cellpadding=0 border=0 id=ab_payments_list style="clear: both;">
41
- <thead>
42
- <tr>
43
- <th width=150 class="desc active" order-by=created><a href="javascript:void(0)"><?php _e( 'Date', 'ab' ) ?></a></th>
44
- <th width=100 order-by=type><a href="javascript:void(0)"><?php _e( 'Type', 'ab' ) ?></a></th>
45
- <th width=150 order-by=customer><a href="javascript:void(0)"><?php _e( 'Customer', 'ab' ) ?></a></th>
46
- <th width=150 order-by=provider><a href="javascript:void(0)"><?php _e( 'Provider', 'ab' ) ?></a></th>
47
- <th width=150 order-by=service><a href="javascript:void(0)"><?php _e( 'Service', 'ab' ) ?></a></th>
48
- <th width=50 order-by=amount><a href="javascript:void(0)"><?php _e( 'Amount', 'ab') ?></a></th>
49
- <th width=150 order-by=date><a href="javascript:void(0)"><?php _e( 'Appointment Date', 'ab' ) ?></a></th>
50
- </tr>
51
- </thead>
52
- <tbody id=ab-tb-body>
53
- </tbody>
54
- </table>
55
- <?php include '_alert.php'; ?>
56
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  <script type="text/javascript">
59
  jQuery(function($) {
60
- var data = {},
61
  $report_range = $('#reportrange span'),
62
- picker_ranges = {},
63
- l10nRanges = {};
64
 
65
- picker_ranges[BooklyL10n.today] = ['today', 'today'];
66
- picker_ranges[BooklyL10n.yesterday] = ['yesterday', 'yesterday'];
67
- picker_ranges[BooklyL10n.last_7] = [Date.today().add({ days: -6 }), 'today'];
68
- picker_ranges[BooklyL10n.last_30] = [Date.today().add({ days: -30 }), 'today','selected'];
69
- picker_ranges[BooklyL10n.this_month] = [Date.today().moveToFirstDayOfMonth(), Date.today().moveToLastDayOfMonth()];
70
- picker_ranges[BooklyL10n.last_month] = [Date.today().moveToFirstDayOfMonth().add({ months: -1 }), Date.today().moveToFirstDayOfMonth().add({ days: -1 })];
71
 
72
  $('.selectpicker').selectpicker({style: 'btn-info', size: 5});
73
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- $('#reportrange').daterangepicker({ranges: picker_ranges}, function(start, end) {
76
- l10nRanges.l10n(start, end);
 
 
 
77
  });
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  });
80
  </script>
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/service/AB_ServiceController.php CHANGED
@@ -1,19 +1,55 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
-
5
- include 'forms/AB_CategoryForm.php';
6
- include 'forms/AB_ServiceForm.php';
7
 
8
  /**
9
  * Class AB_ServiceController
10
  */
11
  class AB_ServiceController extends AB_Controller {
12
 
 
13
  /**
14
- *
15
  */
16
- public function index() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  $this->staff_collection = $this->getStaffCollection();
18
  $this->category_collection = $this->getCategoryCollection();
19
  $this->service_collection = $this->getServiceCollection();
@@ -23,26 +59,31 @@ class AB_ServiceController extends AB_Controller {
23
  /**
24
  *
25
  */
26
- public function executeCategoryServices() {
 
27
  $this->setDataForServiceList();
28
- $this->render( 'list' );
29
  exit;
30
  }
31
 
32
  /**
33
  *
34
  */
35
- public function executeCategoryForm() {
 
36
  $this->form = new AB_CategoryForm();
37
 
38
- if ( count( $this->getPost() ) ) {
39
- $this->form->bind( $this->getPost() );
40
  if ( $category = $this->form->save() ) {
41
- echo "<div class='ab-category-item' data-id='{$category->id}'>
 
42
  <span class='left displayed-value'>{$category->name}</span>
43
  <a href='#' class='left ab-hidden ab-edit'></a>
44
  <input class=value type=text name=name value='{$category->name}' style='display: none' />
45
- <a href='#' class='left ab-hidden ab-delete'></a></div>";
 
 
46
  exit;
47
  }
48
  }
@@ -52,113 +93,177 @@ class AB_ServiceController extends AB_Controller {
52
  /**
53
  * Update category.
54
  */
55
- public function executeUpdateCategory() {
 
56
  $form = new AB_CategoryForm();
57
- $form->bind( $this->getPost() );
58
- $form->save();
 
 
59
  }
60
 
61
  /**
62
- * Delete category.
63
  */
 
 
 
 
 
 
 
 
 
 
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
- public function executeDeleteCategory() {
 
 
 
 
67
  $category = new AB_Category();
68
  $category->set( 'id', $this->getParameter( 'id', 0 ) );
69
  $category->delete();
70
- }
71
-
72
 
73
- public function executeAddService() {
 
74
  $form = new AB_ServiceForm();
75
- $form->bind( $this->getPost() );
 
76
  $service = $form->save();
77
  $this->setDataForServiceList( $service->get( 'category_id' ) );
78
- $this->render( 'list' );
79
- exit;
80
  }
81
 
82
- public function executeRemoveServices() {
83
- $_post = $this->getPost();
84
- $service_ids = isset( $_post['service_ids'] ) ? $_post['service_ids'] : array();
85
- if ( is_array( $service_ids ) && ! empty( $service_ids ) ) {
86
- $this->getWpdb()->query('DELETE FROM ab_service WHERE id IN (' . implode(', ', $service_ids) . ')');
87
  }
88
  }
89
 
90
- public function executeAssignStaff() {
91
- $_post = $this->getPost();
92
- $service_id = isset( $_post['service_id'] ) ? $_post['service_id'] : 0;
93
- $staff_ids = isset( $_post['staff_ids'] ) ? $_post['staff_ids'] : array();
94
 
95
  if ( $service_id ) {
96
- $wpDb = $this->getWpdb();
97
- $wpDb->query( $wpDb->prepare( 'DELETE FROM ab_staff_service WHERE service_id = %d', $service_id ) );
98
  $service = new AB_Service();
99
  if ( ! empty( $staff_ids ) && $service->load( $service_id ) ) {
100
  foreach ( $staff_ids as $staff_id ) {
101
  $staff_service = new AB_StaffService();
102
- $staff_service->set( 'staff_id', $staff_id );
103
  $staff_service->set( 'service_id', $service_id );
104
- $staff_service->set( 'price', $service->get( 'price' ) );
105
  $staff_service->save();
106
  }
107
  }
108
- echo count( $staff_ids );
109
- exit;
110
  }
 
111
  }
112
 
113
- public function executeUpdateServiceValue() {
 
 
 
 
114
  $form = new AB_ServiceForm();
115
- $form->bind($this->getPost());
116
- $form->save();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  }
118
 
119
- private function setDataForServiceList( $category_id = 0 ) {
120
- if (!$category_id) {
121
- $category_id = isset( $_GET['category_id'] ) ? intval( $_GET['category_id'] ) : 0;
 
 
 
 
122
  }
123
 
124
- $this->service_collection = $this->getServiceCollection($category_id);
125
  $this->staff_collection = $this->getStaffCollection();
126
  $this->category_collection = $this->getCategoryCollection();
127
  }
128
 
129
- private function getCategoryCollection() {
130
- return $this->getWpdb()->get_results( "SELECT * FROM ab_category" );
 
 
 
 
131
  }
132
 
 
 
 
133
  private function getStaffCollection() {
134
- return $this->getWpdb()->get_results( "SELECT * FROM ab_staff" );
135
- }
136
-
137
- private function getServiceCollection( $id = 0 ) {
138
- if ( $id ) {
139
- $query = $this->getWpdb()->prepare(
140
- 'SELECT
141
- service.*,
142
- COUNT(staff.id) AS "total_staff",
143
- GROUP_CONCAT(DISTINCT staff.id) AS "staff_ids"
144
- FROM ab_service service
145
- LEFT JOIN ab_staff_service staff_service ON service.id = staff_service.service_id
146
- LEFT JOIN ab_staff staff ON staff.id = staff_service.staff_id
147
- WHERE service.category_id = %d
148
- GROUP BY service.id, "total_staff"',
149
- $id
150
- );
151
- } else {
152
- $query = 'SELECT
153
- service.*,
154
- COUNT(staff.id) AS "total_staff",
155
- GROUP_CONCAT(DISTINCT staff.id) AS "staff_ids"
156
- FROM ab_service service
157
- LEFT JOIN ab_staff_service staff_service ON service.id = staff_service.service_id
158
- LEFT JOIN ab_staff staff ON staff.id = staff_service.staff_id
159
- GROUP BY service.id, "total_staff"';
160
- }
161
- return $this->getWpdb()->get_results($query);
162
  }
163
 
164
  // Protected methods.
@@ -166,9 +271,12 @@ class AB_ServiceController extends AB_Controller {
166
  /**
167
  * Override parent method to add 'wp_ajax_ab_' prefix
168
  * so current 'execute*' methods look nicer.
 
 
169
  */
170
- protected function registerWpActions( $prefix = '' ) {
 
171
  parent::registerWpActions( 'wp_ajax_ab_' );
172
  }
173
 
174
- }
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();
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
  }
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.
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 CHANGED
@@ -1,6 +1,4 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
 
5
  /**
6
  * Class AB_CategoryForm
@@ -10,7 +8,8 @@ class AB_CategoryForm extends AB_Form {
10
  /**
11
  * Constructor.
12
  */
13
- public function __construct() {
 
14
  parent::$entity_class = 'AB_Category';
15
  parent::__construct();
16
  }
@@ -18,7 +17,8 @@ class AB_CategoryForm extends AB_Form {
18
  /**
19
  * Configure the form.
20
  */
21
- public function configure() {
 
22
  $this->setFields( array( 'name' ) );
23
  }
24
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
2
 
3
  /**
4
  * Class AB_CategoryForm
8
  /**
9
  * Constructor.
10
  */
11
+ public function __construct()
12
+ {
13
  parent::$entity_class = 'AB_Category';
14
  parent::__construct();
15
  }
17
  /**
18
  * Configure the form.
19
  */
20
+ public function configure()
21
+ {
22
  $this->setFields( array( 'name' ) );
23
  }
24
 
backend/modules/service/forms/AB_ServiceForm.php CHANGED
@@ -1,24 +1,22 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
-
5
- include dirname(__FILE__) . '/../../../../lib/entities/AB_Service.php';
6
 
7
  /**
8
  * Class AB_ServiceForm
9
  */
10
- class AB_ServiceForm extends AB_Form {
11
-
12
  /**
13
  * Constructor.
14
  */
15
- public function __construct() {
 
16
  parent::$entity_class = 'AB_Service';
17
  parent::__construct();
18
  }
19
 
20
- public function configure() {
21
- $this->setFields(array('id', 'title', 'duration', 'price', 'category_id', 'color'));
 
22
  }
23
 
24
  /**
@@ -27,20 +25,23 @@ class AB_ServiceForm extends AB_Form {
27
  * @param array $post
28
  * @param array $files
29
  */
30
- public function bind( array $post, array $files = array() ) {
31
- if ( array_key_exists('category_id', $post) && !$post['category_id'] ) {
 
32
  $post['category_id'] = null;
33
  }
34
- parent::bind($post, $files);
35
  }
36
 
37
- public function save() {
 
38
  if ( $this->isNew() ) {
39
- $colors = array('#B0171F', '#DA70D6', '#BF3EFF', '#8470FF', '#4876FF', '#63B8FF', '#00B2EE', '#00F5FF', '#00C78C', '#BDFCC9', '#B4EEB4', '#7CFC00', '#ADFF2F', '#FFFFF0', '#CDCDB4', '#FFFF00', '#FFF68F', '#F0E68C', '#E3CF57', '#FFEBCD', '#FF8C00', '#EE4000', '#FA8072', '#F08080', '#CD0000');
40
- // when adding new service - set its color randomly
41
- $this->data[ 'color' ] = $colors[mt_rand(0, count($colors) - 1)];
42
  }
 
43
 
44
  return parent::save();
45
  }
 
46
  }
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
  /**
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 CHANGED
@@ -1,45 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  .ab-category-item {
2
- width: 110px;
3
  padding: 10px;
4
  padding-left: 50px;
5
  vertical-align: middle;
6
- -moz-border-radius: 5px;
7
  border-radius: 5px;
8
  cursor: pointer;
9
  }
 
 
 
 
 
 
 
10
  .ab-active {
11
  background-color: #ccc;
12
  }
13
 
14
- .wp-picker-container .iris-picker {
15
- border-color: black;
16
- margin-left: 35px;
17
- margin-top: 6px;
18
- }
19
 
20
  #services_list .service-color-wrapper .wp-picker-open + .wp-picker-input-wrap {
21
  display: block;
22
  margin-left: 50px;
23
  margin-top: 20px;
24
  }
 
 
 
 
25
 
26
  .wp-picker-container, .wp-picker-container:active {
27
  display: inline-block;
28
  outline: 0 none;
29
  position: absolute;
30
- margin-top: 3px;
31
- }
32
 
33
  /** Services list **/
34
- #ab_services_wrapper .list-wrapper { min-width: 590px }
 
35
  #ab_services_wrapper .list-wrapper .list-actions { overflow: hidden; margin-top: 10px; }
36
  #ab_services_wrapper .list-wrapper .list-actions .add-service { float: left }
37
  #ab_services_wrapper .list-wrapper .list-actions .delete { float: right }
38
 
39
  #services_list td.last,
40
- #services_list th.last { width: 16px; vertical-align: middle; }
41
- #services_list th, #services_list td { padding:5px }
42
- #services_list select { width: auto; margin: 0; }
 
43
  #services_list .service-color-cell { width: 28px; }
44
  #services_list .service-color-wrapper .wp-color-result { padding-left: 25px; margin: 0; top: auto; }
45
  #services_list .service-color-wrapper .wp-color-result.wp-picker-open { top: auto }
@@ -47,7 +66,79 @@
47
  #services_list .service-color-wrapper .wp-color-result.wp-picker-open:after { content: none }
48
  #services_list .service-color-wrapper .wp-picker-open + .wp-picker-input-wrap { display: block; visibility: hidden; margin-top: -25px; margin-left: 34px; }
49
  #services_list .service-color-wrapper .button { margin-left: 0 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  #new_category_popup .ab-popup { width: 187px }
52
  .staff-popup-wrapper .staff-row { white-space: nowrap; margin: 0; }
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 }
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 CHANGED
@@ -1,289 +1,344 @@
1
  jQuery(function($) {
2
- var $no_result = $('#ab_services_wrapper .no-result');
 
 
3
 
4
- // On new category form submit.
5
- $('#new-category-form').on('submit', function(event) {
6
- var data = $(this).serialize();
7
- $.post(ajaxurl, data, function(response) {
8
- $('.ab-category-item-list').append(response);
9
- $('#new_category_popup').ab_popup('close');
10
- // add created category to services
11
- $.each($('#services_list').find('select[name="category_id"]'), function(key, value) {
12
- var $new_category = $('.ab-category-item:last');
13
- $(value).append('<option value="' + $new_category.data('id') + '">'
14
- + $new_category.find('input').val() + ' </option>');
15
- });
16
- });
17
- return false;
18
- });
19
 
20
- // Preventing multiple creation of new category by pressing Enter-key
21
- $('input[value="ab_category_form"]').parent().find('input:first').one('keypress', function(e) {
22
- var code = (e.keyCode ? e.keyCode : e.which);
23
- if (code == 13) {
24
- $(this).trigger(e).blur();
25
- }
26
- });
 
 
 
27
 
28
- // Categories list delegated events.
29
- $('#ab-categories-list')
 
 
 
 
 
 
 
 
30
 
31
- // On category item click.
32
- .on('click', '.ab-category-item', function() {
33
- var $clicked = $(this);
34
- $.get(ajaxurl, {action:'ab_category_services', category_id: $clicked.data('id')}, function(response) {
35
- $('.ab-category-item').not($clicked).removeClass('ab-active');
36
- $('.ab-category-title').text($clicked.text());
37
- $clicked.addClass('ab-active');
38
- refreshList(response);
39
- });
40
- })
 
 
 
41
 
42
- // On edit category click.
43
- .on('click', '.ab-category-item .ab-edit', function(e) {
44
- // Keep category item click from being executed.
45
- e.stopPropagation();
46
- // Prevent navigating to '#'.
47
- e.preventDefault();
48
- // Hide edit button.
49
- $(this).hide()
50
- // Hide displayed category name and delete button.
51
- .siblings('.displayed-value, .ab-delete').hide().end()
52
- // Show input field.
53
- .nextAll('.value').show().focus();
54
- })
 
 
 
 
 
 
 
 
 
 
 
55
 
56
- // On blur of category edit input.
57
- .on('blur', '.ab-category-item input.value', function() {
58
- var $this = $(this),
59
- $item = $this.closest('.ab-category-item'),
60
- field = $this.attr('name'),
61
- value = $this.attr('value'),
62
- id = $item.data('id');
63
- if (value) {
64
- var data = { action: 'ab_update_category', id: id };
65
- data[field] = value;
66
- $.post(ajaxurl, data, function(response) {
67
- // Hide input field.
68
- $this.hide()
69
- // Show modified category name.
70
- .prevAll('.displayed-value').text(value).show().end()
71
- // Show edit and delete buttons.
72
- .siblings('.ab-edit, .ab-delete').show();
73
- // update edited category's name for services
74
- $.each($('#services_list').find('select[name="category_id"]'), function(k, v) {
75
- $(v).find('option:selected[value="' + id + '"]').text(value);
76
- });
 
77
  });
78
- }
79
- })
80
 
81
- // On delete category click.
82
- .on('click', '.ab-category-item .ab-delete', function(e) {
83
- // Keep category item click from being executed.
84
- e.stopPropagation();
85
- // Prevent navigating to '#'.
86
- e.preventDefault();
87
- // Ask user if he is sure.
88
- if (confirm(BooklyL10n.are_you_sure)) {
89
- var $item = $(this).closest('.ab-category-item');
90
- var data = { action: 'ab_delete_category', id: $item.data('id') };
91
  $.post(ajaxurl, data, function(response) {
92
- // Remove category item from Services
93
- $.each($('#services_list').find('select[name="category_id"]'), function(key, value) {
94
- $(value).find('option[value="' + $item.data('id') + '"]').remove();
95
- });
96
- // Remove category item from DOM.
97
- $item.remove();
98
- if ($item.is('.ab-active')) {
99
- location.reload(true);
100
- }
101
  });
102
- }
103
- });
 
 
104
 
 
 
 
 
105
 
106
- // Services list delegated events.
107
- $('#ab_services_wrapper')
 
 
108
 
109
- // On click on editable cell.
110
- .on('click', '.editable-cell div.displayed-value', function() {
111
- var $this = $(this);
112
- $this.hide().next('.value').show();
113
- // Fix FF accidental blur of input[type=number].
114
- setTimeout( function() { $this.next('.value').focus(); }, 100 );
115
- })
 
 
 
 
 
 
 
116
 
117
- // On blur of input in editable cell.
118
- .on('blur', '.editable-cell input.value', function() {
119
- var $this = $(this),
120
- field = $this.attr('name'),
121
- value = $this.attr('value'),
122
- id = $this.parents('.service-row').attr('id');
123
- if (value) {
124
- var data = { action: 'ab_update_service_value', id: id };
125
- data[field] = value;
126
- $.post(ajaxurl, data, function(response) {
127
- $this.hide();
128
- $this.prev('.displayed-value').text(value).show();
129
  });
130
- }
131
- })
132
 
133
- // On change in 'Duration' or 'Category' drop-down lists.
134
- .on('change', 'select', function() {
135
- var $this = $(this),
136
- field = $this.attr('name'),
137
- value = $this.val(),
138
- $row = $this.parents('.service-row'),
139
- id = $row.attr('id');
140
- if (value) {
141
- var data = { action: 'ab_update_service_value', id: id };
142
- data[field] = value;
143
- $.post(ajaxurl, data, function(response) {
144
- if ($this.attr('name') == 'category_id') {
145
- var services_category_id = parseInt($('.ab-category-item.ab-active').data('id')),
146
- selected_category_id = parseInt(value);
147
- if (services_category_id && selected_category_id != services_category_id) {
148
- if ($('#services_list > tbody > tr').length == 1) {
149
- $('#services_list > tbody > tr').remove();
150
- $('#services_list').hide();
151
- $no_result.show();
152
- } else {
153
- $row.removeClass('last').prev().addClass('last');
154
- $row.remove();
155
- }
156
  }
157
- }
158
  });
159
- }
160
- })
161
 
162
- // On click on 'Add Service' button.
163
- .on('click', 'a.add-service', function(e) {
164
- e.preventDefault();
165
- var selected_category_id = $('#ab-categories-list .ab-active').data('id'),
166
- data = { action: 'ab_add_service' };
167
- if (selected_category_id) {
168
- data['category_id'] = selected_category_id;
169
- }
170
- $.post(ajaxurl, data, function(response) {
171
- refreshList(response);
172
- });
173
- })
174
 
175
- // On change in `select row` checkbox.
176
- .on('change', 'input.row-checker', function() {
177
- if ($(this).attr('checked')) {
178
- $(this).parents('.service-row').addClass('checked');
179
- } else {
180
- $(this).parents('.service-row').removeClass('checked');
181
- }
182
- })
183
 
184
- // On click on 'Delete' button.
185
- .on('click', 'a.delete', function(e){
186
- e.preventDefault();
187
- var $checked_rows = $('#services_list .service-row.checked');
188
- if (!$checked_rows.length) {
189
- alert(BooklyL10n.please_select_at_least_one_service);
190
- return false;
191
- }
192
- var selected_category_id = $('#ab-categories-list .ab-active').data('id'),
193
- data = { action: 'ab_remove_services' },
194
- row_ids = [];
195
- $checked_rows.each(function() {
196
- row_ids.push($(this).attr('id'));
197
- });
198
- if (selected_category_id) {
199
- data['category_id'] = selected_category_id;
200
- }
201
- data['service_ids[]'] = row_ids;
202
- $.post(ajaxurl, data, function() {
203
- $checked_rows.fadeOut(700, function() {
204
- $(this).each(function() {
205
- if ($(this).hasClass('last')) {
206
- $(this).removeClass('last').prev().addClass('last');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  }
208
- });
209
- $(this).remove();
210
- $('#services_list .service-row').removeClass('even odd').each(function(index, value) {
211
- if (index % 2) {
212
- $(this).addClass('even');
213
- } else {
214
- $(this).addClass('odd');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  }
216
- });
217
- if (!$('#services_list > tbody > tr').length) {
218
- $('#services_list').hide();
219
- $no_result.show();
220
- }
 
 
 
 
 
 
 
221
  });
222
- });
223
- })
 
 
224
 
225
- // On change in `select staff` checkbox.
226
- .on('change', 'input.all-staff, input.staff', function(){
227
- var $this = $(this),
228
- $row = $this.parents('.service-row'),
229
- staff_ids = [],
230
- data = { action: 'ab_assign_staff', service_id: $row.attr('id') };
231
- if ($this.hasClass('all-staff')) {
232
- $row.find('.staff').prop('checked', $this.prop('checked'));
233
- } else {
234
- $row.find('.all-staff').prop(
235
- 'checked',
236
- $row.find('.staff:not(:checked)').length == 0
237
- );
238
- }
239
- $row.find('.staff:checked').each(function(){
240
- staff_ids.push($(this).val());
241
- });
242
- data['staff_ids[]'] = staff_ids;
243
- $.post(ajaxurl, data, function(response) {
244
- if (response) {
245
- $row.find('.staff-count').text(response);
246
- }
247
- });
248
- });
249
 
250
- function refreshList(response) {
251
- var $list = $('#ab-services-list');
252
- $list.html(response);
253
  doNotCloseDropDowns();
254
- initColorPicker($list.find('.service-color'));
 
 
255
 
256
- if (response) {
257
- $no_result.hide();
258
- } else {
259
- $no_result.show();
260
- }
261
- }
262
-
263
- function initColorPicker($jquery_collection) {
264
- $jquery_collection.wpColorPicker({
265
- change: function() {
266
- var data = {
267
- action :'ab_update_service_value',
268
- id : $(this).parents('.service-row').first().attr('id')
269
- };
270
- data['color'] = $(this).wpColorPicker('color');
271
- $.post(ajaxurl, data);
272
- }
273
  });
274
- }
275
 
276
- function doNotCloseDropDowns() {
277
- $('#ab-services-list .dropdown-menu').on('click', function(e) {
278
- e.stopPropagation();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  });
280
- }
281
-
282
- initColorPicker($('.service-color'));
283
- doNotCloseDropDowns();
284
-
285
- $.ajaxSetup({
286
- mode: 'abort',
287
- port: 'ab_service'
288
- });
289
  });
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 ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 CHANGED
@@ -1,60 +1,105 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="ab-title"><?php _e('Services', 'ab') ?></div>
3
- <div style="min-width: 800px;">
4
- <div class="ab-left-bar">
5
- <div id="ab-categories-list">
6
- <div class="ab-category-item ab-active ab-main-category-item" data-id=""><?php _e('All Services','ab') ?></div>
7
- <div class="ab-category-item-list">
8
- <?php if (count($category_collection)): ?>
9
- <?php foreach ($category_collection as $category):?>
10
- <div class="ab-category-item" data-id="<?php echo $category->id ?>">
11
- <span class="left displayed-value"><?php esc_html_e( $category->name ) ?></span>
12
- <a href="#" class="left ab-hidden ab-edit"></a>
13
- <input class="value ab-value" type="text" name="name" value="<?php esc_attr_e( $category->name ) ?>" style="display: none" />
14
- <a href="#" class="left ab-hidden ab-delete"></a>
15
- </div>
16
- <?php endforeach ?>
17
- <?php endif ?>
18
- </div>
19
- </div>
20
- <input type="hidden" id="color" />
21
- <div id="new_category_popup" class="ab-popup-wrapper">
22
- <input class="btn btn-info ab-popup-trigger" data- type="submit" value="<?php _e('New Category','ab') ?>" />
23
- <div class="ab-popup" style="display: none">
24
- <div class="ab-arrow"></div>
25
- <div class="ab-content">
26
- <form method="post" id="new-category-form">
27
- <table class="form-horizontal">
28
- <tr>
29
- <td>
30
- <input class="ab-clear-text" style="width: 170px" type="text" name="name" />
31
- <input type="hidden" name="action" value="ab_category_form" />
32
- </td>
33
- </tr>
34
- <tr>
35
- <td>
36
- <input type="submit" class="btn btn-info ab-popup-save ab-update-button" value="<?php _e('Save category','ab') ?>" />
37
- <a class="ab-popup-close" href="#"><?php _e('Cancel','ab') ?></a>
38
- </td>
39
- </tr>
40
- </table>
41
- <a class="ab-popup-close ab-popup-close-icon" href="#"></a>
42
- </form>
43
- </div>
44
- </div>
45
  </div>
46
- </div>
47
- <div class="ab-right-content" id="ab_services_wrapper">
48
- <h2 class="ab-category-title"><?php _e('All services','ab') ?></h2>
49
- <div class="no-result"<?php if (count($category_collection)) : ?> style="display: none"<?php endif; ?>><?php _e( 'No services found. Please add services.','ab' ) ?></div>
50
- <div class="list-wrapper">
51
- <div id="ab-services-list">
52
- <?php include dirname(__FILE__) . DIRECTORY_SEPARATOR . 'list.php' ?>
53
- </div>
54
- <div class="list-actions">
55
- <a class="add-service btn btn-info" href="#"><?php _e('Add Service','ab') ?></a>
56
- <a class="delete btn btn-info" href="#"><?php _e('Delete','ab') ?></a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  </div>
58
  </div>
59
  </div>
60
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/service/templates/list.php DELETED
@@ -1,31 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <?php if ( count( $service_collection ) ) : ?>
3
- <table class="table table-striped" cellspacing="0" cellpadding="0" border="0" id="services_list">
4
- <thead>
5
- <tr>
6
- <th class="first">&nbsp;</th>
7
- <th><?php echo _e( 'Title', 'ab' ) ?></th>
8
- <th width='95'><?php echo _e( 'Duration', 'ab' ) ?></th>
9
- <th><?php echo _e( 'Price', 'ab' ) ?></th>
10
- <th width='65'><?php echo _e( 'Staff', 'ab' ) ?></th>
11
- <th><?php echo _e( 'Category', 'ab' ) ?></th>
12
- <th class="last">&nbsp;</th>
13
- </tr>
14
- </thead>
15
- <tbody>
16
- <?php
17
- foreach ( $service_collection as $i => $service ) {
18
- $row_class = 'service-row ';
19
- $row_class .= $i % 2 ? 'even' : 'odd';
20
- if ( 0 == $i ) {
21
- $row_class .= ' first';
22
- }
23
- if ( ! isset( $service_collection[$i + 1] ) ) {
24
- $row_class .= ' last';
25
- }
26
- include dirname(__FILE__) . DIRECTORY_SEPARATOR . 'list_item.php';
27
- }
28
- ?>
29
- </tbody>
30
- </table>
31
- <?php endif ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/service/templates/list_item.php DELETED
@@ -1,96 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <tr id="<?php echo $service->id ?>" class="<?php echo $row_class ?>">
3
- <td class="first service-color-cell">
4
- <div class="service-color-wrapper">
5
- <input type="hidden" class="service-color" name="color" value="<?php echo $service->color ?>" />
6
- </div>
7
- </td>
8
- <td class="title editable-cell">
9
- <?php if ( $service->title ) : ?>
10
- <div class="displayed-value"><?php esc_html_e( $service->title ) ?></div>
11
- <input class="value ab-value" type="text" name="title" value="<?php esc_attr_e( $service->title ) ?>" style="display: none" />
12
- <?php else : ?>
13
- <div class="displayed-value" style="display: none"></div>
14
- <input class="value ab-value" type="text" name="title" />
15
- <?php endif; ?>
16
- </td>
17
- <td>
18
- <select name="duration">
19
- <?php
20
- $time_interval = get_option( 'ab_settings_time_slot_length' );
21
- ?>
22
- <!-- Build service duration choices with the range from Time Interval Option to 12. -->
23
- <?php for ( $j = $time_interval; $j <= 720; $j += $time_interval ) : ?>
24
- <?php
25
- $duration = $j * 60;
26
- $duration_output = AB_Service::durationToString( $duration );
27
- $selected = $service->duration == $duration ? ' selected="selected"' : '';
28
- ?>
29
- <option value="<?php echo $duration ?>"<?php echo $selected ?>>
30
- <?php echo $duration_output ?>
31
- </option>
32
- <?php endfor; ?>
33
- </select>
34
- </td>
35
- <td align='right' class="editable-cell price">
36
- <div class="displayed-value ab-rtext"><?php echo $service->price ?></div>
37
- <?php if ( $service->price ) : ?>
38
- <input class="value ab-text-focus" type="number" min="0.00" step="any" name="price" value="<?php esc_attr_e( $service->price ) ?>" style="display: none" />
39
- <?php else : ?>
40
- <input class="value ab-text-focus" type="number" min="0.00" step="any" name="price" />
41
- <?php endif; ?>
42
- </td>
43
- <td>
44
- <?php if ( count( $staff_collection ) ) : ?>
45
- <div class="btn-group">
46
- <?php
47
- $assigned_staff_ids = $service->staff_ids ? explode(',', $service->staff_ids) : array();
48
- $all_staff_selected = count( $assigned_staff_ids ) == count( $staff_collection );
49
- ?>
50
- <button class="btn btn-info"><i class="icon-user icon-white"></i> <span class=staff-count><?php echo $service->total_staff ?></span></button>
51
- <button class="btn btn-info dropdown-toggle" data-toggle="dropdown">
52
- <span class="caret"></span>
53
- </button>
54
- <ul class="dropdown-menu">
55
- <li>
56
- <a href="javascript:void(0)">
57
- <input type="checkbox" id="service_<?php echo $service->id ?>_all_staff" class="all-staff"<?php if ( $all_staff_selected ) : ?> checked="checked" <?php endif; ?> />
58
- <label class="inline" for="service_<?php echo $service->id ?>_all_staff"><?php _e('All staff','ab') ?></label>
59
- </a>
60
- </li>
61
- <?php foreach ( $staff_collection as $i => $staff ) : ?>
62
- <li>
63
- <a href="javascript:void(0)" style="padding-left: 30px">
64
- <?php $staff_checked = in_array( $staff->id, $assigned_staff_ids ) ?>
65
- <input type="checkbox" name="staff_ids[]" class="staff" id="service_<?php echo $service->id ?>_staff_<?php echo $i ?>" value="<?php echo $staff->id ?>"<?php if ( $staff_checked ) : ?> checked="checked"<?php endif; ?>/>
66
- <label class="inline" for="service_<?php echo $service->id ?>_staff_<?php echo $i ?>">
67
- <?php esc_html_e( $staff->full_name ) ?>
68
- </label>
69
- </a>
70
- </li>
71
- <?php endforeach; ?>
72
- </ul>
73
- </div>
74
- <?php else : ?>
75
- &nbsp;
76
- <?php endif; ?>
77
- </td>
78
- <td>
79
- <?php if ( count( $category_collection ) ) : ?>
80
- <select name="category_id">
81
- <option value="0"></option>
82
- <?php foreach ( $category_collection as $category ) : ?>
83
- <?php $selected = $category->id == $service->category_id ? ' selected="selected"' : '' ?>
84
- <option value="<?php echo $category->id ?>"<?php echo $selected ?>>
85
- <?php esc_html_e( $category->name ) ?>
86
- </option>
87
- <?php endforeach; ?>
88
- </select>
89
- <?php else: ?>
90
- &nbsp;
91
- <?php endif; ?>
92
- </td>
93
- <td class="last">
94
- <input type="checkbox" class="row-checker" />
95
- </td>
96
- </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/AB_SettingsController.php CHANGED
@@ -1,153 +1,197 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
-
5
- include 'forms/AB_CompanyForm.php';
6
- include 'forms/AB_PaymentsForm.php';
7
- include 'forms/AB_BusinessHoursForm.php';
8
 
9
  /**
10
  * Class AB_SettingsController
11
  */
12
  class AB_SettingsController extends AB_Controller {
13
 
14
- public function index() {
15
- // save the settings
16
- if ( count( $this->getPost() ) ) {
17
- // Payments form
18
- if ( isset( $_GET[ 'type' ] ) && $_GET[ 'type' ] == '_payments' ) {
19
- $this->form = new AB_PaymentsForm();
20
- $this->message_p = __( 'Settings saved.', 'ab' );
21
-
22
- // Business hours form
23
- } elseif ( isset( $_GET[ 'type' ] ) && $_GET[ 'type' ] == '_hours' ) {
24
- $this->form = new AB_BusinessHoursForm();
25
- $this->message_h = __( 'Settings saved.', 'ab' );
26
- }
27
- // Purchase Code Form
28
- elseif ( isset( $_GET[ 'type' ] ) && $_GET[ 'type' ] == '_purchase_code' ) {
29
- update_option( 'ab_envato_purchase_code', esc_html( $this->getParameter( 'ab_envato_purchase_code' ) ) );
30
- $this->message_pc = __( 'Settings saved.', 'ab' );
31
- } elseif ( isset( $_GET[ 'type' ] ) && $_GET[ 'type' ] == '_general' ) {
32
- $ab_settings_time_slot_length = $this->getParameter( 'ab_settings_time_slot_length' );
33
- if ( in_array( $ab_settings_time_slot_length, array( 10, 15, 20, 30, 60 ) ) ) {
34
- update_option( 'ab_settings_time_slot_length', $ab_settings_time_slot_length );
35
- }
36
- update_option( 'ab_settings_no_current_day_appointments', (int)$this->getParameter( 'ab_settings_no_current_day_appointments' ) );
37
- update_option( 'ab_settings_use_client_time_zone', (int)$this->getParameter( 'ab_settings_use_client_time_zone' ) );
38
- update_option( 'ab_settings_cancel_page_url', $this->getParameter( 'ab_settings_cancel_page_url' ) );
39
- $this->message_g = __( 'Settings saved.', 'ab' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
41
- // Holidays form
42
- elseif ( isset( $_GET[ 'type' ] ) && $_GET[ 'type' ] == '_holidays' ) {
43
- // Company form
44
- } else {
45
- $this->form = new AB_CompanyForm();
46
- $this->message_c = __( 'Settings saved.', 'ab' );
47
- }
48
- if ( isset( $_GET[ 'type' ] ) && $_GET[ 'type' ] != '_purchase_code' && $_GET[ 'type' ] != '_holidays'
49
- && $_GET[ 'type' ] != '_import' && $_GET[ 'type' ] != '_general' ) {
50
- $this->form->bind( $this->getPost(), $_FILES );
51
- $this->form->save();
52
- }
53
- }
54
-
55
- // get holidays
56
- $this->holidays = $this->getHolidays();
57
-
58
- $this->render( 'index' );
59
- } // index
60
-
61
- /**
62
- * Ajax request for Holidays calendar
63
- */
64
- public function executeSettingsHoliday() {
65
- $id = $this->getParameter( 'id', false );
66
- $holiday = $this->getParameter( 'holiday' ) == 'true';
67
- $repeat = $this->getParameter( 'repeat' ) == 'true';
68
- $day = $this->getParameter( 'day', false );
69
-
70
- // update or delete the event
71
- if ( $id ) {
72
- if ( $holiday ) {
73
- $this->getWpdb()->update( 'ab_holiday', array('repeat_event' => intval( $repeat ) ), array( 'id' => $id ), array( '%d' ) );
74
- $this->getWpdb()->update( 'ab_holiday', array( 'repeat_event' => intval( $repeat ) ), array( 'parent_id' => $id ), array( '%d' ) );
75
- } else {
76
- $this->getWpdb()->delete( 'ab_holiday', array( 'id' => $id ), array( '%d' ) );
77
- $this->getWpdb()->delete( 'ab_holiday', array( 'parent_id' => $id ), array( '%d' ) );
78
- }
79
- // add the new event
80
- } elseif ( $holiday && $day ) {
81
- $day = new DateTime( $day );
82
- $this->getWpdb()->insert( 'ab_holiday', array( 'holiday' => $day->format( 'Y-m-d H:i:s' ), 'repeat_event' => intval( $repeat ) ), array( '%s', '%d' ) );
83
- $parent_id = $this->getWpdb()->insert_id;
84
- $staff = $this->getWpdb()->get_results( 'SELECT id FROM ab_staff' );
85
- foreach ( $staff as $employee ) {
86
- $this->getWpdb()->insert( 'ab_holiday',
87
- array(
88
- 'holiday' => date( 'Y-m-d H:i:s', $day->format( 'U' ) ),
89
- 'repeat_event' => intval( $repeat ),
90
- 'staff_id' => $employee->id,
91
- 'parent_id' => $parent_id
92
- ),
93
- array( '%s', '%d', '%d' )
94
- );
95
- }
96
- }
97
-
98
- // and return refreshed events
99
- echo $this->getHolidays();
100
- exit;
101
- }
102
-
103
- protected function getHolidays() {
104
- $collection = $this->getWpdb()->get_results( "SELECT * FROM ab_holiday WHERE staff_id IS NULL" );
105
- $holidays = array();
106
- if ( count( $collection ) ) {
107
- foreach ( $collection as $holiday ) {
108
- $holidays[ $holiday->id ] = array(
109
- 'm' => intval( date( 'm', strtotime( $holiday->holiday ) ) ),
110
- 'd' => intval( date( 'd', strtotime( $holiday->holiday ) ) ),
111
- 'title' => $holiday->title,
112
- );
113
- // if not repeated holiday, add the year
114
- if ( ! $holiday->repeat_event ) {
115
- $holidays[ $holiday->id ][ 'y' ] = intval( date( 'Y', strtotime( $holiday->holiday ) ) );
116
- }
117
- }
118
- }
119
-
120
- return json_encode( (object) $holidays );
121
- }
122
 
123
  /**
124
- * Show admin notice about purchase code and license.
125
  */
126
- public function showAdminNotice() {
127
- global $current_user;
128
-
129
- if ( !get_user_meta( $current_user->ID, 'ab_dismiss_admin_notice', true ) &&
130
- get_option( 'ab_envato_purchase_code' ) == '' &&
131
- time() > get_option( 'ab_installation_time' ) + 7*24*60*60
132
- ) {
133
- $this->render( 'admin_notice' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  }
136
 
137
  /**
138
  * Ajax request to dismiss admin notice for current user.
139
  */
140
- public function executeDismissAdminNotice() {
141
- global $current_user;
 
 
142
 
143
- update_user_meta( $current_user->ID, 'ab_dismiss_admin_notice', 1 );
 
 
 
 
 
 
 
 
144
  }
145
 
146
- /**
147
- * Override parent method to add 'wp_ajax_ab_' prefix
148
- * so current 'execute*' methods look nicer.
149
- */
150
- protected function registerWpActions( $prefix = '' ) {
151
- parent::registerWpActions( 'wp_ajax_ab_' );
152
- }
153
  }
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/forms/AB_BusinessHoursForm.php CHANGED
@@ -1,10 +1,12 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
 
 
 
 
5
  class AB_BusinessHoursForm extends AB_Form {
6
 
7
- public function __construct() {
 
8
  $this->setFields(array(
9
  'ab_settings_monday_start',
10
  'ab_settings_monday_end',
@@ -23,33 +25,38 @@ class AB_BusinessHoursForm extends AB_Form {
23
  ));
24
  }
25
 
26
- public function save() {
27
-
28
  foreach ( $this->data as $field => $value ) {
29
  update_option( $field, $value );
30
  }
31
  }
32
 
33
- public function renderField($field_name = 'ab_settings_monday', $is_start = true) {
34
-
35
- $time_format = get_option( 'time_format' );
36
- $ts_length = get_option( 'ab_settings_time_slot_length' );
37
- $time_output = new DateTime( AB_StaffScheduleItem::WORKING_START_TIME, new DateTimeZone( 'UTC' ) );
38
- $time_end = new DateTime( AB_StaffScheduleItem::WORKING_END_TIME, new DateTimeZone( 'UTC' ) );
39
- $ts_length = '+' . $ts_length . ' min';
 
 
 
40
  $option_name = $field_name . ( $is_start ? '_start' : '_end' );
41
- $class_name = $is_start ? 'select_start' : 'select_end';
42
  $selected_value = get_option( $option_name );
43
- $output = "<select name={$option_name} class={$class_name}>";
44
 
45
- if( $is_start ) $output .= "<option value=''>" . __( 'OFF','ab' ) . "</option>";
 
 
 
46
 
47
  while ( $time_output <= $time_end ) {
48
- $value = $time_output->format( 'H:i' );
49
- $op_name = $time_output->format( $time_format );
50
- $selected = $value == $selected_value ? ' selected="selected"' : '';
51
- $output .= "<option value='{$value}'{$selected}>{$op_name}</option>";
52
- $time_output->modify( $ts_length );
53
  }
54
 
55
  $output .= '</select>';
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',
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>';
backend/modules/settings/forms/AB_CompanyForm.php CHANGED
@@ -1,10 +1,12 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
 
 
 
 
5
  class AB_CompanyForm extends AB_Form {
6
 
7
- public function __construct() {
 
8
  $this->setFields(array(
9
  'ab_settings_company_name',
10
  'ab_settings_company_logo',
@@ -16,31 +18,35 @@ class AB_CompanyForm extends AB_Form {
16
  ));
17
  }
18
 
19
- public function bind( array $post, array $files = array() ) {
20
-
 
 
 
 
21
  parent::bind( $post, $files );
22
 
23
- // remove the old image
24
  if ( isset( $post['ab_remove_logo'] ) && file_exists( get_option( 'ab_settings_company_logo_path' ) ) ) {
25
  unlink( get_option( 'ab_settings_company_logo_path' ) );
26
  update_option( 'ab_settings_company_logo_path', '' );
27
  update_option( 'ab_settings_company_logo_url', '' );
28
  }
29
 
30
- // and add new
31
- if ( isset ($files['ab_settings_company_logo']) && $files['ab_settings_company_logo']['tmp_name'] ) {
32
 
33
- if ( in_array( $files['ab_settings_company_logo']['type'], array( "image/gif", "image/jpeg", "image/png" ) ) ) {
34
- $movefile = wp_handle_upload( $files['ab_settings_company_logo'], array( 'test_form' => false ) );
35
- if ( $movefile ) {
36
- $imageResize = new AB_ImageResize($movefile['file']);
37
- $imageResize->resizeImage( 150, 150 );
38
- $imageResize->saveImage($movefile['file']);
39
 
40
- $this->data['ab_settings_company_logo_path'] = $movefile['file'];
41
- $this->data['ab_settings_company_logo_url'] = $movefile['url'];
42
 
43
- // remove the old image
44
  if ( file_exists( get_option( 'ab_settings_company_logo_path' ) ) ) {
45
  unlink( get_option( 'ab_settings_company_logo_path' ) );
46
  }
@@ -49,8 +55,8 @@ class AB_CompanyForm extends AB_Form {
49
  }
50
  }
51
 
52
- public function save() {
53
-
54
  foreach ( $this->data as $field => $value ) {
55
  update_option( $field, $value );
56
  }
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',
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
  }
55
  }
56
  }
57
 
58
+ public function save()
59
+ {
60
  foreach ( $this->data as $field => $value ) {
61
  update_option( $field, $value );
62
  }
backend/modules/settings/forms/AB_PaymentsForm.php DELETED
@@ -1,19 +0,0 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
-
5
- class AB_PaymentsForm extends AB_Form {
6
-
7
- public function __construct() {
8
- $this->setFields(array(
9
- 'ab_settings_pay_locally',
10
- 'ab_paypal_currency'
11
- ));
12
- }
13
-
14
- public function save() {
15
- foreach ( $this->data as $field => $value ) {
16
- update_option( $field, $value );
17
- }
18
- }
19
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/resources/js/settings.js CHANGED
@@ -1,7 +1,12 @@
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_general'),
4
- $all_forms = $('#company-form, #payments-form, #hours-form, #holidays-form, #purchase-code-form, #general-form');
 
 
 
 
 
5
  $('.select_start', $form).on('change', function () {
6
  var $row = $(this).parent(),
7
  $end_select = $('.select_end', $row),
@@ -45,8 +50,9 @@ jQuery(function ($) {
45
  }
46
  }).trigger('change');
47
 
48
- // Reset
49
- $('#ab-hours-reset', $form).on('click', function () {
 
50
  $('.select_start', $form).each(function () {
51
  $(this).val($(this).data('default_value'));
52
  $(this).trigger('click');
@@ -59,7 +65,7 @@ jQuery(function ($) {
59
  $('.select_start', $form).trigger('change');
60
  });
61
 
62
- // Tabs Onclick Handlers
63
  $all_tabs.on('click', function() {
64
  $('.ab-active').removeClass('ab-active');
65
  $(this).addClass('ab-active');
@@ -88,6 +94,38 @@ jQuery(function ($) {
88
  $all_forms.addClass('hidden');
89
  $('#general-form').removeClass('hidden');
90
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
  // Company Tab
93
  $('#ab-delete-logo').on('click', function() {
@@ -100,17 +138,29 @@ jQuery(function ($) {
100
  $('#ab-show-logo').show(300);
101
  });
102
 
103
- //Payment Tab
104
- $('#ab_paypal_type').on('change', function() {
105
- $('#light_notice').modal('show');
106
  });
107
 
108
- $('.ab-help-info').show(function () {
109
- $(this).popover({
110
- trigger : 'hover',
111
- content: $('#' + $(this).data('help_id')).html(),
112
- html: true
113
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  });
115
- });
116
 
 
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),
50
  }
51
  }).trigger('change');
52
 
53
+ // Reset.
54
+ $('#ab-hours-reset', $form).on('click', function ( e ) {
55
+ e.preventDefault();
56
  $('.select_start', $form).each(function () {
57
  $(this).val($(this).data('default_value'));
58
  $(this).trigger('click');
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');
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() {
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
+ });
backend/modules/settings/templates/_companyForm.php CHANGED
@@ -1,25 +1,21 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <form method="post" action="<?php echo $current_url . '&type=_company' ?>" enctype="multipart/form-data" class="ab-staff-form">
3
-
4
- <?php if (isset($message_c)) : ?>
5
- <div id="message" style="margin: 0px!important;" class="updated below-h2">
6
- <button type="button" class="close" data-dismiss="alert">&times;</button>
7
- <p><?php echo $message_c ?></p>
8
- </div>
9
- <?php endif ?>
10
-
11
  <table class="form-horizontal">
12
  <tr>
13
- <td><?php _e('Company name','ab') ?></td>
14
- <td><input type="text" size="33" name="ab_settings_company_name" value="<?php echo get_option('ab_settings_company_name') ?>" reset="<?php echo get_option('ab_settings_company_name') ?>"/></td>
 
 
15
  </tr>
16
  <tr>
17
- <td valign="top"><?php _e('Company logo','ab') ?></td>
 
 
18
  <td>
19
  <?php if ( get_option( 'ab_settings_company_logo_url' ) ): ?>
20
  <div id="ab-show-logo">
21
- <img src="<?php echo get_option( 'ab_settings_company_logo_url' ) ?>" alt="<?php _e( 'Company logo','ab' ) ?>"/>
22
- <a id="ab-delete-logo" href="javascript:void(0)"><?php _e( 'Delete','ab' ) ?></a>
23
  <br/>
24
  </div>
25
  <?php endif ?>
@@ -27,22 +23,28 @@
27
  </td>
28
  </tr>
29
  <tr>
30
- <td valign="top"><?php _e('Address','ab') ?></td>
31
- <td><textarea cols="32" rows="5" name="ab_settings_company_address"><?php echo get_option('ab_settings_company_address') ?></textarea></td>
 
 
32
  </tr>
33
  <tr>
34
- <td><?php _e('Phone','ab') ?></td>
35
- <td><input type="text" size="33" name="ab_settings_company_phone" value="<?php echo get_option('ab_settings_company_phone') ?>" /></td>
 
 
36
  </tr>
37
  <tr>
38
- <td><?php _e('Website','ab') ?></td>
39
- <td><input type="text" size="33" name="ab_settings_company_website" value="<?php echo get_option('ab_settings_company_website') ?>" /></td>
 
 
40
  </tr>
41
  <tr>
42
  <td></td>
43
  <td>
44
- <input type="submit" value="<?php _e( 'Save', 'ab' ) ?>" class="btn btn-info ab-update-button" />
45
- <button id="ab-settings-company-reset" class="ab-reset-form" type="reset"><?php _e( ' Reset ', 'ab' ) ?></button>
46
  </td>
47
  </tr>
48
  </table>
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 ?>
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>
backend/modules/settings/templates/_customers.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <form method="post" action="<?php echo esc_url( add_query_arg( 'type', '_customers' ) ) ?>" enctype="multipart/form-data" class="ab-settings-form">
3
+ <table class="form-horizontal">
4
+ <tr>
5
+ <td>
6
+ <label for="ab_settings_create_account"><?php _e( 'Create WordPress user account for customers', 'bookly' ) ?></label>
7
+ </td>
8
+ <td>
9
+ <?php AB_Utils::optionToggle( 'ab_settings_create_account' ) ?>
10
+ </td>
11
+ <td>
12
+ <?php AB_Utils::popover( __( 'If this setting is enabled then Bookly will be creating WordPress user accounts for all new customers. If the user is logged in then the new customer will be associated with the existing user account.', 'bookly' ) ) ?>
13
+ </td>
14
+ </tr>
15
+ <tr>
16
+ <td>
17
+ <label for="ab_settings_phone_default_country"><?php _e( 'Phone field default country', 'bookly' ) ?></label>
18
+ </td>
19
+ <td class="ab-valign-top">
20
+ <select class="form-control" name="ab_settings_phone_default_country" id="ab_settings_phone_default_country" data-country="<?php echo get_option( 'ab_settings_phone_default_country' ) ?>">
21
+ <option value="auto"><?php _e( 'Guess country by user\'s IP address', 'bookly' ) ?></option>
22
+ <option disabled><?php echo str_repeat( '&#9472;', 30 ) ?></option>
23
+ </select>
24
+ </td>
25
+ <td>
26
+ <?php AB_Utils::popover( __( 'Select default country for the phone field in the \'Details\' step of booking. You can also let Bookly determine the country based on the IP address of the client.', 'bookly' ) ) ?>
27
+ </td>
28
+ </tr>
29
+ <tr>
30
+ <td>
31
+ <label for="ab_sms_default_country_code"><?php _e( 'Default country code', 'bookly' ) ?></label>
32
+ </td>
33
+ <td>
34
+ <input type="text" name="ab_sms_default_country_code" id="ab_sms_default_country_code" value="<?php echo esc_attr( get_option( 'ab_sms_default_country_code' ) ) ?>" class="form-control" />
35
+ </td>
36
+ <td>
37
+ <?php AB_Utils::popover( __( 'Your clients must have their phone numbers in international format in order to receive text messages. However you can specify a default country code that will be used as a prefix for all phone numbers that do not start with "+" or "00". E.g. if you enter "1" as the default country code and a client enters their phone as "(600) 555-2222" the resulting phone number to send the SMS to will be "+1600555222".', 'bookly' ) ) ?>
38
+ </td>
39
+ </tr>
40
+ <tr>
41
+ <td></td>
42
+ <td>
43
+ <?php AB_Utils::submitButton() ?>
44
+ <?php AB_Utils::resetButton( 'ab-payments-reset' ) ?>
45
+ </td>
46
+ </tr>
47
+ </table>
48
+ </form>
backend/modules/settings/templates/_generalForm.php CHANGED
@@ -1,103 +1,122 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <form method="post" action="<?php echo $current_url . '&type=_general' ?>" enctype="multipart/form-data" class="ab-staff-form">
3
-
4
- <?php if (isset($message_g)) : ?>
5
- <div id="message" style="margin: 0px!important;" class="updated below-h2">
6
- <button type="button" class="close" data-dismiss="alert">&times;</button>
7
- <p><?php echo $message_g ?></p>
8
- </div>
9
- <?php endif ?>
10
-
11
  <table class="form-horizontal">
12
  <tr>
13
- <td><?php _e('Time slot length','ab') ?></td>
14
  <td>
15
- <select name="ab_settings_time_slot_length" style="width: 200px;">
 
 
 
16
  <?php
17
- foreach ( array( 10, 15, 20, 30, 60 ) as $duration ) {
18
- $duration_output = AB_Service::durationToString( $duration * 60 );
19
- ?>
20
- <option value="<?php echo $duration ?>" <?php selected( get_option( 'ab_settings_time_slot_length' ), $duration ); ?>>
21
- <?php echo $duration_output ?>
22
- </option>
23
  <?php } ?>
24
  </select>
25
- </td>
26
- <td>
27
- <img
28
- src="<?php echo plugins_url( 'resources/images/help.png', dirname(__FILE__).'/../../../AB_Backend.php' ) ?>"
29
- alt=""
30
- class="ab-help-info"
31
- data-help_id="ab-staff-slot"
32
- rel='popover'
33
- />
34
  </td>
35
  </tr>
36
  <tr>
37
  <td>
38
- <label><?php _e( 'Make it impossible for users to book appointments on the current day (last minute appointments)', 'ab' ) ?></label>
39
  </td>
40
- <td style="vertical-align: top">
41
- <select name="ab_settings_no_current_day_appointments" style="width: 200px;">
42
- <?php foreach ( array( __( 'Disabled', 'ab' ) => '0', __( 'Enabled', 'ab' ) => '1' ) as $text => $mode ): ?>
43
- <option value="<?php echo $mode ?>" <?php selected( get_option( 'ab_settings_no_current_day_appointments' ), $mode ); ?> ><?php echo $text ?></option>
44
- <?php endforeach ?>
45
- </select>
 
 
 
 
46
  </td>
47
  </tr>
48
  <tr>
49
  <td>
50
- <label><?php _e( 'Display available time slots in client\'s time zone', 'ab' ) ?></label>
 
 
 
 
 
 
51
  </td>
 
 
52
  <td>
53
- <select name="ab_settings_use_client_time_zone" style="width: 200px;">
54
- <?php foreach ( array( __( 'Disabled', 'ab' ) => '0', __( 'Enabled', 'ab' ) => '1' ) as $text => $mode ): ?>
55
- <option value="<?php echo $mode ?>" <?php selected( get_option( 'ab_settings_use_client_time_zone' ), $mode ); ?> ><?php echo $text ?></option>
56
- <?php endforeach ?>
57
- </select>
 
 
58
  </td>
 
 
59
  <td>
60
- <img
61
- src="<?php echo plugins_url( 'resources/images/help.png', dirname(__FILE__).'/../../../AB_Backend.php' ) ?>"
62
- alt=""
63
- class="ab-help-info"
64
- data-help_id="ab-staff-zone"
65
- rel='popover'
66
- />
67
  </td>
68
  </tr>
69
  <tr>
70
  <td>
71
- <label><?php _e( 'Cancel appointment page URL', 'ab' ) ?></label>
 
 
 
 
 
 
 
 
 
 
 
72
  </td>
 
 
73
  <td>
74
- <input type="text" name="ab_settings_cancel_page_url" value="<?php echo get_option( 'ab_settings_cancel_page_url' ) ?>" >
 
 
 
75
  </td>
 
 
 
 
 
76
  <td>
77
- <img
78
- src="<?php echo plugins_url( 'resources/images/help.png', dirname(__FILE__).'/../../../AB_Backend.php' ) ?>"
79
- alt=""
80
- class="ab-help-info"
81
- data-help_id="ab-staff-cancel"
82
- rel='popover'
83
- />
 
 
 
84
  </td>
85
  </tr>
86
  <tr>
87
  <td></td>
88
- <td>
89
- <input type="submit" value="<?php _e( 'Save', 'ab' ) ?>" class="btn btn-info ab-update-button" />
90
- <button class="ab-reset-form" type="reset"><?php _e( ' Reset ', 'ab' ) ?></button>
91
  </td>
92
  </tr>
93
  </table>
94
- </form>
95
- <div id="ab-staff-slot" style="display: none">
96
- <p><?php _e('Select the time interval that will be used in frontend and backend, e.g. in calendar, second step of the booking process, while indicating the working hours, etc.', 'ab') ?></p>
97
- </div>
98
- <div id="ab-staff-zone" style="display: none">
99
- <p><?php _e('The value is taken from client’s browser.', 'ab') ?></p>
100
- </div>
101
- <div id="ab-staff-cancel" style="display: none">
102
- <p><?php _e('Insert the URL of the page that is shown to clients after they have cancelled their booking.', 'ab') ?></p>
103
- </div>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <form method="post" action="<?php echo esc_url( add_query_arg( 'type', '_general' ) ) ?>" enctype="multipart/form-data" class="ab-settings-form">
 
 
 
 
 
 
 
 
3
  <table class="form-horizontal">
4
  <tr>
 
5
  <td>
6
+ <label for="ab_settings_time_slot_length"><?php _e( 'Time slot length', 'bookly' ) ?></label>
7
+ </td>
8
+ <td class="ab-valign-top">
9
+ <select class="form-control" name="ab_settings_time_slot_length" id="ab_settings_time_slot_length">
10
  <?php
11
+ foreach ( array( 5, 10, 12, 15, 20, 30, 60, 90, 120, 180, 240, 360 ) as $duration ) {
12
+ $duration_output = AB_DateTimeUtils::secondsToInterval( $duration * 60 );
13
+ ?>
14
+ <option value="<?php echo $duration ?>" <?php selected( get_option( 'ab_settings_time_slot_length' ), $duration ) ?>>
15
+ <?php echo $duration_output ?>
16
+ </option>
17
  <?php } ?>
18
  </select>
19
+ </td>
20
+ <td class="ab-valign-top">
21
+ <?php AB_Utils::popover( __( 'Select the time interval that will be used in frontend and backend, e.g. in calendar, second step of the booking process, while indicating the working hours, etc.', 'bookly' ) ) ?>
 
 
 
 
 
 
22
  </td>
23
  </tr>
24
  <tr>
25
  <td>
26
+ <label for="ab_settings_minimum_time_prior_booking"><?php _e( 'Minimum time requirement prior to booking', 'bookly' ) ?></label>
27
  </td>
28
+ <td class="ab-valign-top">
29
+ <select class="form-control" name="ab_settings_minimum_time_prior_booking" id="ab_settings_minimum_time_prior_booking">
30
+ <option value="0" <?php selected( get_option( 'ab_settings_minimum_time_prior_booking' ), 0 ) ?>><?php _e( 'Disabled', 'bookly' ) ?></option>
31
+ <?php foreach ( array_merge( range( 1, 12 ), array( 24, 48 ) ) as $hour ): ?>
32
+ <option value="<?php echo $hour ?>" <?php selected( get_option( 'ab_settings_minimum_time_prior_booking' ), $hour ) ?>><?php echo AB_DateTimeUtils::secondsToInterval( $hour * 3600 ) ?></option>
33
+ <?php endforeach ?>
34
+ </select>
35
+ </td>
36
+ <td class="ab-valign-top">
37
+ <?php AB_Utils::popover( __( 'Set a minimum amount of time before the chosen appointment (for example, require the customer to book at least 1 hour before the appointment time).', 'bookly' ) ) ?>
38
  </td>
39
  </tr>
40
  <tr>
41
  <td>
42
+ <label for="ab_settings_maximum_available_days_for_booking"><?php _e( 'Number of days available for booking', 'bookly' ) ?></label>
43
+ </td>
44
+ <td class="ab-valign-top">
45
+ <input class="form-control" type="number" id="ab_settings_maximum_available_days_for_booking" name="ab_settings_maximum_available_days_for_booking" min="1" max="365" value="<?php echo esc_attr( get_option( 'ab_settings_maximum_available_days_for_booking', 365 ) ) ?>" />
46
+ </td>
47
+ <td class="ab-valign-top">
48
+ <?php AB_Utils::popover( __( 'Specify the number of days that should be available for booking at step 2 starting from the current day.', 'bookly' ) ) ?>
49
  </td>
50
+ </tr>
51
+ <tr>
52
  <td>
53
+ <label for="ab_settings_use_client_time_zone"><?php _e( 'Display available time slots in client\'s time zone', 'bookly' ) ?></label>
54
+ </td>
55
+ <td class="ab-valign-top">
56
+ <?php AB_Utils::optionToggle( 'ab_settings_use_client_time_zone' ) ?>
57
+ </td>
58
+ <td class="ab-valign-top">
59
+ <?php AB_Utils::popover( __( 'The value is taken from client’s browser.', 'bookly' ) ) ?>
60
  </td>
61
+ </tr>
62
+ <tr>
63
  <td>
64
+ <label for="ab_settings_cancel_page_url"><?php _e( 'Cancel appointment page URL', 'bookly' ) ?></label>
65
+ </td>
66
+ <td class="ab-valign-top">
67
+ <input class="form-control" type="text" name="ab_settings_cancel_page_url" id="ab_settings_cancel_page_url" value="<?php echo esc_attr( get_option( 'ab_settings_cancel_page_url' ) ) ?>" placeholder="<?php echo esc_attr( __( 'Enter a URL', 'bookly' ) ) ?>" />
68
+ </td>
69
+ <td class="ab-valign-top">
70
+ <?php AB_Utils::popover( __( 'Insert a URL of a page that is shown to clients after they have cancelled their booking.', 'bookly' ) ) ?>
71
  </td>
72
  </tr>
73
  <tr>
74
  <td>
75
+ <label for="ab_settings_final_step_url_mode"><?php _e( 'Final step URL', 'bookly' ) ?></label>
76
+ </td>
77
+ <td class="ab-valign-top">
78
+ <select class="form-control" id="ab_settings_final_step_url_mode">
79
+ <?php foreach ( array( __( 'Disabled', 'bookly' ) => 0, __( 'Enabled', 'bookly' ) => 1 ) as $text => $mode ): ?>
80
+ <option value="<?php echo esc_attr( $mode ) ?>" <?php selected( get_option( 'ab_settings_final_step_url' ), $mode ) ?> ><?php echo $text ?></option>
81
+ <?php endforeach ?>
82
+ </select>
83
+ <input class="form-control" style="margin-top: 5px; <?php echo get_option( 'ab_settings_final_step_url' ) == ''? 'display: none':''; ?>" type="text" name="ab_settings_final_step_url" value="<?php echo esc_attr( get_option( 'ab_settings_final_step_url' ) ) ?>" placeholder="<?php echo esc_attr( __( 'Enter a URL', 'bookly' ) ) ?>" />
84
+ </td>
85
+ <td class="ab-valign-top">
86
+ <?php AB_Utils::popover( __( 'Set a URL of a page that the user will be forwarded to after successful booking. If disabled then the default step 5 is displayed.', 'bookly' ) ) ?>
87
  </td>
88
+ </tr>
89
+ <tr>
90
  <td>
91
+ <label for="ab_settings_allow_staff_members_edit_profile"><?php _e( 'Allow staff members to edit their profiles', 'bookly' ) ?></label>
92
+ </td>
93
+ <td class="ab-valign-top">
94
+ <?php AB_Utils::optionToggle( 'ab_settings_allow_staff_members_edit_profile' ) ?>
95
  </td>
96
+ <td class="ab-valign-top">
97
+ <?php AB_Utils::popover( __( 'If this option is enabled then all staff members who are associated with WordPress users will be able to edit their own profiles, services, schedule and days off.', 'bookly' ) ) ?>
98
+ </td>
99
+ </tr>
100
+ <tr>
101
  <td>
102
+ <label for="ab_settings_link_assets_method"><?php _e( 'Method to include Bookly JavaScript and CSS files on the page', 'bookly' ) ?></label>
103
+ </td>
104
+ <td class="ab-valign-top">
105
+ <select class="form-control" name="ab_settings_link_assets_method" id="ab_settings_link_assets_method">
106
+ <option value="enqueue" <?php selected( get_option( 'ab_settings_link_assets_method' ), 'enqueue' ) ?>>Enqueue</option>
107
+ <option value="print" <?php selected( get_option( 'ab_settings_link_assets_method' ), 'print' ) ?>>Print</option>
108
+ </select>
109
+ </td>
110
+ <td class="ab-valign-top">
111
+ <?php AB_Utils::popover( __( 'With "Enqueue" method the JavaScript and CSS files of Bookly will be included on all pages of your website. This method should work with all themes. With "Print" method the files will be included only on the pages which contain Bookly booking form. This method may not work with all themes.', 'bookly' ) ) ?>
112
  </td>
113
  </tr>
114
  <tr>
115
  <td></td>
116
+ <td colspan="2">
117
+ <?php AB_Utils::submitButton() ?>
118
+ <?php AB_Utils::resetButton() ?>
119
  </td>
120
  </tr>
121
  </table>
122
+ </form>
 
 
 
 
 
 
 
 
 
backend/modules/settings/templates/_googleCalendarForm.php ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <form method="post" action="<?php echo esc_url( add_query_arg( 'type', '_google_calendar' ) ) ?>" enctype="multipart/form-data" class="ab-settings-form">
3
+ <table class="form-horizontal">
4
+ <tr>
5
+ <td colspan="3">
6
+ <fieldset class="ab-instruction">
7
+ <legend><?php _e( 'Instructions', 'bookly' ) ?></legend>
8
+ <div>
9
+ <div style="margin-bottom: 10px">
10
+ <?php _e( 'To find your client ID and client secret, do the following:', 'bookly' ) ?>
11
+ </div>
12
+ <ol>
13
+ <li><?php _e( 'Go to the <a href="https://console.developers.google.com/" target="_blank">Google Developers Console</a>.', 'bookly' ) ?></li>
14
+ <li><?php _e( 'Select a project, or create a new one.', 'bookly' ) ?></li>
15
+ <li><?php _e( 'In the sidebar on the left, expand <b>APIs & auth</b>. Next, click <b>APIs</b>. In the list of APIs, make sure the status is <b>ON</b> for the Google Calendar API.', 'bookly' ) ?></li>
16
+ <li><?php _e( 'In the sidebar on the left, select <b>Credentials</b>.', 'bookly' ) ?></li>
17
+ <li><?php _e( 'Create your project\'s OAuth 2.0 credentials by clicking <b>Create new Client ID</b>, selecting <b>Web application</b>, and providing the information needed to create the credentials. For <b>AUTHORIZED REDIRECT URIS</b> enter the <b>Redirect URI</b> found below on this page.', 'bookly' ) ?></li>
18
+ <li><?php _e( 'Look for the <b>Client ID</b> and <b>Client secret</b> in the table associated with each of your credentials.', 'bookly' ) ?></li>
19
+ <li><?php _e( 'Go to Staff Members, select a staff member and click on "Connect" which is located at the bottom of the page.', 'bookly' ) ?></li>
20
+ </ol>
21
+ </div>
22
+ </fieldset>
23
+ </td>
24
+ </tr>
25
+ <tr>
26
+ <td colspan="3">
27
+ <div class="ab-payments-title"><?php _e( 'Google Calendar', 'bookly' ) ?></div>
28
+ </td>
29
+ </tr>
30
+ <tr>
31
+ <td>
32
+ <label for="ab_settings_google_client_id"><?php _e( 'Client ID', 'bookly' ) ?></label>
33
+ </td>
34
+ <td>
35
+ <input id="ab_settings_google_client_id" class="form-control" type="text" />
36
+ </td>
37
+ <td>
38
+ <?php AB_Utils::popover( __( 'The client ID obtained from the Developers Console', 'bookly' ) ) ?>
39
+ </td>
40
+ </tr>
41
+ <tr>
42
+ <td>
43
+ <label for="ab_settings_google_client_secret"><?php _e( 'Client secret', 'bookly' ) ?></label>
44
+ </td>
45
+ <td>
46
+ <input id="ab_settings_google_client_secret" class="form-control" type="text" />
47
+ </td>
48
+ <td>
49
+ <?php AB_Utils::popover( __( 'The client secret obtained from the Developers Console', 'bookly' ) ) ?>
50
+ </td>
51
+ </tr>
52
+ <tr>
53
+ <td>
54
+ <label for="ab_redirect_uri"><?php _e( 'Redirect URI', 'bookly' ) ?></label>
55
+ </td>
56
+ <td>
57
+ <input id="ab_redirect_uri" class="form-control" type="text" readonly value="<?php echo admin_url( 'admin.php' ) . '?page='.AB_StaffController::page_slug ?>" onclick="this.select();" style="cursor: pointer;" />
58
+ </td>
59
+ <td>
60
+ <?php AB_Utils::popover( __( 'Enter this URL as a redirect URI in the Developers Console', 'bookly' ) ) ?>
61
+ </td>
62
+ </tr>
63
+ <tr>
64
+ <td>
65
+ <label for="ab_settings_google_two_way_sync"><?php _e( '2 way sync', 'bookly' ) ?></label>
66
+ </td>
67
+ <td>
68
+ <?php AB_Utils::optionToggle( 'ab_settings_google_two_way_sync', array( 'f' => array( '0', __( 'Disabled', 'bookly' ), 't' => array( '1', __( 'Enabled', 'bookly' ) ) ) ) ) ?>
69
+ </td>
70
+ <td>
71
+ <?php AB_Utils::popover( __( 'By default Bookly pushes new appointments and any further changes to Google Calendar. If you enable this option then Bookly will fetch events from Google Calendar and remove corresponding time slots before displaying the second step of the booking form (this may lead to a delay when users click Next at the first step).', 'bookly' ) ) ?>
72
+ </td>
73
+ </tr>
74
+ <tr>
75
+ <td>
76
+ <label for="ab_settings_google_limit_events"><?php _e( 'Limit number of fetched events', 'bookly' ) ?></label>
77
+ </td>
78
+ <td>
79
+ <select id="ab_settings_google_limit_events" class="form-control">
80
+ <?php foreach ( array( __( 'Disabled', 'bookly' ) => '0', 25 => 25, 50 => 50, 100 => 100, 250 => 250, 500 => 500, 1000 => 1000, 2500 => 2500 ) as $text => $limit ): ?>
81
+ <option value="<?php echo $limit ?>" <?php selected( get_option( 'ab_settings_google_limit_events' ), $limit ) ?> ><?php echo $text ?></option>
82
+ <?php endforeach ?>
83
+ </select>
84
+ </td>
85
+ <td>
86
+ <?php AB_Utils::popover( __( 'If there is a lot of events in Google Calendar sometimes this leads to a lack of memory in PHP when Bookly tries to fetch all events. You can limit the number of fetched events here. This only works when 2 way sync is enabled.', 'bookly' ) ) ?>
87
+ </td>
88
+ </tr>
89
+ <tr>
90
+ <td>
91
+ <label for="ab_settings_google_event_title"><?php _e( 'Template for event title', 'bookly' ) ?></label>
92
+ </td>
93
+ <td>
94
+ <input id="ab_settings_google_event_title" class="form-control" type="text" value="[[SERVICE_NAME]]" >
95
+ </td>
96
+ <td>
97
+ <?php AB_Utils::popover( __( 'Configure what information should be places in the title of Google Calendar event. Available codes are [[SERVICE_NAME]], [[STAFF_NAME]] and [[CLIENT_NAMES]].', 'bookly' ) ) ?>
98
+ </td>
99
+ </tr>
100
+
101
+ <tr>
102
+ <td></td>
103
+ <td>
104
+ <?php AB_Utils::submitButton() ?>
105
+ <?php AB_Utils::resetButton() ?>
106
+ </td>
107
+ <td></td>
108
+ </tr>
109
+ </table>
110
+ </form>
backend/modules/settings/templates/_holidaysForm.php CHANGED
@@ -1,10 +1,10 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div id="ab-annual-calendar-scroll" style="text-align: center;">
3
- <div class="input-prepend input-append">
4
- <span class="ab-week-picker-arrow prev add-on col-arrow">◄</span>
5
- <input style="width: 70px; text-align: center;background: white" class="span2 jcal_year" readonly="readonly" id="appendedPrependedInput" size="16" type="text" value="2014">
6
- <span class="ab-week-picker-arrow next add-on col-arrow">►</span>
7
- </div>
8
  </div>
9
  <div id="ab-annual-calendar"></div>
10
 
@@ -12,13 +12,13 @@
12
  jQuery(function($) {
13
  var d = new Date();
14
  $('#ab-annual-calendar').jCal({
15
- day: new Date(d.getFullYear(), 0, 1),
16
- days: 1,
17
- showMonths: 12,
18
  scrollSpeed: 350,
19
  events: <?php echo $holidays ?>,
20
  action: 'ab_settings_holiday',
21
- dayOffset: <?php echo get_option('start_of_week', 0) ?>
22
  });
23
  });
24
  </script>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div id="ab-annual-calendar-scroll" style="text-align: center; max-width: 715px;">
3
+ <div class="input-prepend input-append form-inline">
4
+ <span class="prev glyphicon glyphicon-triangle-left"></span>
5
+ <input style="width: 70px; text-align: center;background: white" class="jcal_year form-control" readonly="readonly" id="appendedPrependedInput" size="16" type="text" value="">
6
+ <span class="next glyphicon glyphicon-triangle-right"></span>
7
+ </div>
8
  </div>
9
  <div id="ab-annual-calendar"></div>
10
 
12
  jQuery(function($) {
13
  var d = new Date();
14
  $('#ab-annual-calendar').jCal({
15
+ day: new Date(d.getFullYear(), 0, 1),
16
+ days: 1,
17
+ showMonths: 12,
18
  scrollSpeed: 350,
19
  events: <?php echo $holidays ?>,
20
  action: 'ab_settings_holiday',
21
+ dayOffset: <?php echo (int) get_option( 'start_of_week' ) ?>
22
  });
23
  });
24
  </script>
backend/modules/settings/templates/_hoursForm.php CHANGED
@@ -1,30 +1,30 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <form method="post" action="<?php echo $current_url . '&type=_hours' ?>" class="ab-staff-form" id="business-hours">
3
- <?php if (isset($message_h)) : ?>
4
- <div class="alert">
5
- <button type="button" class="close" data-dismiss="alert">&times;</button>
6
- <?php echo $message_h ?>
7
- </div>
8
- <?php endif ?>
9
-
10
- <?php $form = new AB_BusinessHoursForm(); ?>
11
-
12
- <table>
13
- <?php foreach ( array( 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday' ) as $day ): ?>
14
  <tr>
15
- <td><?php _e( ucfirst( $day ) ) ?></td>
16
  <td>
17
- <?php echo $form->renderField( 'ab_settings_' . $day ); ?>
18
- <span><?php _e( ' to ' ) ?></span>
19
- <?php echo $form->renderField( 'ab_settings_' . $day, false ); ?>
 
 
 
20
  </td>
21
  </tr>
22
- <?php endforeach; ?>
 
23
  <tr>
24
  <td></td>
25
  <td>
26
- <input type="submit" value="<?php _e( 'Save', 'ab' ) ?>" class="btn btn-info ab-update-button" />
27
- <a id="ab-hours-reset" href="javascript:void(0)"><?php _e( 'Reset', 'ab' ) ?></a>
28
  </td>
29
  </tr>
30
  </table>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ $start_of_week = (int) get_option( 'start_of_week' );
3
+ ?>
4
+ <form method="post" action="<?php echo esc_url( add_query_arg( 'type', '_hours' ) ) ?>" class="ab-settings-form" id="business-hours">
5
+ <?php $form = new AB_BusinessHoursForm() ?>
6
+ <table class="form-inline">
7
+ <tbody>
8
+ <?php for( $i = 0; $i < 7; $i++):
9
+ $day = strtolower( AB_DateTimeUtils::getWeekDayByNumber( ( $i + $start_of_week ) % 7 ) );
10
+ ?>
 
 
 
11
  <tr>
 
12
  <td>
13
+ <label><?php _e( ucfirst( $day ) ) ?> </label>
14
+ </td>
15
+ <td>
16
+ <?php echo $form->renderField( 'ab_settings_' . $day ) ?>
17
+ <span>&nbsp;<?php _e( 'to', 'bookly' ) ?>&nbsp;</span>
18
+ <?php echo $form->renderField( 'ab_settings_' . $day, false ) ?>
19
  </td>
20
  </tr>
21
+ <?php endfor ?>
22
+ </tbody>
23
  <tr>
24
  <td></td>
25
  <td>
26
+ <?php AB_Utils::submitButton() ?>
27
+ <?php AB_Utils::resetButton( 'ab-hours-reset' ) ?>
28
  </td>
29
  </tr>
30
  </table>
backend/modules/settings/templates/_paymentsForm.php CHANGED
@@ -1,68 +1,64 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <form method="post" action="<?php echo $current_url . '&type=_payments' ?>" class="ab-staff-form">
3
- <?php if (isset($message_p)) : ?>
4
- <div id="message" style="margin: 0px!important;" class="updated below-h2">
5
- <button type="button" class="close" data-dismiss="alert">&times;</button>
6
- <p><?php echo $message_p ?></p>
7
- </div>
8
- <?php endif ?>
9
  <table class="form-horizontal">
10
- <tr>
11
- <td style="width: 170px;"><?php _e( 'Currency','ab' ) ?></td>
12
- <td>
13
- <select name="ab_paypal_currency" style="width: 200px;">
14
- <?php foreach ( PayPal::getCurrencyCodes() as $code ): ?>
15
- <option value="<?php echo $code ?>" <?php selected( get_option( 'ab_paypal_currency' ), $code ); ?> ><?php echo $code ?></option>
16
- <?php endforeach ?>
17
- </select>
18
- </td>
19
- </tr>
20
  <tr>
21
- <td colspan="2"><div class="ab-payments-title"><?php _e( 'Service paid locally','ab' ) ?></div></td>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  </tr>
23
  <tr>
24
  <td colspan="2">
25
- <select name="ab_settings_pay_locally" style="width: 200px;">
26
- <?php foreach ( array( __( 'Disabled', 'ab' ) => '0', __( 'Enabled', 'ab' ) => '1' ) as $text => $mode ): ?>
27
- <option value="<?php echo $mode ?>" <?php selected( get_option( 'ab_settings_pay_locally' ), $mode ); ?> ><?php echo $text ?></option>
28
- <?php endforeach ?>
29
- </select>
30
  </td>
31
  </tr>
32
  <tr>
33
- <td colspan="2"><div class="ab-payments-title"><?php _e( 'PayPal','ab' ) ?></div></td>
34
  </tr>
35
  <tr>
36
  <td colspan="2">
37
- <select id="ab_paypal_type" style="width: 200px;">
38
- <?php foreach ( array( __( 'Disabled', 'ab' ) => 'disabled', 'PayPal Express Checkout' => 'ec' ) as $text => $mode ): ?>
39
- <option value="<?php echo $mode ?>" ><?php echo $text ?></option>
40
- <?php endforeach ?>
41
- </select>
 
 
 
 
 
 
 
 
 
 
 
 
42
  </td>
43
  </tr>
44
  <tr>
45
  <td colspan="2">
46
- <input type="submit" value="<?php _e( 'Save', 'ab' ) ?>" class="btn btn-info ab-update-button" />
47
- <button id="ab-payments-reset" class="ab-reset-form" type="reset"><?php _e( 'Reset', 'ab' ) ?></button>
48
  </td>
49
  <td></td>
50
  </tr>
51
  </table>
52
- </form>
53
-
54
- <div class="modal fade" id="light_notice" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
55
- <div class="modal-dialog">
56
- <div class="modal-content">
57
- <div class="modal-header">
58
- <h4 class="modal-title">Notice</h4>
59
- </div>
60
- <div class="modal-body">
61
- <?php _e('This function is disabled in the light verison of Bookly. If you find the plugin useful for your business please consider buying a licence for the full version. It costs just $35 and for this money you will get many useful functions, lifetime free update and excellent support! More information can be found here'); ?>: <a href="http://bookly.ladela.com" target="_blank">http://bookly.ladela.com</a>
62
- </div>
63
- <div class="modal-footer">
64
- <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
65
- </div>
66
- </div><!-- /.modal-content -->
67
- </div><!-- /.modal-dialog -->
68
- </div><!-- /.modal -->
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <form method="post" action="<?php echo esc_url( add_query_arg( 'type', '_payments' ) ) ?>" class="ab-settings-form">
 
 
 
 
 
 
3
  <table class="form-horizontal">
 
 
 
 
 
 
 
 
 
 
4
  <tr>
5
+ <td style="width: 170px;">
6
+ <label for="ab_paypal_currency"><?php _e( 'Currency', 'bookly' ) ?></label>
7
+ </td>
8
+ <td>
9
+ <select id="ab_paypal_currency" class="form-control" name="ab_paypal_currency">
10
+ <?php foreach ( array( 'AUD', 'BRL', 'CAD', 'CHF', 'CLP', 'COP', 'CZK', 'DKK', 'EUR', 'GBP', 'GTQ', 'HKD', 'HUF', 'IDR', 'INR', 'ILS', 'JPY', 'KRW', 'KZT', 'MXN', 'MYR', 'NOK', 'NZD', 'PHP', 'PLN', 'QAR', 'RON', 'RMB', 'RUB', 'SAR', 'SEK', 'SGD', 'THB', 'TRY', 'TWD', 'UGX', 'USD', 'ZAR' ) as $code ): ?>
11
+ <option value="<?php echo $code ?>" <?php selected( get_option( 'ab_paypal_currency' ), $code ) ?> ><?php echo $code ?></option>
12
+ <?php endforeach ?>
13
+ </select>
14
+ </td>
15
+ </tr>
16
+ <tr>
17
+ <td style="width: 170px;">
18
+ <label for="ab_settings_coupons"><?php _e( 'Coupons', 'bookly' ) ?></label>
19
+ </td>
20
+ <td>
21
+ <?php AB_Utils::optionToggle( 'ab_settings_coupons' ) ?>
22
+ </td>
23
+ </tr>
24
+ <tr>
25
+ <td colspan="2"><div class="ab-payments-title"><?php _e( 'Service paid locally', 'bookly' ) ?></div></td>
26
  </tr>
27
  <tr>
28
  <td colspan="2">
29
+ <?php AB_Utils::optionToggle( 'ab_settings_pay_locally' ) ?>
 
 
 
 
30
  </td>
31
  </tr>
32
  <tr>
33
+ <td colspan="2"><div class="ab-payments-title">PayPal</div></td>
34
  </tr>
35
  <tr>
36
  <td colspan="2">
37
+ <?php AB_Utils::optionToggle( 'ab_paypal_type', array( 'f' => array( 'disabled', __( 'Disabled', 'bookly' ) ), 't' => array( 'ec', 'PayPal Express Checkout' ) ) ) ?>
38
+ </td>
39
+ </tr>
40
+ <tr>
41
+ <td colspan="2"><div class="ab-payments-title">Authorize.Net</div></td>
42
+ </tr>
43
+ <tr>
44
+ <td colspan="2">
45
+ <?php AB_Utils::optionToggle( 'ab_authorizenet_type', array( 'f' => array( 'disabled', __( 'Disabled', 'bookly' ) ), 't' => array( 'aim', 'Authorize.Net AIM' ) ) ) ?>
46
+ </td>
47
+ </tr>
48
+ <tr>
49
+ <td colspan="2"><div class="ab-payments-title">Stripe</div></td>
50
+ </tr>
51
+ <tr>
52
+ <td colspan="2">
53
+ <?php AB_Utils::optionToggle( 'ab_stripe' ) ?>
54
  </td>
55
  </tr>
56
  <tr>
57
  <td colspan="2">
58
+ <?php AB_Utils::submitButton() ?>
59
+ <?php AB_Utils::resetButton( 'ab-payments-reset' ) ?>
60
  </td>
61
  <td></td>
62
  </tr>
63
  </table>
64
+ </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/templates/_purchaseCodeForm.php DELETED
@@ -1,26 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <form enctype="multipart/form-data" method="post" action="<?php echo $current_url . '&type=_purchase_code' ?>" class="ab-staff-form" id="purchase_code">
3
- <?php if ( isset ( $message_pc ) ) : ?>
4
- <div class="alert">
5
- <button type="button" class="close" data-dismiss="alert">&times;</button>
6
- <?php echo $message_pc ?>
7
- </div>
8
- <?php endif ?>
9
-
10
- <table class="form-horizontal">
11
- <tr>
12
- <td><?php _e( 'Purchase Code', 'ab' ) ?></td>
13
- <td>
14
- <label for="purchase_code"></label>
15
- <input class="purchase-code" type="text" size="255" name="ab_envato_purchase_code" value="<?php echo get_option( 'ab_envato_purchase_code' ) ?>" />
16
- </td>
17
- </tr>
18
- <tr>
19
- <td></td>
20
- <td>
21
- <input type="submit" value="<?php _e( 'Save', 'ab' ) ?>" class="btn btn-info ab-update-button" />
22
- <button class="ab-reset-form" type="reset"><?php _e( ' Reset ', 'ab' ) ?></button>
23
- </td>
24
- </tr>
25
- </table>
26
- </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/templates/_woocommerce.php ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <form enctype="multipart/form-data" method="post" action="<?php echo esc_url( add_query_arg( 'type', '_woocommerce' ) ) ?>" class="ab-settings-form" id="woocommerce">
3
+ <table class="form-horizontal">
4
+ <tr>
5
+ <td colspan="2">
6
+ <fieldset class="ab-instruction">
7
+ <legend><?php _e( 'Instructions', 'bookly' ) ?></legend>
8
+ <div>
9
+ <div style="margin-bottom: 10px">
10
+ <?php _e( 'You need to install and activate WooCommerce plugin before using the options below.<br/><br/>Once the plugin is activated do the following steps:', 'bookly' ) ?>
11
+ </div>
12
+ <ol>
13
+ <li><?php _e( 'Create a product in WooCommerce that can be placed in cart.', 'bookly' ) ?></li>
14
+ <li><?php _e( 'In the form below enable WooCommerce option.', 'bookly' ) ?></li>
15
+ <li><?php _e( 'Select the product that you created at step 1 in the drop down list of products.', 'bookly' ) ?></li>
16
+ <li><?php _e( 'If needed, edit item data which will be displayed in the cart.', 'bookly' ) ?></li>
17
+ </ol>
18
+ <div style="margin-top: 10px">
19
+ <?php _e( 'Note that once you have enabled WooCommerce option in Bookly the built-in payment methods will no longer work. All your customers will be redirected to WooCommerce cart instead of standard payment step.', 'bookly' ) ?>
20
+ </div>
21
+ </div>
22
+ </fieldset>
23
+ </td>
24
+ </tr>
25
+ <tr>
26
+ <td colspan="2"><div class="ab-payments-title">WooCommerce</div></td>
27
+ </tr>
28
+ <tr>
29
+ <td>
30
+ <?php AB_Utils::optionToggle( 'ab_woocommerce' ) ?>
31
+ </td>
32
+ </tr>
33
+ <tr>
34
+ <td colspan="2">
35
+ <?php _e( 'Booking product', 'bookly' ) ?>
36
+ <select class="form-control" name="ab_woocommerce_product">
37
+ <?php foreach ( $candidates as $item ) : ?>
38
+ <option value="<?php echo $item['id'] ?>" <?php selected( get_option( 'ab_woocommerce_product' ), $item['id'] ); ?>>
39
+ <?php echo $item['name'] ?>
40
+ </option>
41
+ <?php endforeach ?>
42
+ </select>
43
+ </td>
44
+ </tr>
45
+ <tr>
46
+ <td colspan="2"><div class="ab-payments-title"><?php _e( 'Cart item data', 'bookly' ) ?></td>
47
+ </tr>
48
+ <tr>
49
+ <td colspan="2"><input class="form-control" type="text" name="ab_woocommerce_cart_info_name" value="<?php echo esc_attr( get_option( 'ab_woocommerce_cart_info_name' ) ) ?>" placeholder="<?php echo esc_attr( __( 'Enter a name', 'bookly' ) ) ?>" /></td>
50
+ </tr>
51
+ <tr>
52
+ <td colspan="2">
53
+ <textarea class="form-control" rows="8" name="ab_woocommerce_cart_info_value" style="width: 100%" placeholder="<?php _e( 'Enter a value', 'bookly' ) ?>"><?php echo esc_textarea( get_option( 'ab_woocommerce_cart_info_value' ) ) ?></textarea>
54
+ </td>
55
+ </tr>
56
+ <tr>
57
+ <td>
58
+ <div class="ab-codes">
59
+ <table>
60
+ <tr><td><input value="[[APPOINTMENT_DATE]]" readonly="readonly" onclick="this.select()"> - <?php _e('date of appointment', 'bookly') ?></td></tr>
61
+ <tr><td><input value="[[APPOINTMENT_TIME]]" readonly="readonly" onclick="this.select()"> - <?php _e('time of appointment', 'bookly') ?></td></tr>
62
+ <tr><td><input value="[[CATEGORY_NAME]]" readonly="readonly" onclick="this.select()"> - <?php _e('name of category', 'bookly') ?></td></tr>
63
+ <tr><td><input value="[[SERVICE_NAME]]" readonly="readonly" onclick="this.select()"> - <?php _e('name of service', 'bookly') ?></td></tr>
64
+ <tr><td><input value="[[SERVICE_PRICE]]" readonly="readonly" onclick="this.select()"> - <?php _e('price of service', 'bookly') ?></td></tr>
65
+ <tr><td><input value="[[STAFF_NAME]]" readonly="readonly" onclick="this.select()"> - <?php _e('name of staff', 'bookly') ?></td></tr>
66
+ </table>
67
+ </div>
68
+ </td>
69
+ </tr>
70
+ <tr>
71
+ <td colspan="2">
72
+ <?php AB_Utils::submitButton() ?>
73
+ <?php AB_Utils::resetButton() ?>
74
+ </td>
75
+ </tr>
76
+ </table>
77
+ </form>
backend/modules/settings/templates/admin_notice.php CHANGED
@@ -1,8 +1,9 @@
 
1
  <div id=ab_admin_notice class=update-nag>
2
  <h3>Bookly</h3>
3
- <p><?php _e( 'Please do not forget to specify your purchase code in Bookly <a href="admin.php?page=ab-system-settings">settings</a>. Upon providing the code you will have access to free updates of Bookly. Updates may contain functionality improvements and important security fixes.', 'ab' ) ?></p>
4
- <p><?php _e( '<b>Important!</b> Please be aware that if your copy of Bookly was not downloaded from Codecanyon (the only channel of Bookly distribution), you may put your website under significant risk - it is very likely that it may contain a malicious code, a trojan or a backdoor. Please consider buying a licensed copy of Bookly <a href="http://bookly.ladela.com" target="_blank">here</a>.', 'ab' ) ?></p>
5
- <a id="ab_dismiss" href="#"><?php _e( 'Dismiss', 'ab' ) ?></a>
6
  </div>
7
 
8
  <script type="text/javascript">
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
  <div id=ab_admin_notice class=update-nag>
3
  <h3>Bookly</h3>
4
+ <p><?php _e( 'Please do not forget to specify your purchase code in Bookly <a href="admin.php?page=ab-settings">settings</a>. Upon providing the code you will have access to free updates of Bookly. Updates may contain functionality improvements and important security fixes.', 'bookly' ) ?></p>
5
+ <p><?php _e( '<b>Important!</b> Please be aware that if your copy of Bookly was not downloaded from Codecanyon (the only channel of Bookly distribution), you may put your website under significant risk - it is very likely that it contains a malicious code, a trojan or a backdoor. Please consider buying a licensed copy of Bookly <a href="http://booking-wp-plugin.com" target="_blank">here</a>.', 'bookly' ) ?></p>
6
+ <a id="ab_dismiss" href="#"><?php _e( 'Dismiss', 'bookly' ) ?></a>
7
  </div>
8
 
9
  <script type="text/javascript">
backend/modules/settings/templates/index.php CHANGED
@@ -1,32 +1,70 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div class="ab-title"><?php _e('Settings') ?></div>
3
- <div style="min-width: 800px;">
4
- <div class="ab-left-bar">
5
- <div id="ab_settings_general" class="ab-left-tab <?php echo ( ! isset( $_GET[ 'type' ] ) || $_GET[ 'type' ] == '_general' ) ? 'ab-active' : '' ?>"><?php _e( 'General','ab' ) ?></div>
6
- <div id="ab_settings_company" class="ab-left-tab <?php echo isset( $_GET['type'] ) && $_GET['type'] == '_company' ? 'ab-active' : '' ?>"><?php _e( 'Company','ab' ) ?></div>
7
- <div id="ab_settings_payments" class="ab-left-tab <?php echo isset( $_GET['type'] ) && $_GET['type'] == '_payments' ? 'ab-active' : '' ?>"><?php _e( 'Payments','ab' ) ?></div>
8
- <div id="ab_settings_hours" class="ab-left-tab <?php echo isset( $_GET['type'] ) && $_GET['type'] == '_hours' ? 'ab-active' : '' ?>"><?php _e( 'Business hours','ab' ) ?></div>
9
- <div id="ab_settings_holidays" class="ab-left-tab <?php echo isset( $_GET['type'] ) && $_GET['type'] == '_holidays' ? 'ab-active' : '' ?>"><?php _e( 'Holidays','ab' ) ?></div>
10
- <div id="ab_settings_purchase_code" class="ab-left-tab <?php echo isset( $_GET[ 'type' ] ) && $_GET[ 'type' ] == '_purchase_code' ? 'ab-active' : '' ?>"><?php _e( 'Purchase Code','ab' ) ?></div>
11
- </div>
12
- <div class="ab-right-content" id="content_wrapper">
13
- <div id="general-form" class="<?php echo ( ! isset( $_GET[ 'type' ] ) || $_GET[ 'type' ] == '_general' ) ? '' : 'hidden' ?> ab-staff-tab-content">
14
- <?php include '_generalForm.php' ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  </div>
16
- <div id="company-form" class="<?php echo ( isset( $_GET['type'] ) && $_GET['type'] == '_company' ) ? '' : 'hidden' ?>">
17
- <?php include '_companyForm.php' ?>
18
- </div>
19
- <div id="payments-form" class="<?php echo ( isset( $_GET['type'] ) && $_GET['type'] == '_payments' ) ? '' : 'hidden' ?>">
20
- <?php include '_paymentsForm.php' ?>
21
- </div>
22
- <div id="hours-form" class="<?php echo ( isset( $_GET['type'] ) && $_GET['type'] == '_hours' ) ? '' : 'hidden' ?>">
23
- <?php include '_hoursForm.php' ?>
24
- </div>
25
- <div id="holidays-form" class="<?php echo ( isset( $_GET['type'] ) && $_GET['type'] == '_holidays' ) ? '' : 'hidden' ?> ab-staff-tab-content">
26
- <?php include '_holidaysForm.php' ?>
27
- </div>
28
- <div id="purchase-code-form" class="<?php echo ( isset( $_GET[ 'type' ] ) && $_GET[ 'type' ] == '_purchase_code' ) ? '' : 'hidden' ?> ab-staff-tab-content">
29
- <?php include '_purchaseCodeForm.php' ?>
30
- </div>
31
- </div>
32
- </div>
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( 'Settings', 'bookly' ) ?></h3>
5
+ </div>
6
+ <div class="panel-body">
7
+ <div class="row">
8
+ <div class="ab-settings ab-left-bar col-md-3 col-sm-3 col-xs-12 col-lg-3">
9
+ <?php $type = isset ( $_GET[ 'type' ] ) ? $_GET[ 'type' ] : '_general' ?>
10
+ <div id="ab_settings_general" class="ab-left-tab <?php echo $type == '_general' ? 'ab-active' : '' ?>"><?php _e( 'General', 'bookly' ) ?></div>
11
+ <div id="ab_settings_company" class="ab-left-tab <?php echo $type == '_company' ? 'ab-active' : '' ?>"><?php _e( 'Company', 'bookly' ) ?></div>
12
+ <div id="ab_settings_customers" class="ab-left-tab <?php echo $type == '_customers' ? 'ab-active' : '' ?>"><?php _e( 'Customers', 'bookly' ) ?></div>
13
+ <div id="ab_settings_google_calendar" class="ab-left-tab <?php echo $type == '_google_calendar' ? 'ab-active' : '' ?>"><?php _e( 'Google Calendar', 'bookly' ) ?></div>
14
+ <div id="ab_settings_woocommerce" class="ab-left-tab <?php echo $type == '_woocommerce' ? 'ab-active' : '' ?>">WooCommerce</div>
15
+ <div id="ab_settings_payments" class="ab-left-tab <?php echo $type == '_payments' ? 'ab-active' : '' ?>"><?php _e( 'Payments', 'bookly' ) ?></div>
16
+ <div id="ab_settings_hours" class="ab-left-tab <?php echo $type == '_hours' ? 'ab-active' : '' ?>"><?php _e( 'Business hours', 'bookly' ) ?></div>
17
+ <div id="ab_settings_holidays" class="ab-left-tab <?php echo $type == '_holidays' ? 'ab-active' : '' ?>"><?php _e( 'Holidays', 'bookly' ) ?></div>
18
+ </div>
19
+ <div class="ab-right-content col-md-9 col-sm-9 col-xs-12 col-lg-9" id="content_wrapper">
20
+ <div id="general-form" class="<?php echo ( $type == '_general' ) ? '' : 'hidden' ?>">
21
+ <?php ( $type == '_general' ) && AB_Utils::notice( $message ) ?>
22
+ <?php include '_generalForm.php' ?>
23
+ </div>
24
+ <div id="company-form" class="<?php echo ( $type == '_company' ) ? '' : 'hidden' ?>">
25
+ <?php ( $type == '_company' ) && AB_Utils::notice( $message ) ?>
26
+ <?php include '_companyForm.php' ?>
27
+ </div>
28
+ <div id="customers-form" class="<?php echo ( $type == '_customers' ) ? '' : 'hidden' ?>">
29
+ <?php ( $type == '_customers' ) && AB_Utils::notice( $message ) ?>
30
+ <?php include '_customers.php' ?>
31
+ </div>
32
+ <div id="google-calendar-form" class="<?php echo ( $type == '_google_calendar' ) ? '' : 'hidden' ?>">
33
+ <?php ( $type == '_google_calendar' ) && AB_Utils::notice( $message ) ?>
34
+ <?php include '_googleCalendarForm.php' ?>
35
+ </div>
36
+ <div id="payments-form" class="<?php echo ( $type == '_payments' ) ? '' : 'hidden' ?>">
37
+ <?php ( $type == '_payments' ) && AB_Utils::notice( $message ) ?>
38
+ <?php include '_paymentsForm.php' ?>
39
+ </div>
40
+ <div id="hours-form" class="<?php echo ( $type == '_hours' ) ? '' : 'hidden' ?>">
41
+ <?php ( $type == '_hours' ) && AB_Utils::notice( $message ) ?>
42
+ <?php include '_hoursForm.php' ?>
43
+ </div>
44
+ <div id="holidays-form" class="<?php echo ( $type == '_holidays' ) ? '' : 'hidden' ?>">
45
+ <?php ( $type == '_holidays' ) && AB_Utils::notice( $message ) ?>
46
+ <?php include '_holidaysForm.php' ?>
47
+ </div>
48
+ <div id="woocommerce-form" class="<?php echo ( $type == '_woocommerce' ) ? '' : 'hidden' ?>">
49
+ <?php ( $type == '_woocommerce' ) && AB_Utils::notice( $message ) ?>
50
+ <?php include '_woocommerce.php' ?>
51
+ </div>
52
+ </div>
53
  </div>
54
+ </div>
55
+ </div>
56
+ <div class="modal fade" id="lite_notice" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
57
+ <div class="modal-dialog">
58
+ <div class="modal-content">
59
+ <div class="modal-header">
60
+ <h4 class="modal-title"><?php _e('Notice', 'bookly') ?></h4>
61
+ </div>
62
+ <div class="modal-body">
63
+ <?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>
64
+ </div>
65
+ <div class="modal-footer">
66
+ <button type="button" class="btn btn-default" data-dismiss="modal"><?php _e('Close', 'bookly') ?></button>
67
+ </div>
68
+ </div><!-- /.modal-content -->
69
+ </div><!-- /.modal-dialog -->
70
+ </div><!-- /.modal -->
backend/modules/sms/AB_SmsController.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+
3
+ /**
4
+ * Class AB_SmsController
5
+ */
6
+ class AB_SmsController extends AB_Controller {
7
+
8
+ const page_slug = 'ab-sms';
9
+
10
+ public function index()
11
+ {
12
+ $this->enqueueStyles(
13
+ array(
14
+ 'backend' => array(
15
+ 'css/bookly.main-backend.css',
16
+ 'bootstrap/css/bootstrap.min.css',
17
+ ),
18
+ 'module' => array(
19
+ 'css/sms.css',
20
+ 'css/flags.css',
21
+ )
22
+ )
23
+ );
24
+
25
+ $this->enqueueScripts(
26
+ array(
27
+ 'backend' => array(
28
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
29
+ ),
30
+ 'module' => array(
31
+ 'js/sms.js' => array( 'jquery' ),
32
+ ),
33
+ )
34
+ );
35
+
36
+ $this->prices = array();
37
+ $this->sms = new AB_SMS();
38
+
39
+ if( $response = $this->sms->getPriceList() ){
40
+ $this->prices = $response->list;
41
+ }
42
+
43
+ $this->render( 'index' );
44
+ } // index
45
+
46
+ }
backend/modules/sms/resources/css/flags.css ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .flag {
2
+ width: 32px;
3
+ height: 28px;
4
+ display: block;
5
+ background:url(flags.png) no-repeat;
6
+ margin-top: -8px;
7
+ }
8
+
9
+ .flag.flag-ad {background-position: -32px 0}
10
+ .flag.flag-ae {background-position: -64px 0}
11
+ .flag.flag-af {background-position: -96px 0}
12
+ .flag.flag-ag {background-position: -128px 0}
13
+ .flag.flag-ai {background-position: -160px 0}
14
+ .flag.flag-al {background-position: -192px 0}
15
+ .flag.flag-am {background-position: -224px 0}
16
+ .flag.flag-an {background-position: -256px 0}
17
+ .flag.flag-ao {background-position: -288px 0}
18
+ .flag.flag-ar {background-position: -320px 0}
19
+ .flag.flag-as {background-position: -352px 0}
20
+ .flag.flag-at {background-position: -384px 0}
21
+ .flag.flag-au {background-position: -416px 0}
22
+ .flag.flag-aw {background-position: -448px 0}
23
+ .flag.flag-az {background-position: 0 -32px}
24
+ .flag.flag-ba {background-position: -32px -32px}
25
+ .flag.flag-bb {background-position: -64px -32px}
26
+ .flag.flag-bd {background-position: -96px -32px}
27
+ .flag.flag-be {background-position: -128px -32px}
28
+ .flag.flag-bf {background-position: -160px -32px}
29
+ .flag.flag-bg {background-position: -192px -32px}
30
+ .flag.flag-bh {background-position: -224px -32px}
31
+ .flag.flag-bi {background-position: -256px -32px}
32
+ .flag.flag-bj {background-position: -288px -32px}
33
+ .flag.flag-bm {background-position: -320px -32px}
34
+ .flag.flag-bn {background-position: -352px -32px}
35
+ .flag.flag-bo {background-position: -384px -32px}
36
+ .flag.flag-br {background-position: -416px -32px}
37
+ .flag.flag-bs {background-position: -448px -32px}
38
+ .flag.flag-bt {background-position: 0 -64px}
39
+ .flag.flag-bw {background-position: -32px -64px}
40
+ .flag.flag-by {background-position: -64px -64px}
41
+ .flag.flag-bz {background-position: -96px -64px}
42
+ .flag.flag-ca {background-position: -128px -64px}
43
+ .flag.flag-cd {background-position: -160px -64px}
44
+ .flag.flag-cf {background-position: -192px -64px}
45
+ .flag.flag-cg {background-position: -224px -64px}
46
+ .flag.flag-ch {background-position: -256px -64px}
47
+ .flag.flag-ci {background-position: -288px -64px}
48
+ .flag.flag-ck {background-position: -320px -64px}
49
+ .flag.flag-cl {background-position: -352px -64px}
50
+ .flag.flag-cm {background-position: -384px -64px}
51
+ .flag.flag-cn {background-position: -416px -64px}
52
+ .flag.flag-co {background-position: -448px -64px}
53
+ .flag.flag-cr {background-position: 0 -96px}
54
+ .flag.flag-cu {background-position: -32px -96px}
55
+ .flag.flag-cv {background-position: -64px -96px}
56
+ .flag.flag-cy {background-position: -96px -96px}
57
+ .flag.flag-cz {background-position: -128px -96px}
58
+ .flag.flag-de {background-position: -160px -96px}
59
+ .flag.flag-dj {background-position: -192px -96px}
60
+ .flag.flag-dk {background-position: -224px -96px}
61
+ .flag.flag-dm {background-position: -256px -96px}
62
+ .flag.flag-do {background-position: -288px -96px}
63
+ .flag.flag-dz {background-position: -320px -96px}
64
+ .flag.flag-ec {background-position: -352px -96px}
65
+ .flag.flag-ee {background-position: -384px -96px}
66
+ .flag.flag-eg {background-position: -416px -96px}
67
+ .flag.flag-eh {background-position: -448px -96px}
68
+ .flag.flag-er {background-position: 0 -128px}
69
+ .flag.flag-es {background-position: -32px -128px}
70
+ .flag.flag-et {background-position: -64px -128px}
71
+ .flag.flag-fi {background-position: -96px -128px}
72
+ .flag.flag-fj {background-position: -128px -128px}
73
+ .flag.flag-fm {background-position: -160px -128px}
74
+ .flag.flag-fo {background-position: -192px -128px}
75
+ .flag.flag-fr {background-position: -224px -128px}
76
+ .flag.flag-ga {background-position: -256px -128px}
77
+ .flag.flag-gb {background-position: -288px -128px}
78
+ .flag.flag-gd {background-position: -320px -128px}
79
+ .flag.flag-ge {background-position: -352px -128px}
80
+ .flag.flag-gg {background-position: -384px -128px}
81
+ .flag.flag-gh {background-position: -416px -128px}
82
+ .flag.flag-gi {background-position: -448px -128px}
83
+ .flag.flag-gl {background-position: 0 -160px}
84
+ .flag.flag-gm {background-position: -32px -160px}
85
+ .flag.flag-gn {background-position: -64px -160px}
86
+ .flag.flag-gp {background-position: -96px -160px}
87
+ .flag.flag-gq {background-position: -128px -160px}
88
+ .flag.flag-gr {background-position: -160px -160px}
89
+ .flag.flag-gt {background-position: -192px -160px}
90
+ .flag.flag-gu {background-position: -224px -160px}
91
+ .flag.flag-gw {background-position: -256px -160px}
92
+ .flag.flag-gy {background-position: -288px -160px}
93
+ .flag.flag-hk {background-position: -320px -160px}
94
+ .flag.flag-hn {background-position: -352px -160px}
95
+ .flag.flag-hr {background-position: -384px -160px}
96
+ .flag.flag-ht {background-position: -416px -160px}
97
+ .flag.flag-hu {background-position: -448px -160px}
98
+ .flag.flag-id {background-position: 0 -192px}
99
+ .flag.flag-ie {background-position: -32px -192px}
100
+ .flag.flag-il {background-position: -64px -192px}
101
+ .flag.flag-im {background-position: -96px -192px}
102
+ .flag.flag-in {background-position: -128px -192px}
103
+ .flag.flag-iq {background-position: -160px -192px}
104
+ .flag.flag-ir {background-position: -192px -192px}
105
+ .flag.flag-is {background-position: -224px -192px}
106
+ .flag.flag-it {background-position: -256px -192px}
107
+ .flag.flag-je {background-position: -288px -192px}
108
+ .flag.flag-jm {background-position: -320px -192px}
109
+ .flag.flag-jo {background-position: -352px -192px}
110
+ .flag.flag-jp {background-position: -384px -192px}
111
+ .flag.flag-ke {background-position: -416px -192px}
112
+ .flag.flag-kg {background-position: -448px -192px}
113
+ .flag.flag-kh {background-position: 0 -224px}
114
+ .flag.flag-ki {background-position: -32px -224px}
115
+ .flag.flag-km {background-position: -64px -224px}
116
+ .flag.flag-kn {background-position: -96px -224px}
117
+ .flag.flag-kp {background-position: -128px -224px}
118
+ .flag.flag-kr {background-position: -160px -224px}
119
+ .flag.flag-kw {background-position: -192px -224px}
120
+ .flag.flag-ky {background-position: -224px -224px}
121
+ .flag.flag-kz {background-position: -256px -224px}
122
+ .flag.flag-la {background-position: -288px -224px}
123
+ .flag.flag-lb {background-position: -320px -224px}
124
+ .flag.flag-lc {background-position: -352px -224px}
125
+ .flag.flag-li {background-position: -384px -224px}
126
+ .flag.flag-lk {background-position: -416px -224px}
127
+ .flag.flag-lr {background-position: -448px -224px}
128
+ .flag.flag-ls {background-position: 0 -256px}
129
+ .flag.flag-lt {background-position: -32px -256px}
130
+ .flag.flag-lu {background-position: -64px -256px}
131
+ .flag.flag-lv {background-position: -96px -256px}
132
+ .flag.flag-ly {background-position: -128px -256px}
133
+ .flag.flag-ma {background-position: -160px -256px}
134
+ .flag.flag-mc {background-position: -192px -256px}
135
+ .flag.flag-md {background-position: -224px -256px}
136
+ .flag.flag-me {background-position: -256px -256px}
137
+ .flag.flag-mg {background-position: -288px -256px}
138
+ .flag.flag-mh {background-position: -320px -256px}
139
+ .flag.flag-mk {background-position: -352px -256px}
140
+ .flag.flag-ml {background-position: -384px -256px}
141
+ .flag.flag-mm {background-position: -416px -256px}
142
+ .flag.flag-mn {background-position: -448px -256px}
143
+ .flag.flag-mo {background-position: 0 -288px}
144
+ .flag.flag-mq {background-position: -32px -288px}
145
+ .flag.flag-mr {background-position: -64px -288px}
146
+ .flag.flag-ms {background-position: -96px -288px}
147
+ .flag.flag-mt {background-position: -128px -288px}
148
+ .flag.flag-mu {background-position: -160px -288px}
149
+ .flag.flag-mv {background-position: -192px -288px}
150
+ .flag.flag-mw {background-position: -224px -288px}
151
+ .flag.flag-mx {background-position: -256px -288px}
152
+ .flag.flag-my {background-position: -288px -288px}
153
+ .flag.flag-mz {background-position: -320px -288px}
154
+ .flag.flag-na {background-position: -352px -288px}
155
+ .flag.flag-nc {background-position: -384px -288px}
156
+ .flag.flag-ne {background-position: -416px -288px}
157
+ .flag.flag-ng {background-position: -448px -288px}
158
+ .flag.flag-ni {background-position: 0 -320px}
159
+ .flag.flag-nl {background-position: -32px -320px}
160
+ .flag.flag-no {background-position: -64px -320px}
161
+ .flag.flag-np {background-position: -96px -320px}
162
+ .flag.flag-nr {background-position: -128px -320px}
163
+ .flag.flag-nz {background-position: -160px -320px}
164
+ .flag.flag-om {background-position: -192px -320px}
165
+ .flag.flag-pa {background-position: -224px -320px}
166
+ .flag.flag-pe {background-position: -256px -320px}
167
+ .flag.flag-pf {background-position: -288px -320px}
168
+ .flag.flag-pg {background-position: -320px -320px}
169
+ .flag.flag-ph {background-position: -352px -320px}
170
+ .flag.flag-pk {background-position: -384px -320px}
171
+ .flag.flag-pl {background-position: -416px -320px}
172
+ .flag.flag-pr {background-position: -448px -320px}
173
+ .flag.flag-ps {background-position: 0 -352px}
174
+ .flag.flag-pt {background-position: -32px -352px}
175
+ .flag.flag-pw {background-position: -64px -352px}
176
+ .flag.flag-py {background-position: -96px -352px}
177
+ .flag.flag-qa {background-position: -128px -352px}
178
+ .flag.flag-re {background-position: -160px -352px}
179
+ .flag.flag-ro {background-position: -192px -352px}
180
+ .flag.flag-rs {background-position: -224px -352px}
181
+ .flag.flag-ru {background-position: -256px -352px}
182
+ .flag.flag-rw {background-position: -288px -352px}
183
+ .flag.flag-sa {background-position: -320px -352px}
184
+ .flag.flag-sb {background-position: -352px -352px}
185
+ .flag.flag-sc {background-position: -384px -352px}
186
+ .flag.flag-sd {background-position: -416px -352px}
187
+ .flag.flag-se {background-position: -448px -352px}
188
+ .flag.flag-sg {background-position: 0 -384px}
189
+ .flag.flag-si {background-position: -32px -384px}
190
+ .flag.flag-sk {background-position: -64px -384px}
191
+ .flag.flag-sl {background-position: -96px -384px}
192
+ .flag.flag-sm {background-position: -128px -384px}
193
+ .flag.flag-sn {background-position: -160px -384px}
194
+ .flag.flag-so {background-position: -192px -384px}
195
+ .flag.flag-sr {background-position: -224px -384px}
196
+ .flag.flag-st {background-position: -256px -384px}
197
+ .flag.flag-sv {background-position: -288px -384px}
198
+ .flag.flag-sy {background-position: -320px -384px}
199
+ .flag.flag-sz {background-position: -352px -384px}
200
+ .flag.flag-tc {background-position: -384px -384px}
201
+ .flag.flag-td {background-position: -416px -384px}
202
+ .flag.flag-tg {background-position: -448px -384px}
203
+ .flag.flag-th {background-position: 0 -416px}
204
+ .flag.flag-tj {background-position: -32px -416px}
205
+ .flag.flag-tl {background-position: -64px -416px}
206
+ .flag.flag-tm {background-position: -96px -416px}
207
+ .flag.flag-tn {background-position: -128px -416px}
208
+ .flag.flag-to {background-position: -160px -416px}
209
+ .flag.flag-tr {background-position: -192px -416px}
210
+ .flag.flag-tt {background-position: -224px -416px}
211
+ .flag.flag-tv {background-position: -256px -416px}
212
+ .flag.flag-tw {background-position: -288px -416px}
213
+ .flag.flag-tz {background-position: -320px -416px}
214
+ .flag.flag-ua {background-position: -352px -416px}
215
+ .flag.flag-ug {background-position: -384px -416px}
216
+ .flag.flag-us {background-position: -416px -416px}
217
+ .flag.flag-uy {background-position: -448px -416px}
218
+ .flag.flag-uz {background-position: 0 -448px}
219
+ .flag.flag-va {background-position: -32px -448px}
220
+ .flag.flag-vc {background-position: -64px -448px}
221
+ .flag.flag-ve {background-position: -96px -448px}
222
+ .flag.flag-vg {background-position: -128px -448px}
223
+ .flag.flag-vi {background-position: -160px -448px}
224
+ .flag.flag-vn {background-position: -192px -448px}
225
+ .flag.flag-vu {background-position: -224px -448px}
226
+ .flag.flag-ws {background-position: -256px -448px}
227
+ .flag.flag-ye {background-position: -288px -448px}
228
+ .flag.flag-za {background-position: -320px -448px}
229
+ .flag.flag-zm {background-position: -352px -448px}
230
+ .flag.flag-zw {background-position: -384px -448px}
backend/modules/sms/resources/css/flags.png ADDED
Binary file
backend/modules/sms/resources/css/sms.css ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* notifications */
2
+ .ab-notifications table td { padding-left: 20px; padding-top: 10px; }
3
+ .ab-notifications .ab-form-field { padding-top: 10px; }
4
+ .ab-notifications .ab-toggle-arrow { position: absolute; right: 0; top: 9px; width: 24px; height: 24px; cursor: pointer; }
5
+ .ab-notifications .ab-form-label { display: block; float: left; margin-top: 3px; min-width: 80px; }
6
+ .ab-notifications .ab-form-row { padding: 3px 0; overflow: hidden; }
7
+ .ab-notifications .ab-form-row input[type="text"] { width: 700px!important; position: relative; z-index: 9; }
8
+ .ab-notifications #message_editor { margin-top: -20px; }
9
+ .ab-notifications #message_editor .wp-editor-wrap { margin-left: 81px; }
10
+ .ab-notifications legend { margin: 0; border: 0; line-height: normal; }
11
+ .ab-notifications legend input { margin: 0!important; }
12
+ .ab-notifications legend label { display: inline; font-size: 17px; }
13
+ .ab-notifications .ab-codes table tr td { padding: 0 20px 0 0; font-size: 12px;}
14
+ .ab-notifications .ab-codes table tr td input { cursor: pointer; border: none; width: 250px; }
15
+ .ab-notifications .ab-codes .ab-codes-inner {
16
+ max-width: 320px;
17
+ display: inline-block;
18
+ vertical-align: top;
19
+ border: 1px solid silver;
20
+ box-sizing: border-box;
21
+ padding: 10px;
22
+ margin-right: 15px;
23
+ margin-bottom: 20px;
24
+ }
25
+ .ab-notifications .ab-codes .ab-codes-inner input { cursor: pointer; border: none; width: 250px; }
26
+ .ab-notifications .ab-codes .ab-codes-inner legend {
27
+ display: inline-block;
28
+ width: auto;
29
+ padding: 0 10px 0px 10px;
30
+ font-size: 17px;
31
+ font-weight: bold;
32
+ }
33
+ .ab-notifications .panel-title > input { margin: 0 5px 0 0; }
34
+ .ab-notifications a[data-toggle="collapse"] {
35
+ outline: none;
36
+ box-shadow: none;
37
+ padding-right: 25px;
38
+ background: url("../../../../resources/images/notifications-arrow-up.png") 100% 50% no-repeat;
39
+ background-size: 17px 17px;
40
+ }
41
+ .ab-notifications a[data-toggle="collapse"].collapsed {
42
+ background: url("../../../../resources/images/notifications-arrow-down.png") 100% 50% no-repeat;
43
+ background-size: 17px 17px;
44
+ }
45
+ .ab-sms-holder > textarea { width: 370px; }
46
+ .ab-logout {
47
+ border: 0;
48
+ background: none;
49
+ width: 100%;
50
+ padding: 0;
51
+ text-align: left;
52
+ }
53
+ #sms_tabs { margin: 15px 0; }
backend/modules/sms/resources/js/sms.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ jQuery(function($) {
2
+ $('.panel a, .panel input, .panel button').on('click', function(e){
3
+ e.preventDefault();
4
+ $('#lite_notice').modal('show');
5
+ });
6
+ });
backend/modules/sms/templates/_price.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <table class="table table-striped">
3
+ <thead>
4
+ <tr>
5
+ <th></th>
6
+ <th><?php _e( 'Country', 'bookly' ) ?></th>
7
+ <th class="text-right"><?php _e( 'Code', 'bookly' ) ?></th>
8
+ <th class="text-right"><?php _e( 'Price', 'bookly' ) ?></th>
9
+ </tr>
10
+ </thead>
11
+ <tbody id="pricelist">
12
+ <?php if ( $prices ) : ?>
13
+ <?php foreach ( $prices as $price ) : ?>
14
+ <tr><td><i class="flag flag-<?php echo esc_attr( $price->country_iso_code ) ?>"></i></td><td><?php echo $price->country_name ?></td><td class="text-right"><?php echo $price->phone_code ?></td><td class="text-right">$<?php echo rtrim( $price->price, '0' ) ?></td></tr>
15
+ <?php endforeach ?>
16
+ <?php else : ?>
17
+ <tr><td colspan="4"><span class="ab-loader"></span></td></tr>
18
+ <?php endif ?>
19
+ </tbody>
20
+ </table>
21
+ <p><?php _e( 'If you do not see your country in the list please contact us at <a href="mailto:support@ladela.com">support@ladela.com</a>.', 'bookly' ) ?></p>
backend/modules/sms/templates/index.php ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ /** @var AB_SMS $sms */
3
+ ?>
4
+ <div class="panel panel-default">
5
+ <div class="panel-heading">
6
+ <h3 class="panel-title"><?php _e( 'SMS Notifications', 'bookly' ) ?></h3>
7
+ </div>
8
+ <div class="panel-body">
9
+ <div class="ab-wrapper-container">
10
+ <div class="row">
11
+ <div class="col-xs-12">
12
+ <div
13
+ class="alert alert-info"><?php _e( 'SMS Notifications (or "Bookly SMS") is a service for notifying your customers via text messages which are sent to mobile phones.<br/>It is necessary to register in order to start using this service.<br/>After registration you will need to configure notification messages and top up your balance in order to start sending SMS.', 'bookly' ) ?></div>
14
+ </div>
15
+ </div>
16
+ <div class="row">
17
+ <div class="col-xs-3">
18
+ <form method="post" class="ab-login-form well">
19
+
20
+ <fieldset>
21
+ <legend><?php _e( 'Login', 'bookly' ) ?></legend>
22
+ <div class="form-group">
23
+ <label for="ab_username"><?php _e( 'Email', 'bookly' ) ?></label>
24
+ <input id="ab_username" class="form-control" type="text" required="required" value="" name="username"/>
25
+ </div>
26
+ <div class="form-group">
27
+ <label for="ab_password"><?php _e( 'Password', 'bookly' ) ?></label>
28
+ <input id="ab_password" class="form-control" type="password" required="required" name="password"/>
29
+ </div>
30
+ <div class="form-group">
31
+ <button type="submit" name="form-login" class="btn btn-info"><?php _e( 'Log In', 'bookly' ) ?></button>
32
+ <a href="#" class="show-register-form"><?php _e( 'Registration', 'bookly' ) ?></a>
33
+ <a href="#" class="show-forgot-form"><?php _e( 'Forgot password', 'bookly' ) ?></a>
34
+ </div>
35
+ </fieldset>
36
+
37
+ </form>
38
+
39
+ <form method="post" class="ab-register-form well" style="display: none;">
40
+ <fieldset>
41
+ <legend><?php _e( 'Registration', 'bookly' ) ?></legend>
42
+ <div class="form-group">
43
+ <label for="ab_r_username"><?php _e( 'Email', 'bookly' ) ?></label>
44
+ <input id="ab_r_username" name="username" class="form-control" required="required" value="" type="text"/>
45
+ </div>
46
+ <div class="form-group">
47
+ <label for="ab_r_password"><?php _e( 'Password', 'bookly' ) ?></label>
48
+ <input id="ab_r_password" name="password" class="form-control" required="required" value="" type="password"/>
49
+ </div>
50
+ <div class="form-group">
51
+ <label for="ab_r_repeat_password"><?php _e( 'Repeat password', 'bookly' ) ?></label>
52
+ <input id="ab_r_repeat_password" name="password_repeat" class="form-control" required="required" value="" type="password"/>
53
+ </div>
54
+ <div class="form-group">
55
+ <label for="ab_r_tos"><?php _e( 'Accept <a href="javascript:void(0)" data-toggle="modal" data-target="#ab-tos">Terms & Conditions</a>', 'bookly' ) ?></label>
56
+ <input id="ab_r_tos" name="accept_tos" class="form-control" required="required" value="1" type="checkbox" style="margin:0"/>
57
+ </div>
58
+
59
+ <div class="form-group">
60
+ <button type="submit" name="form-registration" class="btn btn-info"><?php _e( 'Register', 'bookly' ) ?></button>
61
+ <a href="#" class="show-login-form"><?php _e( 'Log In', 'bookly' ) ?></a>
62
+ </div>
63
+ </fieldset>
64
+ </form>
65
+
66
+ <form method="post" class="ab-forgot-form well" style="display: none;">
67
+ <fieldset>
68
+ <legend><?php _e( 'Forgot password', 'bookly' ) ?></legend>
69
+ <div class="form-group">
70
+ <input name="username" class="form-control" value="" type="text" placeholder="<?php echo esc_attr( __( 'Email', 'bookly' ) ) ?>"/>
71
+ </div>
72
+ <div class="form-group hidden">
73
+ <input name="code" class="form-control" value="" type="text" placeholder="<?php echo esc_attr( __( 'Enter code from email', 'bookly' ) ) ?>"/>
74
+ </div>
75
+ <div class="form-group hidden">
76
+ <input name="password" class="form-control" value="" type="password" placeholder="<?php echo esc_attr( __( 'New password', 'bookly' ) ) ?>"/>
77
+ </div>
78
+ <div class="form-group hidden">
79
+ <input name="password_repeat" class="form-control" value="" type="password" placeholder="<?php echo esc_attr( __( 'Repeat new password', 'bookly' ) ) ?>"/>
80
+ </div>
81
+ <div class="form-group">
82
+ <button class="btn btn-info form-forgot-next" data-step="0"><?php _e( 'Next', 'bookly' ) ?></button>
83
+ <a href="#" class="show-login-form"><?php _e( 'Log In', 'bookly' ) ?></a>
84
+ </div>
85
+ </fieldset>
86
+ </form>
87
+ </div>
88
+ <div class="col-xs-9">
89
+ <?php include "_price.php" ?>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ <div class="modal fade" id="lite_notice" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
96
+ <div class="modal-dialog">
97
+ <div class="modal-content">
98
+ <div class="modal-header">
99
+ <h4 class="modal-title"><?php _e('Notice', 'bookly') ?></h4>
100
+ </div>
101
+ <div class="modal-body">
102
+ <?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>
103
+ </div>
104
+ <div class="modal-footer">
105
+ <button type="button" class="btn btn-default" data-dismiss="modal"><?php _e('Close', 'bookly') ?></button>
106
+ </div>
107
+ </div><!-- /.modal-content -->
108
+ </div><!-- /.modal-dialog -->
109
+ </div><!-- /.modal -->
110
+
backend/modules/staff/AB_StaffController.php CHANGED
@@ -1,13 +1,4 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
-
5
- include 'forms/AB_StaffMemberNewForm.php';
6
- include 'forms/AB_StaffMemberEditForm.php';
7
- include 'forms/AB_StaffServicesForm.php';
8
- include 'forms/AB_StaffScheduleForm.php';
9
- include 'forms/AB_StaffScheduleItemBreakForm.php';
10
- include 'forms/widget/AB_TimeChoiceWidget.php';
11
 
12
  /**
13
  * Class AB_StaffController
@@ -20,114 +11,190 @@ include 'forms/widget/AB_TimeChoiceWidget.php';
20
  */
21
  class AB_StaffController extends AB_Controller {
22
 
23
- public function renderStaffMembers() {
24
- $path = dirname( dirname( __FILE__ ) );
25
- wp_enqueue_style( 'ab-style', plugins_url( 'resources/css/ab_style.css', $path ) );
26
- wp_enqueue_style( 'ab-staff', plugins_url( 'resources/css/staff.css', __FILE__ ) );
27
- wp_enqueue_style( 'ab-bootstrap', plugins_url( 'resources/bootstrap/css/bootstrap.min.css', $path ) );
28
- wp_enqueue_script( 'ab-bootstrap', plugins_url( 'resources/bootstrap/js/bootstrap.min.js', $path ), array( 'jquery' ) );
29
- wp_enqueue_script( 'ab-popup', plugins_url( 'resources/js/ab_popup.js', $path ), array( 'jquery' ) );
30
- wp_enqueue_script( 'ab-system-staff', plugins_url( 'resources/js/staff.js', __FILE__ ), array( 'jquery' ) );
31
- wp_enqueue_script( 'ab-jCal', plugins_url( 'resources/js/jCal.js', $path ), array( 'jquery' ) );
32
- wp_enqueue_style( 'ab-jCal', plugins_url( 'resources/css/jCal.css', $path ) );
33
- wp_localize_script( 'ab-jCal', 'BooklyL10n', array(
34
- 'we_are_not_working' => __( 'We are not working on this day', 'ab' ),
35
- 'repeat' => __( 'Repeat every year', 'ab' ),
36
- 'month' => array(
37
- 'January' => __( 'January', 'ab' ),
38
- 'February' => __( 'February', 'ab' ),
39
- 'March' => __( 'March', 'ab' ),
40
- 'April' => __( 'April', 'ab' ),
41
- 'May' => __( 'May', 'ab' ),
42
- 'June' => __( 'June', 'ab' ),
43
- 'July' => __( 'July', 'ab' ),
44
- 'August' => __( 'August', 'ab' ),
45
- 'September' => __( 'September', 'ab' ),
46
- 'October' => __( 'October', 'ab' ),
47
- 'November' => __( 'November', 'ab' ),
48
- 'December' => __( 'December', 'ab' )
 
 
 
 
 
 
49
  ),
50
- 'day' => array(
51
- 'Mon' => __( 'Mon', 'ab' ),
52
- 'Tue' => __( 'Tue', 'ab' ),
53
- 'Wed' => __( 'Wed', 'ab' ),
54
- 'Thu' => __( 'Thu', 'ab' ),
55
- 'Fri' => __( 'Fri', 'ab' ),
56
- 'Sat' => __( 'Sat', 'ab' ),
57
- 'Sun' => __( 'Sun', 'ab' )
58
  )
59
  ) );
60
 
61
- wp_enqueue_script('jquery-ui-dialog');
62
- wp_enqueue_style("wp-jquery-ui-dialog");
 
 
 
 
 
 
 
63
 
64
  $this->form = new AB_StaffMemberNewForm();
65
- $this->collection = $this->getWpdb()->get_results( "SELECT * FROM ab_staff LIMIT 1" );
66
- if ( !isset ( $this->active_staff_id ) ) {
67
- $this->active_staff_id = $this->collection ? $this->collection[0]->id : 0;
68
- }
69
 
70
  $this->render( 'list' );
71
  }
72
 
73
- public function executeStaffServices() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  $this->form = new AB_StaffServicesForm();
75
- $this->form->load($this->getParameter( 'id' ));
76
- $this->staff_id = $this->getParameter( 'id' );
77
  $this->render( 'services' );
78
  exit;
79
  }
80
 
81
- public function executeStaffSchedule() {
 
82
  $staff = new AB_Staff();
83
- $staff->load( $this->_post['id'] );
84
- $this->schedule_list = $staff->getScheduleList();
 
85
  $this->render( 'schedule' );
86
  exit;
87
  }
88
 
89
- public function executeStaffScheduleUpdate() {
 
90
  $this->form = new AB_StaffScheduleForm();
91
- $this->form->bind($this->getPost());
92
  $this->form->save();
93
- exit;
 
94
  }
95
 
96
- public function executeStaffScheduleHandleBreak() {
97
- $_post = $this->getPost();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
- $start_time = $_post['start_time'];
100
- $end_time = $_post['end_time'];
101
- $working_start = $_post['working_start'];
102
- $working_end = $_post['working_end'];
 
 
103
 
104
- if ( strtotime( date( 'Y-m-d ' . $start_time ) ) >= strtotime( date( 'Y-m-d ' . $end_time ) ) ) {
105
- echo json_encode( array(
106
  'success' => false,
107
- 'error_msg' => __( 'The start time must be less than the end one', 'ab'),
108
  ) );
109
- exit;
110
  }
111
 
112
  $staffScheduleItem = new AB_StaffScheduleItem();
113
- $staffScheduleItem->load( $_post['staff_schedule_item_id'] );
114
 
115
- $break_id = isset( $_post['break_id'] ) ? $_post['break_id'] : 0;
 
116
 
117
  $in_working_time = $working_start <= $start_time && $start_time <= $working_end
118
  && $working_start <= $end_time && $end_time <= $working_end;
119
  if ( !$in_working_time || ! $staffScheduleItem->isBreakIntervalAvailable( $start_time, $end_time, $break_id ) ) {
120
- echo json_encode( array(
121
  'success' => false,
122
- 'error_msg' => __( 'The requested interval is not available', 'ab'),
123
  ) );
124
- exit;
125
  }
126
 
127
- $time_format = get_option( 'time_format' );
128
- $formatted_interval_start = date_i18n( $time_format, strtotime( $start_time ) );
129
- $formatted_interval_end = date_i18n( $time_format, strtotime( $end_time ) );
130
- $formatted_interval = $formatted_interval_start . ' - ' . $formatted_interval_end;
131
 
132
  if ( $break_id ) {
133
  $break = new AB_ScheduleItemBreak();
@@ -136,183 +203,272 @@ class AB_StaffController extends AB_Controller {
136
  $break->set( 'end_time', $end_time );
137
  $break->save();
138
 
139
- echo json_encode( array(
140
  'success' => true,
141
  'new_interval' => $formatted_interval,
142
  ) );
143
  } else {
144
  $this->form = new AB_StaffScheduleItemBreakForm();
145
- $this->form->bind( $this->getPost() );
146
 
147
  $staffScheduleItemBreak = $this->form->save();
148
  if ( $staffScheduleItemBreak ) {
149
- $breakStart = new AB_TimeChoiceWidget( array( 'use_empty' => false ) );
150
  $break_start_choices = $breakStart->render(
151
  '',
152
  $start_time,
153
- array(
154
- 'class' => 'break-start',
155
- 'data-default_value' => AB_StaffScheduleItem::WORKING_START_TIME
156
  )
157
  );
158
- $breakEnd = new AB_TimeChoiceWidget( array( 'use_empty' => false ) );
159
  $break_end_choices = $breakEnd->render(
160
  '',
161
  $end_time,
162
- array(
163
- 'class' => 'break-end',
164
- 'data-default_value' => date( 'H:i:s', strtotime( AB_StaffScheduleItem::WORKING_START_TIME . ' + 1 hour' ) )
165
  )
166
  );
167
- echo json_encode(array(
168
  'success' => true,
169
- 'item_content' => '<div class="break-interval-wrapper" data-break_id="' . $staffScheduleItemBreak->get( 'id' ) . '">
170
- <div class="ab-popup-wrapper hide-on-non-working-day">
171
- <a class="ab-popup-trigger break-interval" href="javascript:void(0)">' . $formatted_interval . '</a>
172
- <div class="ab-popup" style="display: none">
173
- <div class="ab-arrow"></div>
174
- <div class="error" style="display: none"></div>
175
- <div class="ab-content">
176
- <table cellspacing="0" cellpadding="0">
177
- <tr>
178
- <td>' . $break_start_choices . ' <span class="hide-on-non-working-day">' . __( 'to', 'ab') . '</span> ' . $break_end_choices . '</td>
179
- </tr>
180
- <tr>
181
- <td>
182
- <a class="btn btn-info ab-popup-save ab-save-break">' . __('Save break','ab') . '</a>
183
- <a class="ab-popup-close" href="#">' . __('Cancel', 'ab') . '</a>
184
- </td>
185
- </tr>
186
- </table>
187
- <a class="ab-popup-close ab-popup-close-icon" href="javascript:void(0)"></a>
188
- </div>
189
- </div>
190
- </div>
191
- <img class="delete-break" src="' . plugins_url( 'resources/images/delete_cross.png', dirname(__FILE__).'/../../AB_Backend.php' ) . '" />
192
- </div>'
193
  ) );
194
  } else {
195
- echo json_encode( array(
196
  'success' => false,
197
- 'error_msg' => __( 'Error adding the break interval', 'ab'),
198
  ) );
199
  }
200
  }
201
-
202
- exit;
203
  }
204
 
205
- public function executeDeleteStaffScheduleBreak() {
 
206
  $break = new AB_ScheduleItemBreak();
207
- $break->load( $this->_post['id'] );
208
  $break->delete();
209
- exit;
 
210
  }
211
 
212
- public function executeStaffServicesUpdate() {
 
213
  $this->form = new AB_StaffServicesForm();
214
- $this->form->bind($this->getPost());
215
  $this->form->save();
216
- exit;
 
217
  }
218
 
219
- public function executeEditStaff() {
 
220
  $this->form = new AB_StaffMemberEditForm();
221
  $this->staff = new AB_Staff();
222
- $this->staff->load( $this->_get['id'] );
223
- if(isset($_SESSION['was_update'])){
224
- unset($_SESSION['was_update']);
225
- $this->update = true;
226
- $this->render( 'edit');
227
- }else{
228
- $this->render('edit');
229
- }
230
  exit;
231
  }
232
 
233
- public function updateStaff() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  $form = new AB_StaffMemberEditForm();
235
- $form->bind($this->getPost(), $this->getFiles());
236
- $form->save();
 
237
  // Set staff id to load the form for.
238
- $this->active_staff_id = $_POST['id'];
239
- $_SESSION['was_update'] = true;
 
 
 
 
 
 
240
  }
241
 
242
- public function executeDeleteStaffAvatar() {
 
243
  $staff = new AB_Staff();
244
- $staff->load( $this->_post['id'] );
245
- unlink( $staff->get( 'avatar_path' ) );
 
 
 
 
 
 
 
 
 
 
 
246
  $staff->set( 'avatar_url', '' );
247
  $staff->set( 'avatar_path', '' );
248
  $staff->save();
249
- exit;
250
  }
251
 
252
- public function executeStaffHolidays() {
253
- $this->id = isset( $this->_post['id'] ) ? intval( $this->_post['id'] ) : false;
 
254
  $this->holidays = $this->getHolidays( $this->id );
255
- $this->render('holidays');
256
  exit;
257
  }
258
 
259
- public function executeStaffHolidaysUpdate() {
260
-
261
- $id = $this->getParameter('id');
262
- $holiday = $this->getParameter('holiday') == 'true';
263
- $repeat = $this->getParameter('repeat') == 'true';
264
- $day = $this->getParameter('day', false);
265
- $staff_id = $this->getParameter('staff_id');
266
 
267
  if ( $staff_id ) {
268
- // update or delete the event
269
  if ( $id ) {
270
  if ( $holiday ) {
271
- $this->getWpdb()->update( 'ab_holiday', array( 'repeat_event' => intval( $repeat ) ), array( 'id' => $id ), array( '%d' ) );
272
  } else {
273
- $this->getWpdb()->delete( 'ab_holiday', array( 'id' => $id ), array( '%d' ) );
274
  }
275
- // add the new event
276
- } else if ( $holiday && $day ) {
277
- $day = new DateTime($day);
278
- $this->getWpdb()->insert( 'ab_holiday', array( 'holiday' => date( 'Y-m-d H:i:s', $day->format( 'U' ) ), 'repeat_event' => intval( $repeat ), 'staff_id' => $staff_id ), array( '%s', '%d', '%d' ) );
279
  }
280
 
281
- // and return refreshed events
282
- echo $this->getHolidays($staff_id);
283
  }
284
  exit;
285
  }
286
 
287
-
288
-
289
  // Protected methods.
290
 
291
- /**
292
- * Override parent method to add 'wp_ajax_ab_' prefix
293
- * so current 'execute*' methods look nicer.
294
- */
295
- protected function registerWpActions( $prefix = '' ) {
296
- parent::registerWpActions( 'wp_ajax_ab_' );
297
- }
298
-
299
- protected function getHolidays($id) {
300
- $collection = $this->getWpdb()->get_results( $this->getWpdb()->prepare( "SELECT * FROM ab_holiday WHERE staff_id = %d", $id ) );
301
  $holidays = array();
302
  if ( count( $collection ) ) {
303
  foreach ( $collection as $holiday ) {
304
- $holidays[$holiday->id] = array(
305
- 'm' => intval(date('m', strtotime($holiday->holiday))),
306
- 'd' => intval(date('d', strtotime($holiday->holiday))),
307
- 'title' => $holiday->title,
308
  );
309
  // if not repeated holiday, add the year
310
- if ( ! $holiday->repeat_event ) {
311
- $holidays[$holiday->id]['y'] = intval(date('Y', strtotime($holiday->holiday)));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  }
 
 
313
  }
 
 
314
  }
315
 
316
- return json_encode( (object) $holidays );
317
  }
 
 
 
 
 
 
 
 
 
 
 
 
318
  }
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
 
 
 
 
 
 
 
2
 
3
  /**
4
  * Class AB_StaffController
11
  */
12
  class AB_StaffController extends AB_Controller {
13
 
14
+ const page_slug = 'ab-system-staff';
15
+
16
+ protected function getPermissions()
17
+ {
18
+ return get_option( 'ab_settings_allow_staff_members_edit_profile' ) ? array( '_this' => 'user' ) : array();
19
+ }
20
+
21
+ public function index()
22
+ {
23
+ /** @var WP_Locale $wp_locale */
24
+ global $wp_locale;
25
+
26
+ $this->enqueueStyles( array(
27
+ 'frontend' => array(
28
+ 'css/intlTelInput.css',
29
+ 'css/ladda.min.css',
30
+ ),
31
+ 'backend' => array(
32
+ 'css/bookly.main-backend.css',
33
+ 'bootstrap/css/bootstrap.min.css',
34
+ 'css/jCal.css',
35
+ ),
36
+ 'module' => array(
37
+ 'css/staff.css'
38
+ )
39
+ ) );
40
+
41
+ $this->enqueueScripts( array(
42
+ 'backend' => array(
43
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
44
+ 'js/ab_popup.js' => array( 'jquery' ),
45
+ 'js/jCal.js' => array( 'jquery' ),
46
  ),
47
+ 'module' => array(
48
+ 'js/staff.js' => array( 'jquery-ui-sortable', 'jquery' ),
49
+ ),
50
+ 'frontend' => array(
51
+ 'js/intlTelInput.min.js' => array( 'jquery' ),
52
+ 'js/spin.min.js' => array( 'jquery' ),
53
+ 'js/ladda.min.js' => array( 'jquery' ),
 
54
  )
55
  ) );
56
 
57
+ wp_localize_script( 'ab-staff.js', 'BooklyL10n', array(
58
+ 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
59
+ 'we_are_not_working' => __( 'We are not working on this day', 'bookly' ),
60
+ 'repeat' => __( 'Repeat every year', 'bookly' ),
61
+ 'months' => array_values( $wp_locale->month ),
62
+ 'days' => array_values( $wp_locale->weekday_abbrev ),
63
+ 'country' => get_option( 'ab_settings_phone_default_country' ),
64
+ 'intlTelInput_utils' => plugins_url( 'intlTelInput.utils.js', AB_PATH . '/frontend/resources/js/intlTelInput.utils.js' ),
65
+ ) );
66
 
67
  $this->form = new AB_StaffMemberNewForm();
68
+
69
+ $this->staff_members =AB_Staff::query()->sortBy( 'position' )->limit(1)->fetchArray();
70
+
71
+ $this->active_staff_id = 1;
72
 
73
  $this->render( 'list' );
74
  }
75
 
76
+ public function executeCreateStaff()
77
+ {
78
+ $this->form = new AB_StaffMemberNewForm();
79
+ $this->form->bind( $this->getPostParameters() );
80
+
81
+ $staff = $this->form->save();
82
+ if ( $staff ) {
83
+ $this->render( 'list_item', array( 'staff' => $staff ) );
84
+ // Register string for translate in WPML.
85
+ do_action( 'wpml_register_single_string', 'bookly', 'staff_' . $staff->get( 'id' ), $staff->get( 'full_name' ) );
86
+ }
87
+ exit;
88
+ }
89
+
90
+ public function executeUpdateStaffPosition()
91
+ {
92
+ $staff_sorts = $this->getParameter( 'position' );
93
+ foreach ( $staff_sorts as $position => $staff_id ) {
94
+ $staff_sort = new AB_Staff();
95
+ $staff_sort->load( $staff_id );
96
+ $staff_sort->set( 'position', $position );
97
+ $staff_sort->save();
98
+ }
99
+ }
100
+
101
+ public function executeStaffServices()
102
+ {
103
  $this->form = new AB_StaffServicesForm();
104
+ $this->form->load( 1 );
105
+ $this->staff_id = 1;
106
  $this->render( 'services' );
107
  exit;
108
  }
109
 
110
+ public function executeStaffSchedule()
111
+ {
112
  $staff = new AB_Staff();
113
+ $staff->load( 1 );
114
+ $this->schedule_items = $staff->getScheduleItems();
115
+ $this->staff_id = 1;
116
  $this->render( 'schedule' );
117
  exit;
118
  }
119
 
120
+ public function executeStaffScheduleUpdate()
121
+ {
122
  $this->form = new AB_StaffScheduleForm();
123
+ $this->form->bind( $this->getPostParameters() );
124
  $this->form->save();
125
+
126
+ wp_send_json_success();
127
  }
128
 
129
+ /**
130
+ *
131
+ * @throws Exception
132
+ */
133
+ public function executeResetBreaks()
134
+ {
135
+ $breaks = $this->getParameter( 'breaks' );
136
+
137
+ // Remove all breaks for staff member.
138
+ $break = new AB_ScheduleItemBreak();
139
+ $break->removeBreaksByStaffId( 1 );
140
+ $html_breaks = array();
141
+
142
+ // Restore previous breaks.
143
+ if ( isset( $breaks['breaks'] ) && is_array( $breaks['breaks'] ) ) {
144
+ foreach ($breaks['breaks'] as $day) {
145
+ $schedule_item_break = new AB_ScheduleItemBreak();
146
+ $schedule_item_break->setFields($day);
147
+ $schedule_item_break->save();
148
+ }
149
+ }
150
+
151
+ $staff = new AB_Staff();
152
+ $staff->load( 1 );
153
+
154
+ // Make array with breaks (html) for each day.
155
+ foreach ( $staff->getScheduleItems() as $item ) {
156
+ /** @var AB_StaffScheduleItem $item */
157
+ $html_breaks[ $item->get( 'id' )] = $this->render( '_breaks', array(
158
+ 'day_is_not_available' => null === $item->get( 'start_time' ),
159
+ 'item' => $item,
160
+ ), false );
161
+ }
162
+
163
+ wp_send_json( $html_breaks );
164
+ }
165
 
166
+ public function executeStaffScheduleHandleBreak()
167
+ {
168
+ $start_time = $this->getParameter( 'start_time' );
169
+ $end_time = $this->getParameter( 'end_time' );
170
+ $working_start = $this->getParameter( 'working_start' );
171
+ $working_end = $this->getParameter( 'working_end' );
172
 
173
+ if ( AB_DateTimeUtils::timeToSeconds( $start_time ) >= AB_DateTimeUtils::timeToSeconds( $end_time ) ) {
174
+ wp_send_json( array(
175
  'success' => false,
176
+ 'error_msg' => __( 'The start time must be less than the end one', 'bookly' ),
177
  ) );
 
178
  }
179
 
180
  $staffScheduleItem = new AB_StaffScheduleItem();
181
+ $staffScheduleItem->load( $this->getParameter( 'staff_schedule_item_id' ) );
182
 
183
+ $bound = array( $staffScheduleItem->get( 'start_time' ), $staffScheduleItem->get( 'end_time' ) );
184
+ $break_id = $this->getParameter( 'break_id', 0 );
185
 
186
  $in_working_time = $working_start <= $start_time && $start_time <= $working_end
187
  && $working_start <= $end_time && $end_time <= $working_end;
188
  if ( !$in_working_time || ! $staffScheduleItem->isBreakIntervalAvailable( $start_time, $end_time, $break_id ) ) {
189
+ wp_send_json( array(
190
  'success' => false,
191
+ 'error_msg' => __( 'The requested interval is not available', 'bookly' ),
192
  ) );
 
193
  }
194
 
195
+ $formatted_start = AB_DateTimeUtils::formatTime( AB_DateTimeUtils::timeToSeconds( $start_time ) );
196
+ $formatted_end = AB_DateTimeUtils::formatTime( AB_DateTimeUtils::timeToSeconds( $end_time ) );
197
+ $formatted_interval = $formatted_start . ' - ' . $formatted_end;
 
198
 
199
  if ( $break_id ) {
200
  $break = new AB_ScheduleItemBreak();
203
  $break->set( 'end_time', $end_time );
204
  $break->save();
205
 
206
+ wp_send_json( array(
207
  'success' => true,
208
  'new_interval' => $formatted_interval,
209
  ) );
210
  } else {
211
  $this->form = new AB_StaffScheduleItemBreakForm();
212
+ $this->form->bind( $this->getPostParameters() );
213
 
214
  $staffScheduleItemBreak = $this->form->save();
215
  if ( $staffScheduleItemBreak ) {
216
+ $breakStart = new AB_TimeChoiceWidget( array( 'use_empty' => false, 'type' => 'from', 'bound' => $bound ) );
217
  $break_start_choices = $breakStart->render(
218
  '',
219
  $start_time,
220
+ array( 'class' => 'break-start form-control',
221
+ 'data-default_value' => AB_StaffScheduleItem::WORKING_START_TIME
 
222
  )
223
  );
224
+ $breakEnd = new AB_TimeChoiceWidget( array( 'use_empty' => false, 'type' => 'bound', 'bound' => $bound ) );
225
  $break_end_choices = $breakEnd->render(
226
  '',
227
  $end_time,
228
+ array( 'class' => 'break-end form-control',
229
+ 'data-default_value' => date( 'H:i:s', strtotime( AB_StaffScheduleItem::WORKING_START_TIME . ' + 1 hour' ) )
 
230
  )
231
  );
232
+ wp_send_json( array(
233
  'success' => true,
234
+ 'item_content' => $this->render( '_break', array(
235
+ 'staff_schedule_item_break_id' => $staffScheduleItemBreak->get( 'id' ),
236
+ 'formatted_interval' => $formatted_interval,
237
+ 'break_start_choices' => $break_start_choices,
238
+ 'break_end_choices' => $break_end_choices,
239
+ ), false),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
  ) );
241
  } else {
242
+ wp_send_json( array(
243
  'success' => false,
244
+ 'error_msg' => __( 'Error adding the break interval', 'bookly' ),
245
  ) );
246
  }
247
  }
 
 
248
  }
249
 
250
+ public function executeDeleteStaffScheduleBreak()
251
+ {
252
  $break = new AB_ScheduleItemBreak();
253
+ $break->load( 1 );
254
  $break->delete();
255
+
256
+ wp_send_json_success();
257
  }
258
 
259
+ public function executeStaffServicesUpdate()
260
+ {
261
  $this->form = new AB_StaffServicesForm();
262
+ $this->form->bind( $this->getPostParameters() );
263
  $this->form->save();
264
+
265
+ wp_send_json_success();
266
  }
267
 
268
+ public function executeEditStaff()
269
+ {
270
  $this->form = new AB_StaffMemberEditForm();
271
  $this->staff = new AB_Staff();
272
+ $this->staff->load( 1 );
273
+ $staff_errors = array();
274
+
275
+ $this->authUrl = false;
276
+ // Register string for translate in WPML.
277
+ do_action( 'wpml_register_single_string', 'bookly', 'staff_' . $this->staff->get( 'id' ), $this->staff->get( 'full_name' ) );
278
+
279
+ $this->render( 'edit', array( 'staff_errors' => $staff_errors ) );
280
  exit;
281
  }
282
 
283
+ /**
284
+ * Update staff from POST request.
285
+ * @see AB_Backend.php
286
+ */
287
+ public function updateStaff()
288
+ {
289
+ if ( ! AB_Utils::isCurrentUserAdmin() ) {
290
+ // Check permissions to prevent one staff member from updating profile of another staff member.
291
+ do {
292
+ if ( get_option( 'ab_settings_allow_staff_members_edit_profile' ) ) {
293
+ $staff = new AB_Staff();
294
+ $staff->load( 1 );
295
+ if ( $staff->get( 'wp_user_id' ) == get_current_user_id() ) {
296
+ unset ( $_POST['wp_user_id'] );
297
+ break;
298
+ }
299
+ }
300
+ do_action( 'admin_page_access_denied' );
301
+ wp_die( __( 'Bookly: You do not have sufficient permissions to access this page.', 'bookly' ) );
302
+ } while ( 0 );
303
+ }
304
+
305
  $form = new AB_StaffMemberEditForm();
306
+ $form->bind( $this->getPostParameters(), $_FILES );
307
+ $result = $form->save();
308
+
309
  // Set staff id to load the form for.
310
+ $this->active_staff_id = 1;
311
+
312
+ if ( $result === false && array_key_exists( 'google_calendar', $form->getErrors() ) ) {
313
+ $errors = $form->getErrors();
314
+ $_SESSION['google_calendar_error'] = $errors['google_calendar'];
315
+ } else {
316
+ $_SESSION['bookly_updated'] = true;
317
+ }
318
  }
319
 
320
+ public function executeDeleteStaff()
321
+ {
322
  $staff = new AB_Staff();
323
+ $staff->load( $this->getParameter( 'id' ) );
324
+ $staff->delete();
325
+ $form = new AB_StaffMemberForm();
326
+ wp_send_json( $form->getUsersForStaff() );
327
+ }
328
+
329
+ public function executeDeleteStaffAvatar()
330
+ {
331
+ $staff = new AB_Staff();
332
+ $staff->load( 1 );
333
+ if ( file_exists( $staff->get( 'avatar_path' ) ) ) {
334
+ unlink( $staff->get( 'avatar_path' ) );
335
+ }
336
  $staff->set( 'avatar_url', '' );
337
  $staff->set( 'avatar_path', '' );
338
  $staff->save();
339
+ wp_send_json_success();
340
  }
341
 
342
+ public function executeStaffHolidays()
343
+ {
344
+ $this->id = $this->getParameter( 'id', 0 );
345
  $this->holidays = $this->getHolidays( $this->id );
346
+ $this->render( 'holidays' );
347
  exit;
348
  }
349
 
350
+ public function executeStaffHolidaysUpdate()
351
+ {
352
+ $id = 1;
353
+ $holiday = $this->getParameter( 'holiday' ) == 'true';
354
+ $repeat = $this->getParameter( 'repeat' ) == 'true';
355
+ $day = $this->getParameter( 'day', false );
356
+ $staff_id = $this->getParameter( 'staff_id' );
357
 
358
  if ( $staff_id ) {
359
+ // Update or delete the event.
360
  if ( $id ) {
361
  if ( $holiday ) {
362
+ $this->getWpdb()->update( AB_Holiday::getTableName(), array( 'repeat_event' => intval( $repeat ) ), array( 'id' => $id ), array( '%d' ) );
363
  } else {
364
+ AB_Holiday::query()->delete()->where( 'id', $id )->execute();
365
  }
366
+ // Add the new event.
367
+ } elseif ( $holiday && $day ) {
368
+ $this->getWpdb()->insert( AB_Holiday::getTableName(), array( 'date' => $day, 'repeat_event' => intval( $repeat ), 'staff_id' => $staff_id ), array( '%s', '%d', '%d' ) );
 
369
  }
370
 
371
+ // And return refreshed events.
372
+ echo $this->getHolidays( $staff_id );
373
  }
374
  exit;
375
  }
376
 
 
 
377
  // Protected methods.
378
 
379
+ protected function getHolidays( $id )
380
+ {
381
+ $collection = AB_Holiday::query( 'h' )->where( 'h.staff_id', 1 )->fetchArray();
 
 
 
 
 
 
 
382
  $holidays = array();
383
  if ( count( $collection ) ) {
384
  foreach ( $collection as $holiday ) {
385
+ $holidays[$holiday['id']] = array(
386
+ 'm' => intval( date( 'm', strtotime( $holiday['date'] ) ) ),
387
+ 'd' => intval( date( 'd', strtotime( $holiday['date'] ) ) ),
388
+ 'title' => $holiday['title'],
389
  );
390
  // if not repeated holiday, add the year
391
+ if ( ! $holiday['repeat_event'] ) {
392
+ $holidays[ $holiday['id'] ]['y'] = intval( date( 'Y', strtotime( $holiday['date'] ) ) );
393
+ }
394
+ }
395
+ }
396
+
397
+ return json_encode( $holidays );
398
+ }
399
+
400
+ /**
401
+ * Extend parent method to control access on staff member level.
402
+ *
403
+ * @param string $action
404
+ * @return bool
405
+ */
406
+ protected function hasAccess( $action )
407
+ {
408
+ if ( parent::hasAccess( $action ) ) {
409
+
410
+ if ( ! AB_Utils::isCurrentUserAdmin() ) {
411
+ $staff = new AB_Staff();
412
+
413
+ switch ( $action ) {
414
+ case 'executeEditStaff':
415
+ case 'executeDeleteStaffAvatar':
416
+ case 'executeStaffServices':
417
+ case 'executeStaffSchedule':
418
+ case 'executeStaffHolidays':
419
+ $staff->load( 1 );
420
+ break;
421
+ case 'executeStaffServicesUpdate':
422
+ case 'executeStaffHolidaysUpdate':
423
+ $staff->load( $this->getParameter( 'staff_id' ) );
424
+ break;
425
+ case 'executeStaffScheduleHandleBreak':
426
+ $staffScheduleItem = new AB_StaffScheduleItem();
427
+ $staffScheduleItem->load( $this->getParameter( 'staff_schedule_item_id' ) );
428
+ $staff->load( $staffScheduleItem->get( 'staff_id' ) );
429
+ break;
430
+ case 'executeDeleteStaffScheduleBreak':
431
+ $break = new AB_ScheduleItemBreak();
432
+ $break->load( 1 );
433
+ $staffScheduleItem = new AB_StaffScheduleItem();
434
+ $staffScheduleItem->load( $break->get( 'staff_schedule_item_id' ) );
435
+ $staff->load( $staffScheduleItem->get( 'staff_id' ) );
436
+ break;
437
+ case 'executeStaffScheduleUpdate':
438
+ if ( $this->hasParameter( 'days' ) ) {
439
+ foreach ( $this->getParameter( 'days' ) as $id => $day_index ) {
440
+ $staffScheduleItem = new AB_StaffScheduleItem();
441
+ $staffScheduleItem->load( $id );
442
+ $staff = new AB_Staff();
443
+ $staff->load( $staffScheduleItem->get( 'staff_id' ) );
444
+ if ( $staff->get( 'wp_user_id' ) != get_current_user_id() ) {
445
+ return false;
446
+ }
447
+ }
448
+ }
449
+ break;
450
+ default:
451
+ return false;
452
  }
453
+
454
+ return $staff->get( 'wp_user_id' ) == get_current_user_id();
455
  }
456
+
457
+ return true;
458
  }
459
 
460
+ return false;
461
  }
462
+
463
+ /**
464
+ * Override parent method to add 'wp_ajax_ab_' prefix
465
+ * so current 'execute*' methods look nicer.
466
+ *
467
+ * @param string $prefix
468
+ */
469
+ protected function registerWpActions( $prefix = '' )
470
+ {
471
+ parent::registerWpActions( 'wp_ajax_ab_' );
472
+ }
473
+
474
  }
backend/modules/staff/forms/AB_StaffMemberEditForm.php CHANGED
@@ -1,40 +1,59 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
- if ( !function_exists( 'wp_handle_upload' ) ) require_once(ABSPATH . 'wp-admin/includes/file.php');
4
- include dirname(__FILE__) . '/../../../../lib/AB_ImageResize.php';
5
 
6
  class AB_StaffMemberEditForm extends AB_StaffMemberForm {
7
 
8
- public function configure() {
 
 
 
9
  $this->setFields( array(
10
  'wp_user_id',
11
  'full_name',
12
- 'email',
13
  'phone',
14
- 'avatar'
 
15
  ) );
16
  }
17
 
18
- public function bind( array $post, array $files = array() ) {
19
- if ( isset( $post[ 'wp_user_id' ] ) && ! $post[ 'wp_user_id' ] ) {
20
- $post[ 'wp_user_id' ] = null;
21
- }
22
-
 
23
  parent::bind( $post );
24
 
25
- if ( isset ( $files[ 'avatar' ] ) && $files[ 'avatar' ][ 'tmp_name' ] ) {
 
 
 
 
 
 
 
26
 
27
- if ( in_array( $files[ 'avatar' ][ 'type' ], array( "image/gif", "image/jpeg", "image/png" ) ) ) {
28
- $movefile = wp_handle_upload( $files[ 'avatar' ], array( 'test_form' => false ) );
29
- if ( $movefile ) {
30
- $imageResize = new AB_ImageResize( $movefile[ 'file' ] );
31
- $imageResize->resizeImage( 80, 80 );
32
- $imageResize->saveImage( $movefile[ 'file' ], 80 );
33
 
34
- $this->data[ 'avatar_path' ] = $movefile[ 'file' ];
35
- $this->data[ 'avatar_url' ] = $movefile[ 'url' ];
 
 
 
 
36
  }
37
  }
38
  }
39
  }
 
 
 
 
 
 
 
 
 
40
  }
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ if ( ! function_exists( 'wp_handle_upload' ) ) { require_once( ABSPATH . 'wp-admin/includes/file.php' ); }
 
 
3
 
4
  class AB_StaffMemberEditForm extends AB_StaffMemberForm {
5
 
6
+ private $errors = array();
7
+
8
+ public function configure()
9
+ {
10
  $this->setFields( array(
11
  'wp_user_id',
12
  'full_name',
13
+ 'email',
14
  'phone',
15
+ 'avatar',
16
+ 'position',
17
  ) );
18
  }
19
 
20
+ /**
21
+ * @param array $post
22
+ * @param array $files
23
+ */
24
+ public function bind( array $post, array $files = array() )
25
+ {
26
  parent::bind( $post );
27
 
28
+ if ( isset ( $files['avatar'] ) && $files['avatar']['tmp_name'] ) {
29
+
30
+ if ( in_array( $files['avatar']['type'], array( 'image/gif', 'image/jpeg', 'image/png' ) ) ) {
31
+ $uploaded = wp_handle_upload( $files['avatar'], array( 'test_form' => false ) );
32
+ if ( $uploaded ) {
33
+ $editor = wp_get_image_editor( $uploaded['file'] );
34
+ $editor->resize( 200, 200 );
35
+ $editor->save( $uploaded['file'] );
36
 
37
+ $this->data['avatar_path'] = $uploaded['file'];
38
+ $this->data['avatar_url'] = $uploaded['url'];
 
 
 
 
39
 
40
+ // Remove old image.
41
+ $staff = new AB_Staff();
42
+ $staff->load( $post['id'] );
43
+ if ( file_exists( $staff->get( 'avatar_path' ) ) ) {
44
+ unlink( $staff->get( 'avatar_path' ) );
45
+ }
46
  }
47
  }
48
  }
49
  }
50
+
51
+ /**
52
+ * @return array
53
+ */
54
+ public function getErrors()
55
+ {
56
+ return $this->errors;
57
+ }
58
+
59
  }
backend/modules/staff/forms/AB_StaffMemberForm.php CHANGED
@@ -1,7 +1,4 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
- include dirname(__FILE__) . '/../../../../lib/entities/AB_Staff.php';
5
 
6
  /**
7
  * Class AB_StaffMemberForm
@@ -13,7 +10,8 @@ class AB_StaffMemberForm extends AB_Form {
13
  /**
14
  * Constructor.
15
  */
16
- public function __construct() {
 
17
  parent::$entity_class = 'AB_Staff';
18
  parent::__construct();
19
  }
@@ -30,21 +28,35 @@ class AB_StaffMemberForm extends AB_Form {
30
  * @param integer $staff_id If null then it means new staff
31
  * @return array
32
  */
33
- public function getUsersForStaff($staff_id = null) {
 
34
  /** @var wpdb $wpdb */
35
  global $wpdb;
36
- global $table_prefix;
37
-
38
- $query = sprintf(
39
- 'SELECT ID, user_email, display_name FROM %susers
40
- WHERE ID NOT IN(SELECT DISTINCT IFNULL( wp_user_id, 0 ) FROM ab_staff %s)
41
  ORDER BY display_name',
42
- $table_prefix,
43
- $staff_id !== null
44
- ? "WHERE ab_staff.id <> $staff_id"
45
- : ''
46
- );
47
-
48
- return $wpdb->get_results( $query );
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  }
 
50
  }
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
 
2
 
3
  /**
4
  * Class AB_StaffMemberForm
10
  /**
11
  * Constructor.
12
  */
13
+ public function __construct()
14
+ {
15
  parent::$entity_class = 'AB_Staff';
16
  parent::__construct();
17
  }
28
  * @param integer $staff_id If null then it means new staff
29
  * @return array
30
  */
31
+ public function getUsersForStaff( $staff_id = null )
32
+ {
33
  /** @var wpdb $wpdb */
34
  global $wpdb;
35
+ if ( ! is_multisite() ) {
36
+ $query = sprintf(
37
+ 'SELECT ID, user_email, display_name FROM ' . $wpdb->users . '
38
+ WHERE ID NOT IN(SELECT DISTINCT IFNULL( wp_user_id, 0 ) FROM ' . AB_Staff::getTableName() . ' %s)
 
39
  ORDER BY display_name',
40
+ $staff_id !== null
41
+ ? "WHERE " . AB_Staff::getTableName() . ".id <> $staff_id"
42
+ : ''
43
+ );
44
+ $users = $wpdb->get_results( $query );
45
+ } else {
46
+ // In Multisite show users only for current blog.
47
+ if( $staff_id == null ) {
48
+ $query = AB_Staff::query( 's' )->select( 'DISTINCT wp_user_id' )->whereNot( 'wp_user_id', null );
49
+ } else {
50
+ $query = AB_Staff::query( 's' )->select( 'wp_user_id' )->whereNot( 'id', $staff_id );
51
+ }
52
+ $occupied_wp_users = array();
53
+ foreach ( $query->fetchArray() as $staff ) {
54
+ $occupied_wp_users[] = $staff['wp_user_id'];
55
+ }
56
+ $users = get_users( array( 'blog_id' => get_current_blog_id(), 'orderby' => 'display_name', 'exclude' => $occupied_wp_users ) );
57
+ }
58
+
59
+ return $users;
60
  }
61
+
62
  }
backend/modules/staff/forms/AB_StaffMemberNewForm.php CHANGED
@@ -1,13 +1,12 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
- include 'AB_StaffMemberForm.php';
4
 
5
  /**
6
  * Class AB_StaffMemberNewForm
7
  */
8
  class AB_StaffMemberNewForm extends AB_StaffMemberForm {
9
 
10
- public function configure() {
 
11
  $this->setFields( array( 'wp_user_id', 'full_name' ) );
12
  }
13
  }
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
2
 
3
  /**
4
  * Class AB_StaffMemberNewForm
5
  */
6
  class AB_StaffMemberNewForm extends AB_StaffMemberForm {
7
 
8
+ public function configure()
9
+ {
10
  $this->setFields( array( 'wp_user_id', 'full_name' ) );
11
  }
12
  }
backend/modules/staff/forms/AB_StaffScheduleForm.php CHANGED
@@ -1,6 +1,4 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
 
5
  /**
6
  * Class AB_StaffScheduleForm
@@ -10,31 +8,27 @@ class AB_StaffScheduleForm extends AB_Form {
10
  /**
11
  * Constructor.
12
  */
13
- public function __construct() {
 
14
  parent::$entity_class = 'AB_StaffScheduleItem';
15
  parent::__construct();
16
  }
17
 
18
- /**
19
- * @var wpdb $wpdb
20
- */
21
- private $wpdb;
22
-
23
- public function configure() {
24
- global $wpdb;
25
- $this->wpdb = $wpdb;
26
  $this->setFields( array( 'days', 'staff_id', 'start_time', 'end_time' ) );
27
  }
28
 
29
- public function save() {
 
30
  if ( isset($this->data['days']) ) {
31
- foreach ( $this->data['days'] as $id => $schedule_item_id ) {
32
  $staffScheduleItem = new AB_StaffScheduleItem();
33
  $staffScheduleItem->load( $id );
34
- $staffScheduleItem->set( 'schedule_item_id', $schedule_item_id );
35
- if ($this->data['start_time'][$schedule_item_id]) {
36
- $staffScheduleItem->set( 'start_time', $this->data['start_time'][$schedule_item_id] );
37
- $staffScheduleItem->set( 'end_time', $this->data['end_time'][$schedule_item_id] );
38
  } else {
39
  $staffScheduleItem->set( 'start_time', null );
40
  $staffScheduleItem->set( 'end_time', null );
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
2
 
3
  /**
4
  * Class AB_StaffScheduleForm
8
  /**
9
  * Constructor.
10
  */
11
+ public function __construct()
12
+ {
13
  parent::$entity_class = 'AB_StaffScheduleItem';
14
  parent::__construct();
15
  }
16
 
17
+ public function configure()
18
+ {
 
 
 
 
 
 
19
  $this->setFields( array( 'days', 'staff_id', 'start_time', 'end_time' ) );
20
  }
21
 
22
+ public function save()
23
+ {
24
  if ( isset($this->data['days']) ) {
25
+ foreach ( $this->data['days'] as $id => $day_index ) {
26
  $staffScheduleItem = new AB_StaffScheduleItem();
27
  $staffScheduleItem->load( $id );
28
+ $staffScheduleItem->set( 'day_index', $day_index );
29
+ if ($this->data['start_time'][$day_index]) {
30
+ $staffScheduleItem->set( 'start_time', $this->data['start_time'][$day_index] );
31
+ $staffScheduleItem->set( 'end_time', $this->data['end_time'][$day_index] );
32
  } else {
33
  $staffScheduleItem->set( 'start_time', null );
34
  $staffScheduleItem->set( 'end_time', null );
backend/modules/staff/forms/AB_StaffScheduleItemBreakForm.php CHANGED
@@ -1,8 +1,4 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
-
5
- include dirname(__FILE__) . '/../../../../lib/entities/AB_ScheduleItemBreak.php';
6
 
7
  /**
8
  * Class AB_StaffScheduleItemBreakForm
@@ -12,16 +8,19 @@ class AB_StaffScheduleItemBreakForm extends AB_Form {
12
  /**
13
  * Constructor.
14
  */
15
- public function __construct() {
 
16
  parent::$entity_class = 'AB_ScheduleItemBreak';
17
  parent::__construct();
18
  }
19
 
20
- public function configure() {
 
21
  $this->setFields( array(
22
  'staff_schedule_item_id',
23
  'start_time',
24
  'end_time'
25
  ) );
26
  }
27
- }
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
 
 
2
 
3
  /**
4
  * Class AB_StaffScheduleItemBreakForm
8
  /**
9
  * Constructor.
10
  */
11
+ public function __construct()
12
+ {
13
  parent::$entity_class = 'AB_ScheduleItemBreak';
14
  parent::__construct();
15
  }
16
 
17
+ public function configure()
18
+ {
19
  $this->setFields( array(
20
  'staff_schedule_item_id',
21
  'start_time',
22
  'end_time'
23
  ) );
24
  }
25
+
26
+ }
backend/modules/staff/forms/AB_StaffServicesForm.php CHANGED
@@ -1,9 +1,4 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
-
5
- include dirname(__FILE__) . '/../../../../lib/entities/AB_Category.php';
6
- include dirname(__FILE__) . '/../../../../lib/entities/AB_StaffService.php';
7
 
8
  /**
9
  * Class AB_StaffServicesForm
@@ -13,7 +8,8 @@ class AB_StaffServicesForm extends AB_Form {
13
  /**
14
  * Constructor.
15
  */
16
- public function __construct() {
 
17
  parent::$entity_class = 'AB_StaffService';
18
  parent::__construct();
19
  }
@@ -43,45 +39,34 @@ class AB_StaffServicesForm extends AB_Form {
43
  */
44
  private $uncategorized_services = array();
45
 
46
- public function configure() {
 
47
  global $wpdb;
48
  $this->wpdb = $wpdb;
49
- $this->setFields( array( 'price', 'service', 'staff_id' ) );
50
  }
51
 
52
- public function load($staff_id) {
53
- $data = $this->wpdb->get_results( '
54
- SELECT c.name AS category_name, s.*
55
- FROM ab_category c
56
- INNER JOIN ab_service s ON c.id = s.category_id
57
- ', ARRAY_A );
58
-
59
  if ( !$data ) {
60
  $data = array();
61
  }
62
 
63
- $uncategorized_services = $this->wpdb->get_results( 'SELECT * FROM ab_service WHERE category_id IS NULL' );
64
- foreach ( $uncategorized_services as $uncategorized_service ) {
65
- $abService = new AB_Service();
66
- $abService->setData($uncategorized_service);
67
 
68
- $this->uncategorized_services[] = $abService;
69
- }
70
-
71
- $rows = $this->wpdb->get_results( $this->wpdb->prepare('
72
- SELECT s.service_id, s.price
73
- FROM ab_staff_service s
74
- WHERE s.staff_id = %d
75
- ', $staff_id) );
76
-
77
- if ( $rows ) {
78
- foreach ($rows as $row) {
79
- $this->selected[$row->service_id] = $row->price;
80
  }
81
  }
82
 
83
- foreach ($data as $row) {
84
- if ( !isset($this->collection[ $row['category_id'] ]) ) {
85
  $abCategory = new AB_Category();
86
  $abCategory->set( 'id', $row['category_id'] );
87
  $abCategory->set( 'name', $row['category_name'] );
@@ -89,23 +74,24 @@ class AB_StaffServicesForm extends AB_Form {
89
  }
90
  unset( $row['category_name'] );
91
 
92
- $abService = new AB_Service();
93
- $abService->setData($row);
94
- $this->category_services[$row['category_id']][] = $abService->get( 'id' );
95
- $this->collection[ $row['category_id'] ]->addService($abService);
96
  }
97
  }
98
 
99
- public function save() {
 
100
  $staff_id = $this->data['staff_id'];
101
  if ( $staff_id ) {
102
- $this->wpdb->delete( 'ab_staff_service', array( 'staff_id' => $staff_id ), array( '%d' ) );
103
- if ( isset($this->data['service']) ) {
104
  foreach ( $this->data['service'] as $service_id ) {
105
  $staffService = new AB_StaffService();
106
  $staffService->set( 'service_id', $service_id );
107
  $staffService->set( 'staff_id', $staff_id );
108
  $staffService->set( 'price', $this->data['price'][ $service_id ] );
 
109
  $staffService->save();
110
  }
111
  }
@@ -115,15 +101,17 @@ class AB_StaffServicesForm extends AB_Form {
115
  /**
116
  * @return AB_Category[]|array
117
  */
118
- public function getCollection() {
 
119
  return $this->collection;
120
  }
121
 
122
  /**
123
  * @return array
124
  */
125
- public function getSelected() {
126
- return is_array($this->selected) ? $this->selected : array();
 
127
  }
128
 
129
  /**
@@ -133,4 +121,5 @@ class AB_StaffServicesForm extends AB_Form {
133
  {
134
  return $this->uncategorized_services;
135
  }
 
136
  }
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
 
 
 
2
 
3
  /**
4
  * Class AB_StaffServicesForm
8
  /**
9
  * Constructor.
10
  */
11
+ public function __construct()
12
+ {
13
  parent::$entity_class = 'AB_StaffService';
14
  parent::__construct();
15
  }
39
  */
40
  private $uncategorized_services = array();
41
 
42
+ public function configure()
43
+ {
44
  global $wpdb;
45
  $this->wpdb = $wpdb;
46
+ $this->setFields( array( 'price', 'service', 'staff_id', 'capacity' ) );
47
  }
48
 
49
+ public function load( $staff_id )
50
+ {
51
+ $data = AB_Category::query( 'c' )->select( 'c.name AS category_name, s.*' )->innerJoin( 'AB_Service', 's', 's.category_id = c.id' )->fetchArray();
 
 
 
 
52
  if ( !$data ) {
53
  $data = array();
54
  }
55
 
56
+ $this->uncategorized_services = AB_Service::query( 's' )->where( 's.category_id', null )->fetchArray();
 
 
 
57
 
58
+ $staff_services = AB_StaffService::query( 'ss' )
59
+ ->select( 'ss.service_id, ss.price, ss.capacity' )
60
+ ->where( 'ss.staff_id', $staff_id )
61
+ ->fetchArray();
62
+ if ( $staff_services ) {
63
+ foreach ( $staff_services as $staff_service ) {
64
+ $this->selected[ $staff_service['service_id'] ] = array( 'price' => $staff_service['price'], 'capacity' => $staff_service['capacity'] );
 
 
 
 
 
65
  }
66
  }
67
 
68
+ foreach ( $data as $row ) {
69
+ if ( ! isset( $this->collection[ $row['category_id'] ] ) ) {
70
  $abCategory = new AB_Category();
71
  $abCategory->set( 'id', $row['category_id'] );
72
  $abCategory->set( 'name', $row['category_name'] );
74
  }
75
  unset( $row['category_name'] );
76
 
77
+ $abService = new AB_Service( $row );
78
+ $this->category_services[ $row['category_id'] ][] = $abService->get( 'id' );
79
+ $this->collection[ $row['category_id'] ]->addService( $abService );
 
80
  }
81
  }
82
 
83
+ public function save()
84
+ {
85
  $staff_id = $this->data['staff_id'];
86
  if ( $staff_id ) {
87
+ AB_StaffService::query()->delete()->where( 'staff_id', $staff_id )->execute();
88
+ if ( isset( $this->data['service'] ) ) {
89
  foreach ( $this->data['service'] as $service_id ) {
90
  $staffService = new AB_StaffService();
91
  $staffService->set( 'service_id', $service_id );
92
  $staffService->set( 'staff_id', $staff_id );
93
  $staffService->set( 'price', $this->data['price'][ $service_id ] );
94
+ $staffService->set( 'capacity', $this->data['capacity'][ $service_id ] );
95
  $staffService->save();
96
  }
97
  }
101
  /**
102
  * @return AB_Category[]|array
103
  */
104
+ public function getCollection()
105
+ {
106
  return $this->collection;
107
  }
108
 
109
  /**
110
  * @return array
111
  */
112
+ public function getSelected()
113
+ {
114
+ return $this->selected;
115
  }
116
 
117
  /**
121
  {
122
  return $this->uncategorized_services;
123
  }
124
+
125
  }
backend/modules/staff/forms/widget/AB_TimeChoiceWidget.php CHANGED
@@ -1,6 +1,4 @@
1
- <?php
2
-
3
- if ( !defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
 
5
  class AB_TimeChoiceWidget {
6
  /**
@@ -13,29 +11,39 @@ class AB_TimeChoiceWidget {
13
  *
14
  * @param array $options
15
  */
16
- public function __construct( array $options = array() ) {
 
17
  // Handle widget options.
18
  $options = array_merge( array(
19
- 'use_empty' => true,
20
- 'empty_value' => null
 
21
  ), $options );
22
 
23
  // Insert empty value if required.
24
- if ( $options[ 'use_empty' ] ) {
25
- $this->values[ null ] = $options[ 'empty_value' ];
26
  }
27
 
28
- $tf = get_option( 'time_format' );
29
- $ts_length = get_option( 'ab_settings_time_slot_length' );
30
- $time_start = new AB_DateTime( AB_StaffScheduleItem::WORKING_START_TIME, new DateTimeZone( 'UTC' ) );
31
- $time_end = new AB_DateTime( AB_StaffScheduleItem::WORKING_END_TIME, new DateTimeZone( 'UTC' ) );
 
 
 
 
 
 
 
 
 
32
 
33
  // Run the loop.
34
- while ( $time_start->format( 'U' ) <= $time_end->format( 'U' ) ) {
35
- $this->values[ $time_start->format( 'H:i:s' ) ] = $time_start->format( $tf );
36
- $time_start->modify( '+' . $ts_length . ' min' );
37
  }
38
- $this->values[ $time_end->format( 'H:i:s' ) ] = $time_end->format( $tf );
39
  }
40
 
41
  /**
@@ -47,16 +55,28 @@ class AB_TimeChoiceWidget {
47
  *
48
  * @return string
49
  */
50
- public function render( $name, $value = null, array $attributes = array() ) {
51
- $options = array();
 
52
  $attributes_str = '';
 
53
  foreach ( $this->values as $option_value => $option_text ) {
54
-
55
- $selected = strval( $value ) == strval( $option_value );
56
- $options[ ] = sprintf(
 
 
 
 
 
 
 
 
 
 
57
  '<option value="%s"%s>%s</option>',
58
  $option_value,
59
- ($selected ? ' selected="selected"' : ''),
60
  $option_text
61
  );
62
  }
@@ -64,21 +84,7 @@ class AB_TimeChoiceWidget {
64
  $attributes_str .= sprintf( ' %s="%s"', $attr_name, $attr_value );
65
  }
66
 
67
- return sprintf( '<select name="%s"%s>%s</select>', $name, $attributes_str, implode( '', $options ) );
68
  }
69
 
70
- public function renderOptions( $start, $selected = '' ) {
71
- $options = array();
72
- foreach ( $this->values as $option_value => $option_text ) {
73
- if ( $start && strval( $option_value ) < strval( $start ) ) continue;
74
- $options[ ] = sprintf(
75
- '<option value="%s"%s>%s</option>',
76
- $option_value,
77
- (strval( $selected ) == strval( $option_value ) ? 'selected="selected"' : ''),
78
- $option_text
79
- );
80
- }
81
-
82
- return $options;
83
- }
84
  }
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
2
 
3
  class AB_TimeChoiceWidget {
4
  /**
11
  *
12
  * @param array $options
13
  */
14
+ public function __construct( array $options = array() )
15
+ {
16
  // Handle widget options.
17
  $options = array_merge( array(
18
+ 'use_empty' => true,
19
+ 'empty_value' => null,
20
+ 'type' => 'from',
21
  ), $options );
22
 
23
  // Insert empty value if required.
24
+ if ( $options['use_empty'] ) {
25
+ $this->values[null] = $options['empty_value'];
26
  }
27
 
28
+ $ts_length = AB_Config::getTimeSlotLength();
29
+ if ( isset( $options['bound'] ) ) {
30
+ $time_start = AB_DateTimeUtils::timeToSeconds( $options['bound'][0] );
31
+ $time_end = AB_DateTimeUtils::timeToSeconds( $options['bound'][1] );
32
+ } else {
33
+ $time_start = AB_StaffScheduleItem::WORKING_START_TIME;
34
+ $time_end = AB_StaffScheduleItem::WORKING_END_TIME;
35
+ }
36
+ if ( $options['type'] == 'from' ) {
37
+ $time_end -= $ts_length; // Exclude last slot.
38
+ } elseif ( $options['type'] == 'to' ) {
39
+ $time_end *= 2; // Create slots for 2 days.
40
+ }
41
 
42
  // Run the loop.
43
+ while ( $time_start <= $time_end ) {
44
+ $this->values[ AB_DateTimeUtils::buildTimeString( $time_start ) ] = AB_DateTimeUtils::formatTime( $time_start );
45
+ $time_start += $ts_length;
46
  }
 
47
  }
48
 
49
  /**
55
  *
56
  * @return string
57
  */
58
+ public function render( $name, $value = null, array $attributes = array() )
59
+ {
60
+ $options = '';
61
  $attributes_str = '';
62
+ $exist_slot = false;
63
  foreach ( $this->values as $option_value => $option_text ) {
64
+ if ( $exist_slot === false ) {
65
+ if ( $value == $option_value ) {
66
+ $exist_slot = true;
67
+ } elseif ( $value < $option_value ) {
68
+ $options .= sprintf(
69
+ '<option value="%s" selected="selected">%s</option>',
70
+ $value,
71
+ AB_DateTimeUtils::formatTime( $value )
72
+ );
73
+ $exist_slot = true;
74
+ }
75
+ }
76
+ $options .= sprintf(
77
  '<option value="%s"%s>%s</option>',
78
  $option_value,
79
+ selected( $value, $option_value, false ),
80
  $option_text
81
  );
82
  }
84
  $attributes_str .= sprintf( ' %s="%s"', $attr_name, $attr_value );
85
  }
86
 
87
+ return sprintf( '<select name="%s" data-default_value="%s"%s>%s</select>', $name, $value, $attributes_str, $options );
88
  }
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  }
backend/modules/staff/resources/css/staff.css CHANGED
@@ -1,27 +1,56 @@
1
  #ab-staff-schedule > table { width: 100% }
2
- #ab-staff-schedule .staff-schedule-item-row > td { padding: 10px 0; border-bottom: 1px solid #DDDDDD; }
3
  #ab-staff-schedule .staff-schedule-item-row.ab-last-row > td { border: 0; }
4
  #ab-staff-schedule .staff-schedule-item-row > td.first { width: 115px }
5
  #ab-staff-schedule .staff-schedule-item-row td.add-break { width: 80px }
6
- #ab-staff-schedule .staff-schedule-item-row td.working-intervals { width: 210px }
7
- #ab-staff-schedule .staff-schedule-item-row .break-interval-wrapper { margin-right: 7px; white-space: nowrap; float: left; }
8
- #ab-staff-schedule .staff-schedule-item-row .break-interval-wrapper img {
9
- border: 0 none;
10
- cursor: pointer;
11
- display: block;
12
- float: left;
13
- margin-left: 5px;
14
- margin-top: 2px;
15
- width: 12px;
16
- }
17
  #ab-staff-schedule .staff-schedule-item-row .breaks-list { width: 100% }
18
  #ab-staff-schedule .staff-schedule-item-row .breaks-list .breaks-list-label { width: 60px }
19
  #ab-staff-schedule .staff-schedule-item-row .ab-popup-wrapper .error { padding: 3px; margin: 5px; }
20
- #ab-staff-schedule .staff-schedule-item-row .breaks-list .ab-popup-wrapper { float: left; margin-right: 3px; }
 
 
 
 
21
  #ab-staff-schedule .error { margin: 0 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  #ab-staff-list { margin-left: 0; }
23
- #ab-edit-staff .ab-nav-tabs { height: 37px; }
24
- .ab-staff-form .control-label { text-align: left!important; }
25
- .working-end, .working-start { margin-bottom: 0; font-size: 13px; }
26
- .ab-content .break-end, .ab-content .break-start { width: auto; margin-bottom: 0; }
27
- .add-break form { margin: 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  #ab-staff-schedule > table { width: 100% }
2
+ #ab-staff-schedule .staff-schedule-item-row > td { padding: 10px 5px; border-bottom: 1px solid #DDDDDD; }
3
  #ab-staff-schedule .staff-schedule-item-row.ab-last-row > td { border: 0; }
4
  #ab-staff-schedule .staff-schedule-item-row > td.first { width: 115px }
5
  #ab-staff-schedule .staff-schedule-item-row td.add-break { width: 80px }
6
+ #ab-staff-schedule .staff-schedule-item-row td.working-intervals { white-space: nowrap; }
7
+ #ab-staff-schedule .staff-schedule-item-row .break-interval-wrapper { white-space: nowrap; }
8
+ #ab-staff-schedule .staff-schedule-item-row .break-interval-wrapper img { border: 0 none; cursor: pointer; display: inline-block; margin-left: 5px; margin-top: 2px; width: 12px; }
 
 
 
 
 
 
 
 
9
  #ab-staff-schedule .staff-schedule-item-row .breaks-list { width: 100% }
10
  #ab-staff-schedule .staff-schedule-item-row .breaks-list .breaks-list-label { width: 60px }
11
  #ab-staff-schedule .staff-schedule-item-row .ab-popup-wrapper .error { padding: 3px; margin: 5px; }
12
+ #ab-staff-schedule .staff-schedule-item-row .breaks-list .ab-popup-wrapper { margin-left: 5px; display: inline-block; }
13
+ #ab-staff-schedule .staff-schedule-item-row .working-start,
14
+ #ab-staff-schedule .staff-schedule-item-row .working-end { width: 90px!important; margin-bottom: 0; font-size: 13px; }
15
+ #ab-staff-schedule .staff-schedule-item-row .break-end,
16
+ #ab-staff-schedule .staff-schedule-item-row .break-start { width: auto; margin-bottom: 0; }
17
  #ab-staff-schedule .error { margin: 0 }
18
+
19
+ #ab-edit-staff .ab-staff-tab-content { position: relative; }
20
+ .ab-nav-head { overflow: hidden; position: relative; margin-bottom: 20px; }
21
+ .ab-nav-head h2 { margin: 0; padding-right: 20px; }
22
+ .tabbable .tab-content .tab-pane { padding-top: 15px; }
23
+
24
+ #ab-staff-services ul ul li { margin-left: 20px; }
25
+ #ab-staff-services ul ul li.ab-services-category { margin: 25px 0px 15px 20px; position: relative; }
26
+ #ab-staff-services ul .ab-title-service { position: absolute; right: 0px; top: 1px; }
27
+ #ab-staff-services ul .ab-title-service > div { display: inline-block; width: 60px; text-align: center; }
28
+ #ab-staff-services ul .ab-title-service > div:first-child { margin-right: 5px; }
29
+ #ab-staff-services ul ul li ul li { margin-left: 20px; overflow: hidden; }
30
+ #ab-staff-services ul ul li ul li .ab-price { width: 70px; margin: 0 0 0 5px; text-align: right; height: 30px!important; }
31
+ #ab-staff-services ul ul li ul li .ab-list-title { margin: 5px 5px 0 0; }
32
+ #ab-staff-services { max-width: 590px; }
33
+ #ab-staff-services #ajax-send-service { margin-left: 40px; }
34
+
35
  #ab-staff-list { margin-left: 0; }
36
+ #ab-staff-list .ab-staff-member .ab-avatar { max-width: 35px; max-height: 35px; margin-right: 10px; }
37
+ #ab-staff-list .ab-staff-member { padding: 5px; padding-left: 25px; margin: 3px 0; overflow: hidden; cursor: pointer; position: relative; border-radius: 5px; }
38
+ #ab-staff-list .ab-staff-member .ab-handle { position: absolute; left: 5px; top: 50%; cursor: move; margin-top: -10px; }
39
+ #ab-staff-list .ab-staff-member:hover,
40
+ #ab-staff-list .ab-staff-member.ab-active { background: #d6d6d6; }
41
+
42
+ #ab-staff-details-container .ab-staff-form .control-label { text-align: left!important; }
43
+ .ab-staff-form { max-width: 450px; }
44
+
45
+ .ab-calendar-tabs .ab-text-align,
46
+ .ab-left-bar #ab-staff-list .ab-staff-member .ab-text-align { height: 35px; display: table-cell; vertical-align: middle; }
47
+
48
+ /* media query */
49
+ @media screen and (max-width: 782px) {
50
+ .ab-nav-tabs li a {
51
+ padding: 5px 10px;
52
+ }
53
+ .ab-nav-head {
54
+ margin-top: 20px;
55
+ }
56
+ }
backend/modules/staff/resources/js/staff.js CHANGED
@@ -1,27 +1,33 @@
1
  jQuery(function($) {
2
 
3
  var $name_input = $('#ab-newstaff-fullname'),
 
4
  $list_item_number = $('#ab-list-item-number'),
5
  $wp_user_select = $('#ab-newstaff-wpuser'),
 
6
  $staff_member = $('.ab-staff-member'),
7
  $edit_form = $('#ab-edit-staff-member'),
8
  $new_form = $('#ab-new-satff');
9
 
 
 
 
 
10
  // Saves new staff form
11
- $('#ab-save-newstaff').bind('click', function () {
12
- $('#light_notice').modal('show');
13
  });
14
 
15
  // Save new staff on enter press
16
- $name_input.bind('keypress', function (e) {
17
  var code = (e.keyCode ? e.keyCode : e.which);
18
  if (code == 13) {
19
- $('#light_notice').modal('show');
20
  }
21
  });
22
 
23
  // Close new staff form on esc
24
- $new_form.bind('keypress', function (e) {
25
  var code = (e.keyCode ? e.keyCode : e.which);
26
  if (code == 27) {
27
  $('.ab-popup-wrapper').ab_popup('close');
@@ -35,51 +41,57 @@ jQuery(function($) {
35
 
36
  var staff_id = $(this).data('staff-id');
37
  var active_tab_id = $('ul.nav li.active a').attr('id');
 
38
  $.get(ajaxurl, { action: 'ab_edit_staff', id: staff_id }, function (response) {
39
  $edit_form.html(response);
40
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  // Deletes staff avatar
42
- $('#ab-delete-avatar').bind('click', function () {
43
  $.post(ajaxurl, { action: 'ab_delete_staff_avatar', id: staff_id }, function () {
44
  $('#ab-staff-avatar-image', $edit_form).remove();
45
  });
46
  });
47
 
48
- $('#ab-update-staff').bind('click', function (e) {
49
  if (!validateForm($edit_form)) {
50
  e.preventDefault(e);
51
  e.stopPropagation(e);
 
 
52
  }
53
  });
54
 
55
- $('#ab-staff-wpuser').bind('change', function () {
56
  if ($(this).val()) {
57
  $('#ab-staff-full-name').val($(this).find(':selected').text());
58
  $('#ab-staff-email').val($(this).find(':selected').data('email'));
59
  }
60
  });
61
 
62
- helpInit();
63
-
64
- var service_container = $('#ab-staff-services-container'),
65
- details_container = $('#ab-staff-details-container'),
66
- schedule_container = $('#ab-staff-schedule-container'),
67
- holidays_container = $('#ab-staff-holidays-container'),
68
- $schedule_form,
69
- services_form,
70
- tabs = $('.ab-list-link', $edit_form);
71
-
72
  // Opens services tab
73
- $('#ab-staff-services-tab').bind('click', function () {
74
  activateTab.call($(this));
75
  service_container.show();
76
 
77
  // Loads services form
78
  if (!service_container.children().length) {
 
79
  $.post(ajaxurl, { action: 'ab_staff_services', id: staff_id }, function (response) {
80
  service_container.html(response);
81
  services_form = $('form', service_container);
82
-
83
  var auto_tick_checkboxes = function() {
84
  // Handle 'select category' checkbox.
85
  $('.ab-services-category .ab-category-checkbox').each(function() {
@@ -95,170 +107,199 @@ jQuery(function($) {
95
  );
96
  };
97
 
98
- // Select all services related to choosen category
99
  $('.ab-category-checkbox', services_form).bind('click', function () {
100
- $('.ab-category-services .ab-category-' + $(this).data('category-id')).attr('checked', $(this).is(':checked')).change();
101
  auto_tick_checkboxes();
102
  });
103
 
104
- // Select all services
105
- $('#ab-all-services').bind('click', function () {
106
- $('.ab-service-checkbox', services_form).attr('checked', $(this).is(':checked')).change();
107
- $('.ab-category-checkbox').attr('checked', $(this).is(':checked'));
108
  });
109
 
110
  // Select service
111
- $('.ab-service-checkbox', services_form).bind('click', function () {
112
  var $this = $(this);
113
  var $price = $this.closest('li').find('.ab-price');
114
  $this.is(':checked') ? $price.removeAttr('disabled') : $price.attr('disabled', true);
115
  auto_tick_checkboxes();
116
  });
117
 
118
- $('.ab-service-checkbox',services_form).bind('change', function(){
119
- var $price = $('.ab-price[name="price['+$(this).val()+']"]');
120
- $(this).is(':checked') ? $price.removeAttr('disabled') : $price.attr('disabled', true);
121
  });
122
 
123
  // Saves services
124
- $('#ab-staff-services-update').bind('click', function () {
125
- $('.spinner', services_form).fadeIn('slow');
 
 
126
  $.post(ajaxurl, services_form.serialize(), function (response) {
127
- $('.spinner', services_form).fadeOut('slow');
 
 
 
128
  });
129
  });
130
 
 
 
 
 
 
131
  auto_tick_checkboxes();
 
 
 
 
1
  jQuery(function($) {
2
 
3
  var $name_input = $('#ab-newstaff-fullname'),
4
+ $email_input = $('#ab-newstaff-email'),
5
  $list_item_number = $('#ab-list-item-number'),
6
  $wp_user_select = $('#ab-newstaff-wpuser'),
7
+ $staff_list = $('#ab-staff-list'),
8
  $staff_member = $('.ab-staff-member'),
9
  $edit_form = $('#ab-edit-staff-member'),
10
  $new_form = $('#ab-new-satff');
11
 
12
+ function saveNewForm() {
13
+ $('#lite_notice').modal('show');
14
+ }
15
+
16
  // Saves new staff form
17
+ $('#ab-save-newstaff').on('click', function () {
18
+ saveNewForm();
19
  });
20
 
21
  // Save new staff on enter press
22
+ $name_input.on('keypress', function (e) {
23
  var code = (e.keyCode ? e.keyCode : e.which);
24
  if (code == 13) {
25
+ saveNewForm();
26
  }
27
  });
28
 
29
  // Close new staff form on esc
30
+ $new_form.on('keypress', function (e) {
31
  var code = (e.keyCode ? e.keyCode : e.which);
32
  if (code == 27) {
33
  $('.ab-popup-wrapper').ab_popup('close');
41
 
42
  var staff_id = $(this).data('staff-id');
43
  var active_tab_id = $('ul.nav li.active a').attr('id');
44
+ $edit_form.html('<div class="loading-indicator"><span class="ab-loader"></span></div>');
45
  $.get(ajaxurl, { action: 'ab_edit_staff', id: staff_id }, function (response) {
46
  $edit_form.html(response);
47
 
48
+ var service_container = $('#ab-staff-services-container'),
49
+ loading_indicator = $('.loading-indicator'),
50
+ details_container = $('#ab-staff-details-container'),
51
+ schedule_container = $('#ab-staff-schedule-container'),
52
+ holidays_container = $('#ab-staff-holidays-container'),
53
+ $schedule_form,
54
+ services_form;
55
+
56
+ initHelps();
57
+ initNotices();
58
+ Ladda.bind( 'button[type=submit]' );
59
+
60
  // Deletes staff avatar
61
+ $('#ab-delete-avatar').on('click', function () {
62
  $.post(ajaxurl, { action: 'ab_delete_staff_avatar', id: staff_id }, function () {
63
  $('#ab-staff-avatar-image', $edit_form).remove();
64
  });
65
  });
66
 
67
+ $('#ab-update-staff').on('click', function (e) {
68
  if (!validateForm($edit_form)) {
69
  e.preventDefault(e);
70
  e.stopPropagation(e);
71
+ } else {
72
+ $edit_form.phone = $("#ab-staff-phone").intlTelInput('getNumber');
73
  }
74
  });
75
 
76
+ $('#ab-staff-wpuser').on('change', function () {
77
  if ($(this).val()) {
78
  $('#ab-staff-full-name').val($(this).find(':selected').text());
79
  $('#ab-staff-email').val($(this).find(':selected').data('email'));
80
  }
81
  });
82
 
 
 
 
 
 
 
 
 
 
 
83
  // Opens services tab
84
+ $('#ab-staff-services-tab').on('click', function () {
85
  activateTab.call($(this));
86
  service_container.show();
87
 
88
  // Loads services form
89
  if (!service_container.children().length) {
90
+ loading_indicator.show();
91
  $.post(ajaxurl, { action: 'ab_staff_services', id: staff_id }, function (response) {
92
  service_container.html(response);
93
  services_form = $('form', service_container);
94
+
95
  var auto_tick_checkboxes = function() {
96
  // Handle 'select category' checkbox.
97
  $('.ab-services-category .ab-category-checkbox').each(function() {
107
  );
108
  };
109
 
110
+ // Select all services related to chosen category
111
  $('.ab-category-checkbox', services_form).bind('click', function () {
112
+ $('.ab-category-services .ab-category-' + $(this).data('category-id')).prop('checked', $(this).is(':checked')).change();
113
  auto_tick_checkboxes();
114
  });
115
 
116
+ // Check and uncheck all services
117
+ $('#ab-all-services').on('click', function () {
118
+ $('.ab-service-checkbox', services_form).prop('checked', $(this).is(':checked')).change();
119
+ $('.ab-category-checkbox').prop('checked', $(this).is(':checked'));
120
  });
121
 
122
  // Select service
123
+ $('.ab-service-checkbox', services_form).on('click', function () {
124
  var $this = $(this);
125
  var $price = $this.closest('li').find('.ab-price');
126
  $this.is(':checked') ? $price.removeAttr('disabled') : $price.attr('disabled', true);
127
  auto_tick_checkboxes();
128
  });
129
 
130
+ $('.ab-service-checkbox',services_form).on('change', function(){
131
+ var $input_fields = $('.ab-price[name="price['+$(this).val()+']"]').add('.ab-price[name="capacity['+$(this).val()+']"]');
132
+ $(this).is(':checked') ? $input_fields.removeAttr('disabled') : $input_fields.attr('disabled', true);
133
  });
134
 
135
  // Saves services
136
+ $('#ajax-send-service').on('click', function (e) {
137
+ e.preventDefault();
138
+ var ladda = Ladda.create(this);
139
+ ladda.start();
140
  $.post(ajaxurl, services_form.serialize(), function (response) {
141
+ ladda.stop();
142
+ if (response.success) {
143
+ showSuccessNotice();
144
+ }
145
  });
146
  });
147
 
148
+ // After reset auto tick group checkboxes.
149
+ $('button[type=reset]').on('click', function() {
150
+ setTimeout(auto_tick_checkboxes, 0);
151
+ });
152
+
153
  auto_tick_checkboxes();
154
+ loading_indicator.hide();
155
+
156
+ services_form.find('input[name^="capacity"]').on('change', function(){
157
+