WordPress Online Booking and Scheduling Plugin – Bookly - Version 17.4

Version Description

Download this release

Release Info

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

Code changes from version 17.3 to 17.4

Files changed (89) hide show
  1. backend/Backend.php +2 -5
  2. backend/components/dialogs/appointment/delete/templates/delete.php +2 -2
  3. backend/components/dialogs/appointment/edit/Dialog.php +2 -12
  4. backend/components/dialogs/appointment/edit/resources/js/ng-appointment.js +6 -3
  5. backend/components/dialogs/appointment/edit/templates/edit.php +1 -1
  6. backend/components/dialogs/customer/edit/Dialog.php +4 -12
  7. backend/components/dialogs/customer/edit/resources/js/ng-customer.js +9 -1
  8. backend/components/dialogs/sms/Ajax.php +1 -1
  9. backend/components/dialogs/staff/categories/proxy/Pro.php +15 -0
  10. backend/components/dialogs/staff/edit/Ajax.php +405 -0
  11. backend/components/dialogs/staff/edit/Dialog.php +91 -0
  12. backend/{modules/staff → components/dialogs/staff/edit}/forms/StaffServices.php +2 -2
  13. backend/components/dialogs/staff/edit/proxy/Pro.php +16 -0
  14. backend/components/dialogs/staff/edit/proxy/SpecialDays.php +16 -0
  15. backend/components/dialogs/staff/edit/resources/js/staff-days-off.js +62 -0
  16. backend/{modules/staff → components/dialogs/staff/edit}/resources/js/staff-details.js +95 -40
  17. backend/components/dialogs/staff/edit/resources/js/staff-edit-dialog.js +274 -0
  18. backend/components/dialogs/staff/edit/resources/js/staff-schedule.js +368 -0
  19. backend/components/dialogs/staff/edit/resources/js/staff-services.js +187 -0
  20. backend/{modules/staff → components/dialogs/staff/edit}/templates/_break.php +0 -0
  21. backend/{modules/staff → components/dialogs/staff/edit}/templates/_breaks.php +0 -0
  22. backend/{modules/staff/templates/_details.php → components/dialogs/staff/edit/templates/details.php} +45 -19
  23. backend/components/dialogs/staff/edit/templates/dialog.php +28 -0
  24. backend/components/dialogs/staff/edit/templates/dialog_body.php +53 -0
  25. backend/{modules/staff → components/dialogs/staff/edit}/templates/holidays.php +1 -1
  26. backend/{modules/staff → components/dialogs/staff/edit}/templates/schedule.php +1 -1
  27. backend/{modules/staff → components/dialogs/staff/edit}/templates/services.php +2 -2
  28. backend/modules/appearance/Page.php +1 -1
  29. backend/modules/appearance/resources/js/appearance.js +3 -3
  30. backend/modules/appointments/Ajax.php +104 -67
  31. backend/modules/appointments/Page.php +2 -25
  32. backend/modules/appointments/proxy/GroupBooking.php +18 -0
  33. backend/modules/appointments/proxy/Ratings.php +3 -2
  34. backend/modules/appointments/resources/js/appointments.js +54 -55
  35. backend/modules/appointments/templates/index.php +1 -3
  36. backend/modules/calendar/Page.php +2 -11
  37. backend/modules/calendar/resources/js/calendar-common.js +5 -5
  38. backend/modules/dashboard/Page.php +2 -22
  39. backend/modules/dashboard/resources/js/dashboard.js +19 -19
  40. backend/modules/payments/Page.php +11 -31
  41. backend/modules/payments/resources/js/payments.js +27 -27
  42. backend/modules/services/Ajax.php +4 -3
  43. backend/modules/services/templates/index.php +0 -1
  44. backend/modules/settings/Page.php +1 -1
  45. backend/modules/settings/resources/js/settings.js +2 -1
  46. backend/modules/sms/Page.php +3 -19
  47. backend/modules/sms/resources/js/sms.js +22 -22
  48. backend/modules/staff/Ajax.php +79 -401
  49. backend/modules/staff/Page.php +14 -66
  50. backend/modules/staff/forms/StaffMember.php +0 -62
  51. backend/modules/staff/forms/StaffMemberEdit.php +0 -28
  52. backend/modules/staff/forms/StaffScheduleItemBreak.php +0 -23
  53. backend/modules/staff/proxy/Pro.php +0 -2
  54. backend/modules/staff/proxy/Shared.php +1 -1
  55. backend/modules/staff/resources/js/staff-days-off.js +0 -52
  56. backend/modules/staff/resources/js/staff-list.js +253 -0
  57. backend/modules/staff/resources/js/staff-schedule.js +0 -357
  58. backend/modules/staff/resources/js/staff-services.js +0 -180
  59. backend/modules/staff/resources/js/staff.js +0 -486
  60. backend/modules/staff/templates/_list_item.php +0 -22
  61. backend/modules/staff/templates/_new.php +0 -44
  62. backend/modules/staff/templates/edit.php +0 -82
  63. backend/modules/staff/templates/index.php +55 -99
  64. backend/resources/bootstrap/css/bootstrap-theme.min.css +1 -1
  65. backend/resources/images/sprite.png +0 -0
  66. frontend/modules/booking/ShortCode.php +1 -1
  67. frontend/modules/booking/proxy/PaypalCheckout.php +15 -0
  68. frontend/resources/js/bookly.js +38 -35
  69. frontend/resources/js/bookly.min.js +1 -1
  70. frontend/resources/js/bookly.min.js.map +1 -1
  71. frontend/resources/js/src/details_step.js +1 -1
  72. frontend/resources/js/src/main.js +33 -30
  73. frontend/resources/js/src/repeat_step.js +2 -2
  74. frontend/resources/js/src/service_step.js +1 -1
  75. frontend/resources/js/src/time_step.js +1 -1
  76. languages/bookly.pot +139 -132
  77. lib/Installer.php +2 -1
  78. lib/Plugin.php +1 -0
  79. lib/Query.php +41 -2
  80. lib/UserBookingData.php +4 -3
  81. lib/base/Ajax.php +1 -1
  82. lib/entities/Staff.php +24 -0
  83. lib/slots/Finder.php +13 -2
  84. lib/slots/Generator.php +92 -83
  85. lib/slots/Range.php +16 -0
  86. lib/slots/Staff.php +0 -1
  87. lib/utils/DateTime.php +50 -0
  88. main.php +1 -1
  89. readme.txt +2 -2
backend/Backend.php CHANGED
@@ -71,11 +71,8 @@ abstract class Backend
71
$appearance = __( 'Appearance', 'bookly' );
72
$settings = __( 'Settings', 'bookly' );
73
74
- if ( $current_user->has_cap( 'manage_options' ) ) {
75
- add_submenu_page( 'bookly-menu', $dashboard, $dashboard, 'read',
76
- Modules\Dashboard\Page::pageSlug(), function () {
77
- Modules\Dashboard\Page::render(); } );
78
- }
79
add_submenu_page( 'bookly-menu', $calendar, $calendar, 'read',
80
Modules\Calendar\Page::pageSlug(), function () { Modules\Calendar\Page::render(); } );
81
if ( $current_user->has_cap( 'manage_options' ) || $current_user->has_cap( 'manage_bookly_appointments' ) ) {
71
$appearance = __( 'Appearance', 'bookly' );
72
$settings = __( 'Settings', 'bookly' );
73
74
+ add_submenu_page( 'bookly-menu', $dashboard, $dashboard, 'manage_options',
75
+ Modules\Dashboard\Page::pageSlug(), function () {Modules\Dashboard\Page::render();} );
76
add_submenu_page( 'bookly-menu', $calendar, $calendar, 'read',
77
Modules\Calendar\Page::pageSlug(), function () { Modules\Calendar\Page::render(); } );
78
if ( $current_user->has_cap( 'manage_options' ) || $current_user->has_cap( 'manage_bookly_appointments' ) ) {
backend/components/dialogs/appointment/delete/templates/delete.php CHANGED
@@ -21,8 +21,8 @@ use Bookly\Backend\Components\Controls\Buttons;
21
</div>
22
</div>
23
<div class="modal-footer">
24
- <?php Buttons::renderDelete(); ?>
25
- <?php Buttons::renderCustom( null, 'btn-default', __( 'Cancel', 'bookly' ), array( 'ng-click' => 'closeDialog()', 'data-dismiss' => 'modal' ) ) ?>
26
</div>
27
</div>
28
</div>
21
</div>
22
</div>
23
<div class="modal-footer">
24
+ <?php Buttons::renderCustom( 'bookly-delete', 'btn-danger', esc_html__( 'Delete', 'bookly' ), array(), '<i class="fa fa-fw fa-trash"></i> {caption}' ) ?>
25
+ <?php Buttons::renderCustom( null, 'btn-default', esc_html__( 'Cancel', 'bookly' ), array( 'ng-click' => 'closeDialog()', 'data-dismiss' => 'modal' ) ) ?>
26
</div>
27
</div>
28
</div>
backend/components/dialogs/appointment/edit/Dialog.php CHANGED
@@ -14,8 +14,6 @@ class Dialog extends Lib\Base\Component
14
*/
15
public static function render()
16
{
17
- global $wp_locale;
18
-
19
self::enqueueStyles( array(
20
'backend' => array( 'css/jquery-ui-theme/jquery-ui.min.css', 'css/select2.min.css', 'css/fontawesome-all.min.css' ),
21
'frontend' => array( 'css/ladda.min.css', ),
@@ -39,16 +37,8 @@ class Dialog extends Lib\Base\Component
39
) );
40
41
wp_localize_script( 'bookly-ng-appointment.js', 'BooklyL10nAppDialog', array(
42
- 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
43
- 'dateOptions' => array(
44
- 'dateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_JQUERY_DATEPICKER ),
45
- 'monthNamesShort' => array_values( $wp_locale->month_abbrev ),
46
- 'monthNames' => array_values( $wp_locale->month ),
47
- 'dayNamesMin' => array_values( $wp_locale->weekday_abbrev ),
48
- 'dayNamesShort' => array_values( $wp_locale->weekday_abbrev ),
49
- 'dayNames' => array_values( $wp_locale->weekday ),
50
- 'firstDay' => (int) get_option( 'start_of_week' ),
51
- ),
52
'cf_per_service' => (int) Lib\Config::customFieldsPerService(),
53
'no_result_found' => __( 'No result found', 'bookly' ),
54
'staff_any' => get_option( 'bookly_l10n_option_employee' ),
14
*/
15
public static function render()
16
{
17
self::enqueueStyles( array(
18
'backend' => array( 'css/jquery-ui-theme/jquery-ui.min.css', 'css/select2.min.css', 'css/fontawesome-all.min.css' ),
19
'frontend' => array( 'css/ladda.min.css', ),
37
) );
38
39
wp_localize_script( 'bookly-ng-appointment.js', 'BooklyL10nAppDialog', array(
40
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
41
+ 'datePicker' => Lib\Utils\DateTime::datePickerOptions(),
42
'cf_per_service' => (int) Lib\Config::customFieldsPerService(),
43
'no_result_found' => __( 'No result found', 'bookly' ),
44
'staff_any' => get_option( 'bookly_l10n_option_employee' ),
backend/components/dialogs/appointment/edit/resources/js/ng-appointment.js CHANGED
@@ -1158,7 +1158,7 @@
1158
month = m.format('M'),
1159
day = m.format('DD');
1160
1161
- return BooklyL10nAppDialog.dateOptions.dayNamesMin[weekday] + ', ' + BooklyL10nAppDialog.dateOptions.monthNamesShort[month-1] + ' ' + day;
1162
};
1163
$scope.schFormatTime = function(slots, options) {
1164
for (var i = 0; i < options.length; ++ i) {
@@ -1263,7 +1263,7 @@
1263
return item.deleted;
1264
});
1265
};
1266
- $scope.schDateOptions = jQuery.extend({}, BooklyL10nAppDialog.dateOptions, {dateFormat: 'D, M dd, yy'});
1267
$scope.schViewSeries = function ( customer ) {
1268
jQuery(document.body).trigger( 'recurring_appointments.series_dialog', [ customer.series_id, function (event) {
1269
// Switch to the event owner tab.
@@ -1274,7 +1274,7 @@
1274
/**
1275
* Datepicker options.
1276
*/
1277
- $scope.dateOptions = BooklyL10nAppDialog.dateOptions;
1278
});
1279
1280
/**
@@ -1347,6 +1347,9 @@
1347
* @param function callback
1348
*/
1349
var showAppointmentDialog = function (appointment_id, staff_id, start_date, callback) {
1350
var $dialog = jQuery('#bookly-appointment-dialog');
1351
var $scope = angular.element($dialog[0]).scope();
1352
$scope.$apply(function ($scope) {
1158
month = m.format('M'),
1159
day = m.format('DD');
1160
1161
+ return BooklyL10nAppDialog.datePicker.weekdayShort[weekday] + ', ' + BooklyL10nAppDialog.datePicker.monthNamesShort[month-1] + ' ' + day;
1162
};
1163
$scope.schFormatTime = function(slots, options) {
1164
for (var i = 0; i < options.length; ++ i) {
1263
return item.deleted;
1264
});
1265
};
1266
+ $scope.schDatePickerOptions = jQuery.extend({}, BooklyL10nAppDialog.datePicker, {dateFormat: 'D, M dd, yy'});
1267
$scope.schViewSeries = function ( customer ) {
1268
jQuery(document.body).trigger( 'recurring_appointments.series_dialog', [ customer.series_id, function (event) {
1269
// Switch to the event owner tab.
1274
/**
1275
* Datepicker options.
1276
*/
1277
+ $scope.datePickerOptions = BooklyL10nAppDialog.datePicker;
1278
});
1279
1280
/**
1347
* @param function callback
1348
*/
1349
var showAppointmentDialog = function (appointment_id, staff_id, start_date, callback) {
1350
+ if (jQuery.fn.tooltip.Constructor.VERSION.split('.')[0] === '4') {
1351
+ jQuery('#bookly-tbs .modal.fade').removeClass('fade');
1352
+ }
1353
var $dialog = jQuery('#bookly-appointment-dialog');
1354
var $scope = angular.element($dialog[0]).scope();
1355
$scope.$apply(function ($scope) {
backend/components/dialogs/appointment/edit/templates/edit.php CHANGED
@@ -56,7 +56,7 @@ use Bookly\Lib\Entities\CustomerAppointment;
56
<div class="col-sm-4">
57
<label for="bookly-date"><?php esc_html_e( 'Date', 'bookly' ) ?></label>
58
<input id="bookly-date" class="form-control" type=text
59
- ng-model=form.date ui-date="dateOptions" autocomplete="off"
60
ng-change=onDateChange()>
61
</div>
62
<div class="col-sm-8">
56
<div class="col-sm-4">
57
<label for="bookly-date"><?php esc_html_e( 'Date', 'bookly' ) ?></label>
58
<input id="bookly-date" class="form-control" type=text
59
+ ng-model=form.date ui-date="datePickerOptions" autocomplete="off"
60
ng-change=onDateChange()>
61
</div>
62
<div class="col-sm-8">
backend/components/dialogs/customer/edit/Dialog.php CHANGED
@@ -14,8 +14,6 @@ class Dialog extends Lib\Base\Component
14
*/
15
public static function render()
16
{
17
- global $wp_locale;
18
-
19
self::enqueueStyles( array(
20
'backend' => array( 'css/jquery-ui-theme/jquery-ui.min.css', 'css/select2.min.css', ),
21
'frontend' => get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
@@ -44,16 +42,10 @@ class Dialog extends Lib\Base\Component
44
'utils' => is_rtl() ? '' : plugins_url( 'intlTelInput.utils.js', Lib\Plugin::getDirectory() . '/frontend/resources/js/intlTelInput.utils.js' ),
45
'country' => get_option( 'bookly_cst_phone_default_country' ),
46
),
47
- 'dateOptions' => array(
48
- 'dateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_JQUERY_DATEPICKER ),
49
- 'monthNamesShort' => array_values( $wp_locale->month_abbrev ),
50
- 'monthNames' => array_values( $wp_locale->month ),
51
- 'dayNamesMin' => array_values( $wp_locale->weekday_abbrev ),
52
- 'dayNames' => array_values( $wp_locale->weekday ),
53
- 'firstDay' => (int) get_option( 'start_of_week' ),
54
- 'yearRange' => sprintf( '%s:%s', date_create()->modify( '-100 years' )->format( 'Y' ), date( 'Y' ) ),
55
- 'changeYear' => true,
56
- ),
57
'infoFields' => (array) Lib\Proxy\CustomerInformation::getFieldsWhichMayHaveData(),
58
'noResultFound' => __( 'No result found', 'bookly' ),
59
) );
14
*/
15
public static function render()
16
{
17
self::enqueueStyles( array(
18
'backend' => array( 'css/jquery-ui-theme/jquery-ui.min.css', 'css/select2.min.css', ),
19
'frontend' => get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
42
'utils' => is_rtl() ? '' : plugins_url( 'intlTelInput.utils.js', Lib\Plugin::getDirectory() . '/frontend/resources/js/intlTelInput.utils.js' ),
43
'country' => get_option( 'bookly_cst_phone_default_country' ),
44
),
45
+ 'datePicker' => Lib\Utils\DateTime::datePickerOptions( array(
46
+ 'yearRange' => sprintf( '%s:%s', date_create()->modify( '-100 years' )->format( 'Y' ), date( 'Y' ) ),
47
+ 'changeYear' => true,
48
+ ) ),
49
'infoFields' => (array) Lib\Proxy\CustomerInformation::getFieldsWhichMayHaveData(),
50
'noResultFound' => __( 'No result found', 'bookly' ),
51
) );
backend/components/dialogs/customer/edit/resources/js/ng-customer.js CHANGED
@@ -192,7 +192,15 @@
192
/**
193
* Datepicker options.
194
*/
195
- scope.dateOptions = BooklyL10nCustDialog.dateOptions;
196
197
/**
198
* Toggle checkbox info field.
192
/**
193
* Datepicker options.
194
*/
195
+ scope.datePickerOptions = jQuery.extend({
196
+ beforeShow: function (input, inst) {
197
+ jQuery(document).off('focusin.bs.modal');
198
+ },
199
+ onClose: function () {
200
+ jQuery(document).on('focusin.bs.modal');
201
+ },
202
+ },
203
+ BooklyL10nCustDialog.datePicker);
204
205
/**
206
* Toggle checkbox info field.
backend/components/dialogs/sms/Ajax.php CHANGED
@@ -42,7 +42,7 @@ class Ajax extends Lib\Base\Ajax
42
$notification->load( self::parameter( 'id' ) );
43
$data = $notification->getFields();
44
$data['settings'] = array_merge( Lib\DataHolders\Notification\Settings::getDefault(), json_decode( $data['settings'], true ) );
45
- if ( get_user_meta( get_current_user_id(), 'rich_editing', true ) !== 'false' ) {
46
$data['message'] = wpautop( $data['message'] );
47
}
48
42
$notification->load( self::parameter( 'id' ) );
43
$data = $notification->getFields();
44
$data['settings'] = array_merge( Lib\DataHolders\Notification\Settings::getDefault(), json_decode( $data['settings'], true ) );
45
+ if ( get_user_meta( get_current_user_id(), 'rich_editing', true ) !== 'false' && $notification->getGateway() != 'sms' ) {
46
$data['message'] = wpautop( $data['message'] );
47
}
48
backend/components/dialogs/staff/categories/proxy/Pro.php ADDED
@@ -0,0 +1,15 @@
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Staff\Categories\Proxy;
3
+
4
+ use Bookly\Lib as BooklyLib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Components\Dialogs\Staff\Categories\Proxy
9
+ *
10
+ * @method static void renderDialog()
11
+ * @method static void renderAdd()
12
+ */
13
+ abstract class Pro extends BooklyLib\Base\Proxy
14
+ {
15
+ }
backend/components/dialogs/staff/edit/Ajax.php ADDED
@@ -0,0 +1,405 @@
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Staff\Edit;
3
+
4
+ use Bookly\Backend\Modules\Staff\Forms\Widgets\TimeChoice;
5
+ use Bookly\Backend\Modules\Staff\Proxy as StaffProxy;
6
+ use Bookly\Lib;
7
+
8
+ /**
9
+ * Class Ajax
10
+ * @package Bookly\Backend\Components\Dialogs\Staff\Edit
11
+ */
12
+ class Ajax extends Lib\Base\Ajax
13
+ {
14
+ /** @var Lib\Entities\Staff */
15
+ protected static $staff;
16
+
17
+ /**
18
+ * @inheritdoc
19
+ */
20
+ protected static function permissions()
21
+ {
22
+ $permissions = get_option( 'bookly_gen_allow_staff_edit_profile' ) ? array( '_default' => 'user' ) : array();
23
+ if ( Lib\Config::staffCabinetActive() ) {
24
+ $permissions = array( '_default' => 'user' );
25
+ }
26
+
27
+ return $permissions;
28
+ }
29
+
30
+ /**
31
+ * Get data for staff
32
+ */
33
+ public static function getStaffData()
34
+ {
35
+ $data = StaffProxy\Shared::editStaff(
36
+ array( 'alert' => array( 'error' => array() ), 'tpl' => array() ),
37
+ self::$staff
38
+ );
39
+ $tpl_data = $data['tpl'];
40
+
41
+ $users_for_staff = Lib\Utils\Common::isCurrentUserAdmin() ? self::_getUsersForStaff( self::$staff->getId() ) : array();
42
+
43
+ $staff_fields = self::$staff->getFields();
44
+ $src = false;
45
+ if ( $staff_fields['attachment_id'] ) {
46
+ $src = wp_get_attachment_image_src( $staff_fields['attachment_id'], 'thumbnail' );
47
+ }
48
+ $staff_fields['avatar'] = $src ? $src[0] : null;
49
+
50
+ $response = array(
51
+ 'html' => array(
52
+ 'edit' => self::renderTemplate( 'dialog_body', array( 'staff' => self::$staff ), false ),
53
+ 'details' => self::renderTemplate( 'details', array( 'staff' => self::$staff, 'users_for_staff' => $users_for_staff, 'tpl_data' => $tpl_data ), false ),
54
+ ),
55
+ );
56
+ if ( self::$staff->getId() ) {
57
+ $response['holidays'] = self::$staff->getHolidays();
58
+ $response['html']['services'] = self::_getStaffServices( self::$staff->getId(), null );
59
+ $response['html']['schedule'] = self::_getStaffSchedule( self::$staff->getId(), null );
60
+ $response['html']['special_days'] = Proxy\SpecialDays::getStaffSpecialDaysHtml( '', self::$staff->getId() );
61
+ $response['html']['holidays'] = self::renderTemplate( 'holidays', array( 'holidays' => $response['holidays'] ), false );
62
+ $response['staff'] = $staff_fields;
63
+ $response['alert'] = $data['alert'];
64
+ }
65
+
66
+ wp_send_json_success( $response );
67
+ }
68
+
69
+ /**
70
+ * Update staff from POST request.
71
+ */
72
+ public static function updateStaff()
73
+ {
74
+ if ( ! Lib\Utils\Common::isCurrentUserAdmin() ) {
75
+ // Check permissions to prevent one staff member from updating profile of another staff member.
76
+ do {
77
+ if ( self::parameter( 'staff_cabinet' ) && Lib\Config::staffCabinetActive() ) {
78
+ $allow = true;
79
+ } else {
80
+ $allow = get_option( 'bookly_gen_allow_staff_edit_profile' );
81
+ }
82
+ if ( $allow ) {
83
+ unset ( $_POST['wp_user_id'] );
84
+ break;
85
+ }
86
+ do_action( 'admin_page_access_denied' );
87
+ wp_die( 'Bookly: ' . __( 'You do not have sufficient permissions to access this page.' ) );
88
+ } while ( 0 );
89
+ } elseif ( self::parameter( 'id' ) == 0
90
+ && ! Lib\Config::proActive()
91
+ && Lib\Entities\Staff::query()->count() > 0
92
+ ) {
93
+ do_action( 'admin_page_access_denied' );
94
+ wp_die( 'Bookly: ' . __( 'You do not have sufficient permissions to access this page.' ) );
95
+ }
96
+
97
+ $params = self::postParameters();
98
+ if ( ! $params['category_id'] ) {
99
+ $params['category_id'] = null;
100
+ }
101
+ if ( ! $params['working_time_limit'] ) {
102
+ $params['working_time_limit'] = null;
103
+ }
104
+
105
+ self::$staff->setFields( $params );
106
+
107
+ StaffProxy\Shared::preUpdateStaff( self::$staff, $params );
108
+ self::$staff->save();
109
+ StaffProxy\Shared::updateStaff( self::$staff, $params );
110
+
111
+ wp_send_json_success();
112
+ }
113
+
114
+ /**
115
+ * Get staff services
116
+ */
117
+ public static function getStaffServices()
118
+ {
119
+ $form = new Forms\StaffServices();
120
+ $staff_id = self::parameter( 'staff_id' );
121
+ $location_id = self::parameter( 'location_id' );
122
+
123
+ $form->load( $staff_id, $location_id );
124
+
125
+ $html = self::_getStaffServices( $staff_id, $location_id );
126
+ wp_send_json_success( compact( 'html' ) );
127
+ }
128
+
129
+ /**
130
+ * Update staff services.
131
+ */
132
+ public static function staffServicesUpdate()
133
+ {
134
+ $form = new Forms\StaffServices();
135
+ $form->bind( self::postParameters() );
136
+ $form->save();
137
+
138
+ StaffProxy\Shared::updateStaffServices( self::postParameters() );
139
+
140
+ wp_send_json_success();
141
+ }
142
+
143
+ /**
144
+ * Get staff schedule.
145
+ */
146
+ public static function getStaffSchedule()
147
+ {
148
+ $staff_id = self::parameter( 'staff_id' );
149
+ $location_id = self::parameter( 'location_id' );
150
+ $html = self::_getStaffSchedule( $staff_id, $location_id );
151
+ wp_send_json_success( compact( 'html' ) );
152
+ }
153
+
154
+ /**
155
+ * Update staff holidays.
156
+ */
157
+ public static function staffHolidaysUpdate()
158
+ {
159
+ $id = self::parameter( 'id' );
160
+ $holiday = self::parameter( 'holiday' ) === 'true';
161
+ $repeat = self::parameter( 'repeat' ) === 'true';
162
+ $day = self::parameter( 'day', false );
163
+ if ( self::$staff ) {
164
+ // Update or delete the event.
165
+ if ( $id ) {
166
+ if ( $holiday ) {
167
+ Lib\Entities\Holiday::query()
168
+ ->update()
169
+ ->set( 'repeat_event', (int) $repeat )
170
+ ->where( 'id', $id )
171
+ ->execute();
172
+ } else {
173
+ Lib\Entities\Holiday::query()
174
+ ->delete()
175
+ ->where( 'id', $id )
176
+ ->execute();
177
+ }
178
+ } elseif ( $holiday && $day ) {
179
+ // Add the new event.
180
+ $holiday = new Lib\Entities\Holiday();
181
+ $holiday
182
+ ->setDate( $day )
183
+ ->setRepeatEvent( (int) $repeat )
184
+ ->setStaffId( self::$staff->getId() )
185
+ ->save();
186
+ }
187
+ // And return refreshed events.
188
+ echo json_encode( self::$staff->getHolidays() );
189
+ }
190
+ exit;
191
+ }
192
+
193
+ /**
194
+ * Handle staff schedule break.
195
+ */
196
+ public static function staffScheduleHandleBreak()
197
+ {
198
+ $start_time = self::parameter( 'start_time' );
199
+ $end_time = self::parameter( 'end_time' );
200
+ $working_start = self::parameter( 'working_start' );
201
+ $working_end = self::parameter( 'working_end' );
202
+
203
+ if ( Lib\Utils\DateTime::timeToSeconds( $start_time ) >= Lib\Utils\DateTime::timeToSeconds( $end_time ) ) {
204
+ wp_send_json_error( array( 'message' => __( 'The start time must be less than the end one', 'bookly' ), ) );
205
+ }
206
+
207
+ $res_schedule = new Lib\Entities\StaffScheduleItem();
208
+ $res_schedule->load( self::parameter( 'staff_schedule_item_id' ) );
209
+
210
+ $break_id = self::parameter( 'break_id', 0 );
211
+
212
+ $in_working_time = $working_start <= $start_time && $start_time <= $working_end
213
+ && $working_start <= $end_time && $end_time <= $working_end;
214
+ if ( ! $in_working_time || ! $res_schedule->isBreakIntervalAvailable( $start_time, $end_time, $break_id ) ) {
215
+ wp_send_json_error( array( 'message' => __( 'The requested interval is not available', 'bookly' ), ) );
216
+ }
217
+
218
+ $formatted_start = Lib\Utils\DateTime::formatTime( Lib\Utils\DateTime::timeToSeconds( $start_time ) );
219
+ $formatted_end = Lib\Utils\DateTime::formatTime( Lib\Utils\DateTime::timeToSeconds( $end_time ) );
220
+ $formatted_interval = $formatted_start . ' - ' . $formatted_end;
221
+
222
+ if ( $break_id ) {
223
+ $break = new Lib\Entities\ScheduleItemBreak();
224
+ $break->load( $break_id );
225
+ $break->setStartTime( $start_time )
226
+ ->setEndTime( $end_time )
227
+ ->save();
228
+
229
+ wp_send_json_success( array( 'interval' => $formatted_interval, ) );
230
+ } else {
231
+ $res_schedule_break = new Lib\Entities\ScheduleItemBreak();
232
+ $res_schedule_break->setFields( self::postParameters() );
233
+ $res_schedule_break->save();
234
+ if ( $res_schedule_break ) {
235
+ $breakStart = new TimeChoice( array( 'use_empty' => false, 'type' => 'break_from' ) );
236
+ $breakEnd = new TimeChoice( array( 'use_empty' => false, 'type' => 'to' ) );
237
+ wp_send_json( array(
238
+ 'success' => true,
239
+ 'item_content' => self::renderTemplate( '_break', array(
240
+ 'staff_schedule_item_break_id' => $res_schedule_break->getId(),
241
+ 'formatted_interval' => $formatted_interval,
242
+ 'break_start_choices' => $breakStart->render( '', $start_time, array( 'class' => 'break-start form-control' ) ),
243
+ 'break_end_choices' => $breakEnd->render( '', $end_time, array( 'class' => 'break-end form-control' ) ),
244
+ ), false ),
245
+ ) );
246
+ } else {
247
+ wp_send_json_error( array( 'message' => __( 'Error adding the break interval', 'bookly' ), ) );
248
+ }
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Delete staff schedule break.
254
+ */
255
+ public static function deleteStaffScheduleBreak()
256
+ {
257
+ $break = new Lib\Entities\ScheduleItemBreak();
258
+ $break->setId( self::parameter( 'id', 0 ) );
259
+ $break->delete();
260
+
261
+ wp_send_json_success();
262
+ }
263
+
264
+ /**
265
+ * Get list of users available for particular staff.
266
+ *
267
+ * @param integer $staff_id If null then it means new staff
268
+ * @return array
269
+ */
270
+ private static function _getUsersForStaff( $staff_id = null )
271
+ {
272
+ /** @var \wpdb $wpdb */
273
+ global $wpdb;
274
+ if ( ! is_multisite() ) {
275
+ $query = sprintf(
276
+ 'SELECT ID, user_email, display_name FROM ' . $wpdb->users . '
277
+ WHERE ID NOT IN(SELECT DISTINCT IFNULL( wp_user_id, 0 ) FROM ' . Lib\Entities\Staff::getTableName() . ' %s)
278
+ ORDER BY display_name',
279
+ $staff_id !== null
280
+ ? 'WHERE ' . Lib\Entities\Staff::getTableName() . '.id <> ' . (int) $staff_id
281
+ : ''
282
+ );
283
+ $users = $wpdb->get_results( $query );
284
+ } else {
285
+ // In Multisite show users only for current blog.
286
+ $query = Lib\Entities\Staff::query( 's' )->select( 'DISTINCT wp_user_id' )->whereNot( 'wp_user_id', null );
287
+ if ( $staff_id != null ) {
288
+ $query->whereNot( 'id', $staff_id );
289
+ }
290
+ $exclude_wp_users = array();
291
+ foreach ( $query->fetchArray() as $staff ) {
292
+ $exclude_wp_users[] = $staff['wp_user_id'];
293
+ }
294
+ $users = array_map(
295
+ function ( \WP_User $wp_user ) {
296
+ $obj = new \stdClass();
297
+ $obj->ID = $wp_user->ID;
298
+ $obj->user_email = $wp_user->data->user_email;
299
+ $obj->display_name = $wp_user->data->display_name;
300
+
301
+ return $obj;
302
+ },
303
+ get_users( array( 'blog_id' => get_current_blog_id(), 'orderby' => 'display_name', 'exclude' => $exclude_wp_users ) )
304
+ );
305
+ }
306
+
307
+ return $users;
308
+ }
309
+
310
+ /**
311
+ * @param int $staff_id
312
+ * @param int|null $location_id
313
+ * @return string
314
+ */
315
+ private static function _getStaffServices( $staff_id, $location_id )
316
+ {
317
+ $form = new Forms\StaffServices();
318
+ $form->load( $staff_id, $location_id );
319
+ $services_data = $form->getServicesData();
320
+
321
+ return self::renderTemplate( 'services', compact( 'form', 'services_data', 'staff_id', 'location_id' ), false );
322
+ }
323
+
324
+ /**
325
+ * Get staff schedule.
326
+ *
327
+ * @param int $staff_id
328
+ * @param int|null $location_id
329
+ * @return string|void
330
+ */
331
+ private static function _getStaffSchedule( $staff_id, $location_id )
332
+ {
333
+ $staff = new Lib\Entities\Staff();
334
+ $staff->load( $staff_id );
335
+ $schedule_items = $staff->getScheduleItems( $location_id );
336
+ return self::renderTemplate( 'schedule', compact( 'schedule_items', 'staff_id', 'location_id' ), false );
337
+ }
338
+
339
+ /**
340
+ * Extend parent method to control access on staff member level.
341
+ *
342
+ * @param string $action
343
+ * @return bool
344
+ */
345
+ protected static function hasAccess( $action )
346
+ {
347
+ if ( parent::hasAccess( $action ) ) {
348
+ self::$staff = new Lib\Entities\Staff();
349
+ if ( ! Lib\Utils\Common::isCurrentUserAdmin() ) {
350
+ self::$staff->loadBy( array( 'wp_user_id' => get_current_user_id() ) );
351
+ switch ( $action ) {
352
+ case 'getStaffData':
353
+ case 'updateStaff':
354
+ return self::$staff->isLoaded();
355
+ case 'getStaffSchedule':
356
+ case 'getStaffServices':
357
+ case 'staffHolidaysUpdate':
358
+ case 'staffServicesUpdate':
359
+ return self::$staff->isLoaded()
360
+ && ( self::$staff->getId() == self::parameter( 'staff_id' ) );
361
+ case 'staffScheduleHandleBreak':
362
+ $res_schedule = new Lib\Entities\StaffScheduleItem();
363
+ $res_schedule->load( self::parameter( 'staff_schedule_item_id' ) );
364
+ return self::$staff->isLoaded()
365
+ && ( self::$staff->getId() == $res_schedule->getStaffId() );
366
+ break;
367
+ case 'deleteStaffScheduleBreak':
368
+ $break = new Lib\Entities\ScheduleItemBreak();
369
+ $break->load( self::parameter( 'id' ) );
370
+ $res_schedule = new Lib\Entities\StaffScheduleItem();
371
+ $res_schedule->load( $break->getStaffScheduleItemId() );
372
+ return self::$staff->isLoaded()
373
+ && ( self::$staff->getId() == $res_schedule->getStaffId() );
374
+ break;
375
+ case 'staffScheduleUpdate':
376
+ if ( self::hasParameter( 'days' ) ) {
377
+ foreach ( self::parameter( 'days' ) as $id => $day_index ) {
378
+ $res_schedule = new Lib\Entities\StaffScheduleItem();
379
+ $res_schedule->load( $id );
380
+ $staff = new Lib\Entities\Staff();
381
+ $staff->load( $res_schedule->getStaffId() );
382
+ if ( $staff->getWpUserId() != get_current_user_id() ) {
383
+ return false;
384
+ }
385
+ }
386
+ }
387
+ return true;
388
+ break;
389
+ default:
390
+ return false;
391
+ }
392
+ } else {
393
+ if ( in_array( $action, array( 'getStaffData', 'updateStaff' ) ) ) {
394
+ self::$staff->load( self::parameter( 'id' ) );
395
+ } elseif ( in_array( $action, array( 'staffHolidaysUpdate' ) ) ) {
396
+ self::$staff->load( self::parameter( 'staff_id' ) );
397
+ }
398
+ }
399
+
400
+ return true;
401
+ }
402
+
403
+ return false;
404
+ }
405
+ }
backend/components/dialogs/staff/edit/Dialog.php ADDED
@@ -0,0 +1,91 @@
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Staff\Edit;
3
+
4
+ use Bookly\Backend\Components\Notices\Limitation;
5
+ use Bookly\Backend\Components\Dialogs\Staff\Edit\Proxy;
6
+ use Bookly\Lib;
7
+
8
+ /**
9
+ * Class Dialog
10
+ * @package Bookly\Backend\Components\Dialogs\Staff\Edit
11
+ */
12
+ class Dialog extends Lib\Base\Component
13
+ {
14
+ /**
15
+ * Render create service dialog.
16
+ */
17
+ public static function render()
18
+ {
19
+ /** @var \WP_Locale $wp_locale */
20
+ global $wp_locale;
21
+
22
+ wp_enqueue_media();
23
+
24
+ self::enqueueStyles( array(
25
+ 'frontend' => array_merge(
26
+ array( 'css/ladda.min.css', ),
27
+ get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
28
+ ? array()
29
+ : array( 'css/intlTelInput.css' )
30
+ ),
31
+ 'backend' => array( 'css/fontawesome-all.min.css', 'css/select2.min.css' ),
32
+ ) );
33
+
34
+ self::enqueueScripts( array(
35
+ 'frontend' => array_merge(
36
+ array(
37
+ 'js/spin.min.js' => array( 'jquery' ),
38
+ 'js/ladda.min.js' => array( 'jquery' ),
39
+ ),
40
+ get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
41
+ ? array()
42
+ : array( 'js/intlTelInput.min.js' => array( 'jquery' ) )
43
+ ),
44
+ 'backend' => array(
45
+ 'js/dropdown.js' => array( 'jquery' ),
46
+ 'js/range_tools.js' => array( 'jquery' ),
47
+ 'js/moment.min.js',
48
+ 'js/select2.full.min.js' => array( 'jquery' ),
49
+ ),
50
+ 'module' => array(
51
+ 'js/staff-details.js' => array( 'jquery' ),
52
+ 'js/staff-services.js' => array( 'bookly-staff-details.js' ),
53
+ 'js/staff-schedule.js' => array( 'bookly-staff-services.js' ),
54
+ 'js/staff-days-off.js' => array( 'bookly-staff-schedule.js' ),
55
+ 'js/staff-edit-dialog.js' => array( 'jquery-ui-sortable', 'jquery-ui-datepicker', 'bookly-range_tools.js', 'bookly-staff-days-off.js' )
56
+ ),
57
+ ) );
58
+
59
+ wp_localize_script( 'bookly-staff-edit-dialog.js', 'BooklyStaffEditDialogL10n', array(
60
+ 'csrfToken' => Lib\Utils\Common::getCsrfToken(),
61
+ 'intlTelInput' => array(
62
+ 'enabled' => get_option( 'bookly_cst_phone_default_country' ) != 'disabled',
63
+ 'utils' => is_rtl() ? '' : plugins_url( 'intlTelInput.utils.js', Lib\Plugin::getDirectory() . '/frontend/resources/js/intlTelInput.utils.js' ),
64
+ 'country' => get_option( 'bookly_cst_phone_default_country' ),
65
+ ),
66
+ 'holidays' => array(
67
+ 'loading_img' => plugins_url( 'bookly-responsive-appointment-booking-tool/backend/resources/images/loading.gif' ),
68
+ 'start_of_week' => (int) get_option( 'start_of_week' ),
69
+ 'days' => array_values( $wp_locale->weekday_abbrev ),
70
+ 'months' => array_values( $wp_locale->month ),
71
+ 'close' => __( 'Close', 'bookly' ),
72
+ 'repeat' => __( 'Repeat every year', 'bookly' ),
73
+ 'we_are_not_working' => __( 'We are not working on this day', 'bookly' ),
74
+ ),
75
+ 'services' => array(
76
+ 'capacity_error' => __( 'Min capacity should not be greater than max capacity.', 'bookly' ),
77
+ ),
78
+ 'createStaff' => __( 'Create staff', 'bookly' ),
79
+ 'editStaff' => __( 'Edit staff', 'bookly' ),
80
+ 'areYouSure' => __( 'Are you sure?', 'bookly' ),
81
+ 'settingsSaved' => __( 'Settings saved.', 'bookly' ),
82
+ 'proRequired' => (int) ! Lib\Config::proActive(),
83
+ 'limitation' => Limitation::getHtml(),
84
+ 'activeStaffId' => self::parameter( 'staff_id', 0 )
85
+ ) );
86
+
87
+ self::renderTemplate( 'dialog' );
88
+
89
+ Proxy\Pro::renderArchivingComponents();
90
+ }
91
+ }
backend/{modules/staff → components/dialogs/staff/edit}/forms/StaffServices.php RENAMED
@@ -1,11 +1,11 @@
1
<?php
2
- namespace Bookly\Backend\Modules\Staff\Forms;
3
4
use Bookly\Lib;
5
6
/**
7
* Class StaffServices
8
- * @package Bookly\Backend\Modules\Staff\Forms
9
*/
10
class StaffServices extends Lib\Base\Form
11
{
1
<?php
2
+ namespace Bookly\Backend\Components\Dialogs\Staff\Edit\Forms;
3
4
use Bookly\Lib;
5
6
/**
7
* Class StaffServices
8
+ * @package Bookly\Backend\Components\Dialogs\Staff\Edit\Forms
9
*/
10
class StaffServices extends Lib\Base\Form
11
{
backend/components/dialogs/staff/edit/proxy/Pro.php ADDED
@@ -0,0 +1,16 @@
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Staff\Edit\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ *
9
+ * @package Bookly\Backend\Components\Dialogs\Staff\Edit\Proxy
10
+ *
11
+ * @method static string renderArchivingComponents()
12
+ */
13
+ abstract class Pro extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/components/dialogs/staff/edit/proxy/SpecialDays.php ADDED
@@ -0,0 +1,16 @@
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Staff\Edit\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class SpecialDays
8
+ *
9
+ * @package Bookly\Backend\Components\Dialogs\Staff\Edit\Proxy
10
+ *
11
+ * @method static string getStaffSpecialDaysHtml( string $default, int $staff_id )
12
+ */
13
+ abstract class SpecialDays extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/components/dialogs/staff/edit/resources/js/staff-days-off.js ADDED
@@ -0,0 +1,62 @@
1
+ jQuery(function ($) {
2
+
3
+ var DaysOff = function($container, options) {
4
+ var obj = this;
5
+ jQuery.extend(obj.options, options);
6
+
7
+ if (!$container.children().length) {
8
+ $container.html('<div class="bookly-loading"></div>');
9
+ $.ajax({
10
+ url: ajaxurl,
11
+ data: {action: 'bookly_staff_holidays', id: obj.options.staff_id, csrf_token: obj.options.csrf_token},
12
+ xhrFields: {withCredentials: true},
13
+ crossDomain: 'withCredentials' in new XMLHttpRequest(),
14
+ success: function (response) {
15
+ $container.html(response.data.html);
16
+ init($container, obj);
17
+ }
18
+ });
19
+ } else {
20
+ init($container, obj);
21
+ }
22
+ };
23
+
24
+ function init($container, obj) {
25
+ if ($container.data('init') != true) {
26
+ var d = new Date();
27
+ $('.bookly-js-holidays').jCal({
28
+ day: new Date(d.getFullYear(), 0, 1),
29
+ days: 1,
30
+ showMonths: 12,
31
+ scrollSpeed: 350,
32
+ action: 'bookly_staff_holidays_update',
33
+ csrf_token: obj.options.csrf_token,
34
+ staff_id: obj.options.staff_id,
35
+ events: obj.options.l10n.holidays,
36
+ dayOffset: parseInt(obj.options.l10n.firstDay),
37
+ loadingImg: obj.options.l10n.loading_img,
38
+ dow: obj.options.l10n.days,
39
+ ml: obj.options.l10n.months,
40
+ we_are_not_working: obj.options.l10n.we_are_not_working,
41
+ repeat: obj.options.l10n.repeat,
42
+ close: obj.options.l10n.close
43
+ });
44
+
45
+ $('.bookly-js-jCalBtn', $container).on('click', function (e) {
46
+ e.preventDefault();
47
+ var trigger = $(this).data('trigger');
48
+ $('.bookly-js-holidays', $container).find($(trigger)).trigger('click');
49
+ });
50
+
51
+ $container.data('init', true);
52
+ }
53
+ }
54
+
55
+ DaysOff.prototype.options = {
56
+ staff_id : -1,
57
+ csrf_token: '',
58
+ l10n: {}
59
+ };
60
+
61
+ window.BooklyStaffDaysOff = DaysOff;
62
+ });
backend/{modules/staff → components/dialogs/staff/edit}/resources/js/staff-details.js RENAMED
@@ -60,23 +60,98 @@ jQuery(function ($) {
60
61
$staffLocations.booklyDropdown();
62
63
- $('button:reset', $container).on('click', function () {
64
- setTimeout(function () {
65
- $staffLocations.booklyDropdown('reset');
66
- }, 0);
67
- });
68
69
- $container.on('change', 'select,input,textarea', function () {
70
- has_changes = true;
71
- });
72
- $container.on('click', '.bookly-js-google-calendar-row a', function (e) {
73
- var url = $(this).attr('href');
74
- if (has_changes) {
75
e.preventDefault();
76
- $unsaved_changes.modal('show');
77
- $unsaved_changes.data('url', url);
78
- }
79
- });
80
$unsaved_changes_save.on('click', function () {
81
var ladda = Ladda.create(this);
82
ladda.start();
@@ -84,7 +159,7 @@ jQuery(function ($) {
84
if (response.success) {
85
window.location.href = $unsaved_changes.data('url');
86
} else {
87
- obj.options.booklyAlert({error: [response.data.error]});
88
}
89
ladda.stop();
90
});
@@ -93,34 +168,12 @@ jQuery(function ($) {
93
window.location.href = $unsaved_changes.data('url');
94
});
95
96
- // Save staff member details.
97
- $('#bookly-details-save', $container).on('click', function (e) {
98
- e.preventDefault();
99
- var ladda = Ladda.create(this);
100
- ladda.start();
101
- // for BooklyPro listener in archive.js
102
- // When button disabled, listeners don't process
103
- $(this).removeAttr('disabled');
104
- save(function (response) {
105
- if (response.success) {
106
- obj.options.booklyAlert({success: [obj.options.l10n.saved]});
107
- $('[bookly-js-staff-name-' + obj.options.get_details.id + ']').text($('#bookly-full-name', $form).val());
108
- if (typeof obj.options.renderWpUsers === 'function') {
109
- obj.options.renderWpUsers(response.data.wp_users);
110
- }
111
- } else {
112
- obj.options.booklyAlert({error: [response.data.error]});
113
- }
114
- ladda.stop();
115
- });
116
- });
117
-
118
function save(callback) {
119
var data = $form.serializeArray(),
120
$staff_phone = $('#bookly-phone', $form),
121
phone;
122
try {
123
- phone = BooklyL10n.intlTelInput.enabled ? $staff_phone.intlTelInput('getNumber') : $staff_phone.val();
124
if (phone == '') {
125
phone = $staff_phone.val();
126
}
@@ -155,7 +208,9 @@ jQuery(function ($) {
155
csrf_token: ''
156
},
157
l10n : {},
158
- booklyAlert : window.booklyAlert
159
};
160
161
window.BooklyStaffDetails = Details;
60
61
$staffLocations.booklyDropdown();
62
63
+ $container
64
+ .on('change', 'select,input,textarea', function () {
65
+ has_changes = true;
66
+ })
67
+ .on('click', '.bookly-js-google-calendar-row a', function (e) {
68
+ var url = $(this).attr('href');
69
+ if (has_changes) {
70
+ e.preventDefault();
71
+ $unsaved_changes.modal('show');
72
+ $unsaved_changes.data('url', url);
73
+ }
74
+ })
75
+ .on('click', '.bookly-js-outlook-calendar-row a', function (e) {
76
+ var url = $(this).attr('href');
77
+ if (has_changes) {
78
+ e.preventDefault();
79
+ $unsaved_changes.modal('show');
80
+ $unsaved_changes.data('url', url);
81
+ }
82
+ })
83
+ .on('change', '[name=google_disconnect]', function () {
84
+ has_changes = true;
85
+ $('.bookly-js-google-calendars-list', $container).toggle(!this.checked);
86
+ }).on('change', '[name=outlook_disconnect]', function () {
87
+ has_changes = true;
88
+ $('.bookly-js-outlook-calendars-list', $container).toggle(!this.checked);
89
+ })
90
+ .on('click', '.bookly-pretty-indicator', function (e) {
91
+ e.preventDefault();
92
+ e.stopPropagation();
93
+ var frame = wp.media({
94
+ library: {type: 'image'},
95
+ multiple: false
96
+ });
97
+ frame
98
+ .on('select', function () {
99
+ var selection = frame.state().get('selection').toJSON(),
100
+ img_src;
101
+ if (selection.length) {
102
+ if (selection[0].sizes['thumbnail'] !== undefined) {
103
+ img_src = selection[0].sizes['thumbnail'].url;
104
+ } else {
105
+ img_src = selection[0].url;
106
+ }
107
+ $container.find('[name=attachment_id]').val(selection[0].id).trigger('change');
108
+ $('#bookly-js-staff-avatar').find('.bookly-js-image').css({'background-image': 'url(' + img_src + ')', 'background-size': 'cover'});
109
+ $('.bookly-thumb-delete').show();
110
+ $(this).hide();
111
+ has_changes = true;
112
+ }
113
+ })
114
+ .on('close', function(){
115
+ $('body').addClass('modal-open');
116
+ });
117
118
+ frame.open();
119
+ })
120
+ // Delete staff avatar
121
+ .on('click', '.bookly-thumb-delete', function () {
122
+ var $thumb = $(this).parents('.bookly-js-image');
123
+ $thumb.attr('style', '');
124
+ $container.find('[name=attachment_id]').val('').trigger('change');
125
+ has_changes = true;
126
+ })
127
+ // Save staff member details.
128
+ .on('click', '#bookly-details-save', function (e) {
129
e.preventDefault();
130
+ var ladda = Ladda.create(this);
131
+ ladda.start();
132
+ // for BooklyPro listener in archive.js
133
+ // When button disabled, listeners don't process
134
+ $(this).removeAttr('disabled');
135
+
136
+ save(function (response) {
137
+ if (response.success) {
138
+ obj.options.savingStatus({success: [obj.options.l10n.saved]});
139
+ $('[bookly-js-staff-name-' + obj.options.get_details.id + ']').text($('#bookly-full-name', $form).val());
140
+ if (typeof obj.options.renderWpUsers === 'function') {
141
+ obj.options.renderWpUsers(response.data.wp_users);
142
+ }
143
+ } else {
144
+ obj.options.savingStatus({error: [response.data.error]});
145
+ }
146
+ ladda.stop();
147
+ });
148
+ })
149
+ .on('click', 'button:reset', function () {
150
+ setTimeout(function () {
151
+ $staffLocations.booklyDropdown('reset');
152
+ }, 0);
153
+ });
154
+
155
$unsaved_changes_save.on('click', function () {
156
var ladda = Ladda.create(this);
157
ladda.start();
159
if (response.success) {
160
window.location.href = $unsaved_changes.data('url');
161
} else {
162
+ obj.options.savingStatus({error: [response.data.error]});
163
}
164
ladda.stop();
165
});
168
window.location.href = $unsaved_changes.data('url');
169
});
170
171
function save(callback) {
172
var data = $form.serializeArray(),
173
$staff_phone = $('#bookly-phone', $form),
174
phone;
175
try {
176
+ phone = obj.options.intlTelInput.enabled ? $staff_phone.intlTelInput('getNumber') : $staff_phone.val();
177
if (phone == '') {
178
phone = $staff_phone.val();
179
}
208
csrf_token: ''
209
},
210
l10n : {},
211
+ savingStatus : function (data) {
212
+ $(document.body).trigger('staff_edit.save', [data]);
213
+ }
214
};
215
216
window.BooklyStaffDetails = Details;
backend/components/dialogs/staff/edit/resources/js/staff-edit-dialog.js ADDED
@@ -0,0 +1,274 @@
1
+ jQuery(function ($) {
2
+ 'use strict';
3
+
4
+ let $staffList = $('#staff-list'),
5
+ $modal = $('#bookly-staff-edit-modal'),
6
+ $modalBody = $('.modal-body', $modal),
7
+ $modalTitle = $('.modal-title', $modal),
8
+ $modalFooter = $('.modal-footer ', $modal),
9
+ $saveBtn = $('.bookly-js-save', $modalFooter),
10
+ $archiveBtn = $('.bookly-js-staff-archive', $modalFooter),
11
+ $validateErrors = $('.bookly-js-errors', $modalFooter),
12
+ $deleteCascadeModal = $('.bookly-js-delete-cascade-confirm'),
13
+ $staffCount = $('.bookly-js-staff-count'),
14
+ staff_id,
15
+ holidays
16
+ ;
17
+
18
+ $staffList
19
+ .on('click', '[data-action="edit"]', function () {
20
+ let data = $staffList.DataTable().row($(this).closest('td')).data();
21
+ staff_id = data.id;
22
+ editStaff(staff_id);
23
+ });
24
+
25
+ $('#bookly-js-new-staff')
26
+ .on('click', function () {
27
+ if (BooklyStaffEditDialogL10n.proRequired == '1' && $staffCount.html() > 0) {
28
+ booklyAlert({error: [BooklyStaffEditDialogL10n.limitation]});
29
+ return false;
30
+ } else {
31
+ staff_id = 0;
32
+ editStaff(staff_id);
33
+ }
34
+ });
35
+
36
+ if (BooklyStaffEditDialogL10n.activeStaffId != '0') {
37
+ staff_id = BooklyStaffEditDialogL10n.activeStaffId;
38
+ editStaff(staff_id);
39
+ }
40
+
41
+ function editStaff(staff_id) {
42
+ $modalTitle.html(staff_id ? BooklyStaffEditDialogL10n.editStaff : BooklyStaffEditDialogL10n.createStaff);
43
+ $('#bookly-staff-delete', $modalFooter).toggle(staff_id != 0);
44
+
45
+ $modalFooter.hide();
46
+ $validateErrors.html('');
47
+ $saveBtn.prop('disabled', false);
48
+ $modalBody.html('<div class="bookly-loading"></div>');
49
+ $modal.modal();
50
+ $.get(ajaxurl, {action: 'bookly_get_staff_data', id: staff_id, csrf_token: BooklyStaffEditDialogL10n.csrfToken}, function (response) {
51
+ $modalBody.html(response.data.html.edit);
52
+ booklyAlert(response.data.alert);
53
+ $modalFooter.show();
54
+ holidays = response.data.holidays;
55
+ let $details_container = $('#bookly-details-container', $modalBody),
56
+ $services_container = $('#bookly-services-container', $modalBody),
57
+ $schedule_container = $('#bookly-schedule-container', $modalBody),
58
+ $holidays_container = $('#bookly-holidays-container', $modalBody),
59
+ $special_days_container = $('#bookly-special-days-container', $modalBody)
60
+ ;
61
+ $details_container.append(response.data.html.details);
62
+ $services_container.append(response.data.html.services);
63
+ $schedule_container.append(response.data.html.schedule);
64
+ $holidays_container.append(response.data.html.holidays);
65
+ $special_days_container.append(response.data.html.special_days);
66
+
67
+ $('.panel-footer', $modalBody).hide();
68
+
69
+ new BooklyStaffDetails($details_container, {
70
+ get_details : {},
71
+ intlTelInput: BooklyStaffEditDialogL10n.intlTelInput,
72
+ l10n: BooklyStaffEditDialogL10n
73
+ });
74
+
75
+ $archiveBtn.toggle(staff_id ? response.data.staff.visibility !== 'archive' : false);
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Delete staff member.
81
+ */
82
+ $modalFooter
83
+ .on('click', '#bookly-staff-delete', function (e) {
84
+ e.preventDefault();
85
+
86
+ var ladda = Ladda.create(this),
87
+ data = {
88
+ action: 'bookly_remove_staff',
89
+ 'staff_ids[]': staff_id,
90
+ csrf_token: BooklyStaffEditDialogL10n.csrfToken
91
+ };
92
+ ladda.start();
93
+
94
+ var delete_staff = function (ajaxurl, data) {
95
+ $.post(ajaxurl, data, function (response) {
96
+ ladda.stop();
97
+ if (!response.success) {
98
+ switch (response.data.action) {
99
+ case 'show_modal':
100
+ $deleteCascadeModal.modal('show');
101
+ break;
102
+ case 'confirm':
103
+ if (confirm(BooklyStaffEditDialogL10n.areYouSure)) {
104
+ delete_staff(ajaxurl, $.extend(data, {force_delete: true}));
105
+ }
106
+ break;
107
+ }
108
+ } else {
109
+ $modal.modal('hide');
110
+ $staffList.DataTable().ajax.reload();
111
+ }
112
+ });
113
+ };
114
+
115
+ delete_staff(ajaxurl, data);
116
+ });
117
+
118
+ $modalBody
119
+ // Delete staff avatar
120
+ .on('click', '.bookly-thumb-delete', function () {
121
+ var $thumb = $(this).parents('.bookly-js-image');
122
+ $thumb.attr('style', '');
123
+ $modalBody.find('[name=attachment_id]').val('').trigger('change');
124
+ })
125
+
126
+ // Open details tab
127
+ .on('click', '#bookly-details-tab', function () {
128
+ $('.tab-pane > div').hide();
129
+ $('#bookly-details-container', $modalBody).show();
130
+ })
131
+
132
+ // Open services tab
133
+ .on('click', '#bookly-services-tab', function () {
134
+ $('.tab-pane > div').hide();
135
+ let $container = $('#bookly-services-container', $modalBody);
136
+ new BooklyStaffServices($container, {
137
+ get_staff_services: {
138
+ action: 'bookly_get_staff_services',
139
+ staff_id: staff_id,
140
+ csrf_token: BooklyStaffEditDialogL10n.csrfToken
141
+ },
142
+ onLoad: function () {
143
+ $('.panel-footer', $container).hide();
144
+ $('#bookly-services-save', $container).addClass('bookly-js-save');
145
+ $(document.body).trigger('staff_edit.validation', ['staff-services', false, '']);
146
+ },
147
+ l10n: BooklyStaffEditDialogL10n.services,
148
+ });
149
+
150
+ $('#bookly-services-save', $container).addClass('bookly-js-save');
151
+ $container.show();
152
+ })
153
+
154
+ // Open special days tab
155
+ .on('click', '#bookly-special-days-tab', function () {
156
+ $('.tab-pane > div').hide();
157
+ let $container = $('#bookly-special-days-container', $modalBody);
158
+ new BooklyStaffSpecialDays($container, {
159
+ staff_id: staff_id,
160
+ csrf_token: BooklyStaffEditDialogL10n.csrfToken,
161
+ l10n: SpecialDaysL10n
162
+ });
163
+
164
+ $('#bookly-js-special-days-save-days', $container).addClass('bookly-js-save');
165
+ $container.show();
166
+ })
167
+
168
+ // Open schedule tab
169
+ .on('click', '#bookly-schedule-tab', function () {
170
+ $('.tab-pane > div').hide();
171
+ let $container = $('#bookly-schedule-container', $modalBody);
172
+
173
+ new BooklyStaffSchedule($container, {
174
+ get_staff_schedule: {
175
+ action: 'bookly_get_staff_schedule',
176
+ staff_id: staff_id,
177
+ csrf_token: BooklyStaffEditDialogL10n.csrfToken
178
+ },
179
+ onLoad: function () {
180
+ $('.panel-footer', $container).hide();
181
+ $('#bookly-schedule-save', $container).addClass('bookly-js-save');
182
+ },
183
+ l10n: BooklyL10n
184
+ });
185
+
186
+ $('#bookly-schedule-save', $modalBody).addClass('bookly-js-save');
187
+ $container.show();
188
+ })
189
+
190
+ // Open holiday tab
191
+ .on('click', '#bookly-holidays-tab', function () {
192
+ $('.tab-pane > div').hide();
193
+ let $container = $('#bookly-holidays-container', $modalBody);
194
+
195
+ new BooklyStaffDaysOff($container, {
196
+ staff_id: staff_id,
197
+ csrf_token: BooklyStaffEditDialogL10n.csrfToken,
198
+ l10n: jQuery.extend(BooklyStaffEditDialogL10n.holidays, {holidays: holidays})
199
+ });
200
+
201
+ $container.show();
202
+ });
203
+
204
+ $deleteCascadeModal
205
+ // Delete
206
+ .on('click', '.bookly-js-delete', function () {
207
+ $modalBody.html('<div class="bookly-loading"></div>');
208
+ ladda = Ladda.create(this);
209
+ ladda.start();
210
+ delete_staff(ajaxurl, $.extend(data, {force_delete: true}));
211
+ $deleteCascadeModal.modal('hide');
212
+ ladda.stop();
213
+ })
214
+ // Edit
215
+ .on('click', '.bookly-js-edit', function () {
216
+ ladda = Ladda.create(this);
217
+ ladda.start();
218
+ window.location.href = response.data.filter_url;
219
+ });
220
+
221
+ let waitResposes = 0,
222
+ ladda,
223
+ success;
224
+
225
+ $saveBtn
226
+ .on('click', function (e) {
227
+ e.preventDefault();
228
+ ladda = Ladda.create(this);
229
+ ladda.start();
230
+
231
+ let $buttons = $('.panel-footer', $modalBody);
232
+ waitResposes = 0;
233
+ success = true;
234
+ $buttons
235
+ .each(function () {
236
+ let $button = $('.bookly-js-save', this);
237
+ if ($button.length > 0) {
238
+ waitResposes++;
239
+ $button.trigger('click');
240
+ }
241
+ });
242
+ });
243
+
244
+ $(document.body)
245
+ .on('staff_edit.save', {},
246
+ function (event, result) {
247
+ if (waitResposes > 0) {
248
+ if (result.hasOwnProperty('error')){
249
+ success = false;
250
+ }
251
+ waitResposes --;
252
+ }
253
+ if (waitResposes <= 0) {
254
+ $staffList.DataTable().ajax.reload();
255
+ ladda ? ladda.stop() : null;
256
+ $modal.modal('hide');
257
+ booklyAlert({success: [BooklyStaffEditDialogL10n.settingsSaved]})
258
+ }
259
+ })
260
+ .on('staff_edit.validation', {},
261
+ function (event, tab, has_error, info) {
262
+ let id = 'tab-' + tab + '-validation',
263
+ $container = $validateErrors.find('#' + id);
264
+ if (has_error) {
265
+ if ($container.length === 0) {
266
+ $validateErrors.append($('<div/>').attr('id', id).html(info));
267
+ }
268
+ } else {
269
+ $container.remove();
270
+ }
271
+
272
+ $saveBtn.prop('disabled', $('>', $validateErrors).length !== 0);
273
+ });
274
+ });
backend/components/dialogs/staff/edit/resources/js/staff-schedule.js ADDED
@@ -0,0 +1,368 @@
1
+ jQuery(function ($) {
2
+ var Schedule = function ($container, options) {
3
+ var obj = this;
4
+ jQuery.extend(obj.options, options);
5
+
6
+ // Loads schedule list
7
+ if (!$container.children().length) {
8
+ $container.html('<div class="bookly-loading"></div>');
9
+ $.ajax({
10
+ type: 'POST',
11
+ url: ajaxurl,
12
+ data: obj.options.get_staff_schedule,
13
+ dataType: 'json',
14
+ xhrFields: {withCredentials: true},
15
+ crossDomain: 'withCredentials' in new XMLHttpRequest(),
16
+ success: function (response) {
17
+ // fill in the container
18
+ $container.html('');
19
+ $container.append(response.data.html);
20
+ $container.removeData('init');
21
+ obj.options.onLoad();
22
+ init($container, obj);
23
+ }
24
+ });
25
+ } else {
26
+ init($container, obj);
27
+ }
28
+
29
+ function init($container, obj) {
30
+ if ($container.data('init') != true) {
31
+ $container.booklyHelp();
32
+
33
+ // init 'add break' functionality
34
+ $('.bookly-js-toggle-popover:not(.break-interval)', $container).popover({
35
+ html: true,
36
+ placement: 'bottom',
37
+ template: '<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>',
38
+ trigger: 'manual',
39
+ content: function () {
40
+ return $($(this).data('popover-content')).html()
41
+ }
42
+ }).on('click', function () {
43
+ $(this).popover('toggle');
44
+
45
+ var $popover = $(this).next('.popover'),
46
+ working_start = $popover.closest('.row').find('.working-schedule-start').val(),
47
+ $break_start = $popover.find('.break-start'),
48
+ $break_end = $popover.find('.break-end'),
49
+ working_start_time = working_start.split(':'),
50
+ working_start_hours = parseInt(working_start_time[0], 10),
51
+ break_start_hours = working_start_hours + 1;
52
+ if (break_start_hours < 10) {
53
+ break_start_hours = '0' + break_start_hours;
54
+ }
55
+ var break_end_hours = working_start_hours + 2;
56
+ if (break_end_hours < 10) {
57
+ break_end_hours = '0' + break_end_hours;
58
+ }
59
+ var break_end_hours_str = break_end_hours + ':' + working_start_time[1] + ':' + working_start_time[2],
60
+ break_start_hours_str = break_start_hours + ':' + working_start_time[1] + ':' + working_start_time[2];
61
+
62
+ $break_start.val(break_start_hours_str);
63
+ $break_end.val(break_end_hours_str);
64
+
65
+ hideInaccessibleBreaks($break_start, $break_end);
66
+
67
+ $popover.find('.bookly-popover-close').on('click', function () {
68
+ $popover.popover('hide');
69
+ });
70
+ });
71
+
72
+ $container.off()
73
+ // Save Schedule
74
+ .on('click', '#bookly-schedule-save', function (e) {
75
+ e.preventDefault();
76
+ var ladda = Ladda.create(this);
77
+ ladda.start();
78
+ var data = {};
79
+ $('select.working-schedule-start, select.working-schedule-end, input:hidden', $container).each(function () {
80
+ data[this.name] = this.value;
81
+ });
82
+ data['location_id'] = $('#staff_location_id', $container).val();
83
+ data['custom_location_settings'] = $('#custom_location_settings', $container).val();
84
+ data['staff_id'] = options.get_staff_schedule.staff_id;
85
+ $.post(ajaxurl, $.param(data), function () {
86
+ ladda.stop();
87
+ obj.options.savingStatus({success: [obj.options.l10n.saved]});
88
+ });
89
+ })
90
+ // Resets initial schedule values
91
+ .on('click', '#bookly-schedule-reset', function (e) {
92
+ e.preventDefault();
93
+ var ladda = Ladda.create(this);
94
+ ladda.start();
95
+
96
+ $('.working-schedule-start', $container).each(function () {
97
+ $(this).val($(this).data('default_value'));
98
+ $(this).trigger('change');
99
+ });
100
+
101
+ $('.working-schedule-end', $container).each(function () {
102
+ $(this).val($(this).data('default_value'));
103
+ });
104
+
105
+ // reset breaks
106
+ $.ajax({
107
+ url: ajaxurl,
108
+ type: 'POST',
109
+ data: {action: 'bookly_reset_breaks', breaks: $(this).data('default-breaks'), staff_cabinet: $(this).data('staff-cabinet') || 0, csrf_token: obj.options.l10n.csrfToken},
110
+ dataType: 'json',
111
+ success: function (response) {
112
+ for (var k in response) {
113
+ var $content = $(response[k]);
114
+ $('[data-staff_schedule_item_id=' + k + '] .breaks', $container).html($content);
115
+ $content.find('.bookly-intervals-wrapper .delete-break').on('click', function () {
116
+ deleteBreak.call(this);
117
+ });
118
+ }
119
+ },
120
+ complete: function () {
121
+ ladda.stop();
122
+ }
123
+ });
124
+ })
125
+
126
+ .on('click', '.break-interval', function () {
127
+ var $button = $(this);
128
+ $('.popover').popover('hide');
129
+ var break_id = $button.closest('.bookly-intervals-wrapper').data('break_id');
130
+ $(this).popover({
131
+ html: true,
132
+ placement: 'bottom',
133
+ template: '<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>',
134
+ content: function () {
135
+ return $('.bookly-js-content-break-' + break_id).html();
136
+ },
137
+ trigger: 'manual'
138
+ });
139
+
140
+ $(this).popover('toggle');
141
+
142
+ var $popover = $(this).next('.popover'),
143
+ $break_start = $popover.find('.break-start'),
144
+ $break_end = $popover.find('.break-end');
145
+
146
+ if ($button.hasClass('break-interval')) {
147
+ var interval = $button.html().trim().split(' - ');
148
+ rangeTools.setVal($break_start, interval[0]);
149
+ rangeTools.setVal($break_end, interval[1]);
150
+ }
151
+
152
+ hideInaccessibleBreaks($break_start, $break_end, true);
153
+
154
+ $popover.find('.bookly-popover-close').on('click', function () {
155
+ $popover.popover('hide');
156
+ });
157
+ })
158
+
159
+ .on('click', '.bookly-js-save-break', function (e) {
160
+ var $table = $(this).closest('.bookly-js-schedule-form'),
161
+ $row = $table.parents('.staff-schedule-item-row').first(),
162
+ data = {
163
+ action: 'bookly_staff_schedule_handle_break',
164
+ staff_schedule_item_id: $row.data('staff_schedule_item_id'),
165
+ start_time: $table.find('.break-start > option:selected').val(),
166
+ end_time: $table.find('.break-end > option:selected').val(),
167
+ working_end: $row.find('.working-schedule-end > option:selected').val(),
168
+ working_start: $row.find('.working-schedule-start > option:selected').val(),
169
+ csrf_token: obj.options.l10n.csrfToken
170
+ },
171
+ $break_interval_wrapper = $table.parents('.bookly-intervals-wrapper').first(),
172
+ ladda = Ladda.create(e.currentTarget);
173
+ ladda.start();
174
+
175
+ if ($break_interval_wrapper.data('break_id')) {
176
+ data['break_id'] = $break_interval_wrapper.data('break_id');
177
+ }
178
+ $.ajax({
179
+ url : ajaxurl,
180
+ type: 'POST',
181
+ data: data,
182
+ dataType: 'json',
183
+ success: function (response) {
184
+ if (response.success) {
185
+ if (response['item_content']) {
186
+ var $new_break_interval_item = $(response['item_content']);
187
+ $new_break_interval_item
188
+ .hide()
189
+ .appendTo($row.find('.breaks-list-content'))
190
+ .fadeIn('slow');
191
+ } else if (response.data.interval) {
192
+ $break_interval_wrapper
193
+ .find('.break-interval')
194
+ .text(response.data.interval);
195
+ }
196
+ $('.popover').popover('hide');
197
+ } else {
198
+ obj.options.savingStatus({error: [response.data.message]});
199
+ }
200
+ },
201
+ complete: function () {
202
+ ladda.stop();
203
+ }
204
+ });
205
+
206
+ return false;
207
+ })
208
+
209
+ .on('click', '.bookly-intervals-wrapper .delete-break', function () {
210
+ deleteBreak.call(this);
211
+ })
212
+
213
+ .on('change', '.break-start', function () {
214
+ var $start = $(this);
215
+ var $end = $start.parents('.bookly-flexbox').find('.break-end');
216
+ hideInaccessibleBreaks($start, $end);
217
+ })
218
+
219
+ .on('change', '.working-schedule-start', function () {
220
+ var $this = $(this),
221
+ $end_select = $this.closest('.bookly-flexbox').find('.working-schedule-end'),
222
+ start_time = $this.val();
223
+
224
+ // Hide end time options to keep them within 24 hours after start time.
225
+ var parts = start_time.split(':');
226
+ parts[0] = parseInt(parts[0]) + 24;
227
+ var end_time = parts.join(':');
228
+ var frag = document.createDocumentFragment();
229
+ var old_value = $end_select.val();
230
+ var new_value = null;
231
+ $('option', $end_select).each(function () {
232
+ if (this.value <= start_time || this.value > end_time) {
233
+ var span = document.createElement('span');
234
+ span.style.display = 'none';
235
+ span.appendChild(this.cloneNode(true));
236
+ frag.appendChild(span);
237
+ } else {
238
+ frag.appendChild(this.cloneNode(true));
239
+ if (new_value === null || old_value == this.value) {
240
+ new_value = this.value;
241
+ }
242
+ }
243
+ });
244
+ $end_select.empty().append(frag).val(new_value);
245
+
246
+ // when the working day is disabled (working start time is set to 'OFF')
247
+ // hide all the elements inside the row
248
+ if (!$this.val()) {
249
+ $this.closest('.row').find('.bookly-hide-on-off').hide();
250
+ } else {
251
+ $this.closest('.row').find('.bookly-hide-on-off').show();
252
+ }
253
+ })
254
+ // Change location
255
+ .on('change', '#staff_location_id', function () {
256
+ var get_staff_schedule = {
257
+ action: options.get_staff_schedule.action,
258
+ staff_id: options.get_staff_schedule.staff_id,
259
+ csrf_token: options.get_staff_schedule.csrf_token,
260
+ },
261
+ staff_location_id = $('#staff_location_id', $container).val();
262
+ if (staff_location_id != '') {
263
+ get_staff_schedule['location_id'] = staff_location_id;
264
+ }
265
+ $container.html('');
266
+ new BooklyStaffSchedule($container, {
267
+ get_staff_schedule: get_staff_schedule,
268
+ l10n: options.l10n
269
+ });
270
+ })
271
+ // Change default/custom settings for location
272
+ .on('change', '#custom_location_settings', function () {
273
+ if ($(this).val() == 1) {
274
+ $('.panel', $container).show();
275
+ } else {
276
+ $('.panel', $container).hide();
277
+ }
278
+ })
279
+ ;
280
+
281
+ $('#custom_location_settings', $container).trigger('change');
282
+ $('.working-schedule-start', $container).trigger('change');
283
+ $('.break-start', $container).trigger('change');
284
+ $container.data('init', true);
285
+ }
286
+ }
287
+
288
+ function hideInaccessibleBreaks($start, $end, force_keep_values) {
289
+ var $row = $start.closest('.row'),
290
+ $working_start = $row.find('.working-schedule-start'),
291
+ $working_end = $row.find('.working-schedule-end'),
292
+ frag1 = document.createDocumentFragment(),
293
+ frag2 = document.createDocumentFragment(),
294
+ old_value = $start.val(),
295
+ new_value = null;
296
+
297
+ $('option', $start).each(function () {
298
+ if ((this.value < $working_start.val() || this.value >= $working_end.val()) && (!force_keep_values || this.value != old_value)) {
299
+ var span = document.createElement('span');
300
+ span.style.display = 'none';
301
+ span.appendChild(this.cloneNode(true));
302
+ frag1.appendChild(span);
303
+ } else {
304
+ frag1.appendChild(this.cloneNode(true));
305
+ if (new_value === null || old_value == this.value) {
306
+ new_value = this.value;
307
+ }
308
+ }
309
+ });
310
+ $start.empty().append(frag1).val(new_value);
311
+
312
+ // Hide end time options with value less than in the start time.
313
+ old_value = $end.val();
314
+ new_value = null;
315
+ $('option', $end).each(function () {
316
+ if ((this.value <= $start.val() || this.value > $working_end.val()) && (!force_keep_values || this.value != old_value)) {
317
+ var span = document.createElement('span');
318
+ span.style.display = 'none';
319
+ span.appendChild(this.cloneNode(true));
320
+ frag2.appendChild(span);
321
+ } else {
322
+ frag2.appendChild(this.cloneNode(true));
323
+ if (new_value === null || old_value == this.value) {
324
+ new_value = this.value;
325
+ }
326
+ }
327
+ });
328
+ $end.empty().append(frag2).val(new_value);
329
+ }
330
+
331
+ function deleteBreak() {
332
+ var $break_interval_wrapper = $(this).closest('.bookly-intervals-wrapper');
333
+ if (confirm(obj.options.l10n.are_you_sure)) {
334
+ var ladda = Ladda.create(this);
335
+ ladda.start();
336
+ $.ajax({
337
+ url: ajaxurl,
338
+ type: 'POST',
339
+ data: {action: 'bookly_delete_staff_schedule_break', id: $break_interval_wrapper.data('break_id'), csrf_token: obj.options.l10n.csrfToken},
340
+ success: function (response) {
341
+ if (response.success) {
342
+ $break_interval_wrapper.remove();
343
+ }
344
+ },
345
+ complete: function () {
346
+ ladda.stop();
347
+ }
348
+ });
349
+ }
350
+ }
351
+ };
352
+
353
+ Schedule.prototype.options = {
354
+ get_staff_schedule: {
355
+ action: 'bookly_get_staff_schedule',
356
+ staff_id: -1,
357
+ csrf_token: ''
358
+ },
359
+ savingStatus: function (data) {
360
+ $(document.body).trigger('staff_edit.save', data);
361
+ },
362
+ onLoad: function () {},
363
+ l10n: {}
364
+ };
365
+
366
+ window.BooklyStaffSchedule = Schedule;
367
+ });
368
+
backend/components/dialogs/staff/edit/resources/js/staff-services.js ADDED
@@ -0,0 +1,187 @@
1
+ jQuery(function ($) {
2
+
3
+ var Services = function($container, options) {
4
+ var obj = this;
5
+ jQuery.extend(obj.options, options);
6
+
7
+ // Load services form
8
+ if (!$container.children().length) {
9
+ $container.html('<div class="bookly-loading"></div>');
10
+ $.ajax({
11
+ type : 'POST',
12
+ url : ajaxurl,
13
+ data : obj.options.get_staff_services,
14
+ dataType : 'json',
15
+ xhrFields : { withCredentials: true },
16
+ crossDomain : 'withCredentials' in new XMLHttpRequest(),
17
+ success : function (response) {
18
+ $container.html('');
19
+ $container.append(response.data.html);
20
+ $container.removeData('init');
21
+ obj.options.onLoad();
22
+ init($container, obj);
23
+ }
24
+ });
25
+ } else {
26
+ init($container, obj);
27
+ }
28
+ };
29
+
30
+ function init($container, obj) {
31
+ if ($container.data('init') != true) {
32
+ let $services_form = $('form', $container);
33
+ $services_form.booklyHelp();
34
+ $(document.body).trigger('special_hours.tab_init', [$container, obj.options]);
35
+ var autoTickCheckboxes = function () {
36
+ // Handle 'select category' checkbox.
37
+ $('.bookly-services-category .bookly-category-checkbox').each(function () {
38
+ $(this).prop(
39
+ 'checked',
40
+ $('.bookly-category-services .bookly-service-checkbox.bookly-category-' + $(this).data('category-id') + ':not(:checked)').length == 0
41
+ );
42
+ });
43
+ // Handle 'select all services' checkbox.
44
+ $('#bookly-check-all-entities').prop(
45
+ 'checked',
46
+ $('.bookly-service-checkbox:not(:checked)').length == 0
47
+ );
48
+ };
49
+ var checkCapacityError = function ($form_group) {
50
+ if (parseInt($form_group.find('.bookly-js-capacity-min').val()) > parseInt($form_group.find('.bookly-js-capacity-max').val())) {
51
+ $form_group.addClass('has-error');
52
+ } else {
53
+ $form_group.removeClass('has-error');
54
+ }
55
+ let has_errors = $('.bookly-js-capacity-form-group.has-error', $container).length != 0;
56
+
57
+ if (has_errors) {
58
+ $services_form.find('.bookly-js-services-error').html(obj.options.l10n.capacity_error);
59
+ $services_form.find('#bookly-services-save').prop('disabled', true);
60
+ } else {
61
+ $services_form.find('.bookly-js-services-error').html('');
62
+ $services_form.find('#bookly-services-save').prop('disabled', false);
63
+ }
64
+ obj.options.validation(has_errors, obj.options.l10n.capacity_error);
65
+ };
66
+
67
+ $services_form
68
+ // Select all services related to chosen category
69
+ .on('click', '.bookly-category-checkbox', function () {
70
+ $('.bookly-category-services .bookly-category-' + $(this).data('category-id')).prop('checked', $(this).is(':checked')).change();
71
+ autoTickCheckboxes();
72
+ })
73
+ // Check and uncheck all services
74
+ .on('click', '#bookly-check-all-entities', function () {
75
+ $('.bookly-service-checkbox', $services_form).prop('checked', $(this).is(':checked')).change();
76
+ $('.bookly-category-checkbox').prop('checked', $(this).is(':checked'));
77
+ })
78
+ // Select service
79
+ .on('click', '.bookly-service-checkbox', function () {
80
+ autoTickCheckboxes();
81
+ })
82
+ // Save services
83
+ .on('click', '#bookly-services-save', function (e) {
84
+ e.preventDefault();
85
+ var ladda = Ladda.create(this);
86
+ ladda.start();
87
+ $.ajax({
88
+ type : 'POST',
89
+ url : ajaxurl,
90
+ data : $services_form.serialize(),
91
+ dataType : 'json',
92
+ xhrFields : {withCredentials: true},
93
+ crossDomain: 'withCredentials' in new XMLHttpRequest(),
94
+ success : function (response) {
95
+ ladda.stop();
96
+ if (response.success) {
97
+ obj.options.savingStatus({success: [obj.options.l10n.saved]});
98
+ }
99
+ }
100
+ });
101
+ })
102
+ // After reset auto tick group checkboxes.
103
+ .on('click', '#bookly-services-reset', function () {
104
+ setTimeout(function () {
105
+ autoTickCheckboxes();
106
+ $('.bookly-js-capacity-form-group', $services_form).each(function () {
107
+ checkCapacityError($(this));
108
+ });
109
+ $('.bookly-service-checkbox', $services_form).trigger('change');
110
+ }, 0);
111
+ })
112
+ // Change location
113
+ .on('change', '#staff_location_id', function () {
114
+ let data = {};
115
+ if (this.value != '') {
116
+ data.location_id = this.value;
117
+ }
118
+ let get_staff_services = jQuery.extend(data, obj.options.get_staff_services);
119
+ $container.html('');
120
+ new BooklyStaffServices($container, {
121
+ get_staff_services: get_staff_services,
122
+ l10n : obj.options.l10n,
123
+ });
124
+ })
125
+ // Change default/custom settings for location
126
+ .on('change', '#custom_location_settings', function () {
127
+ if ($(this).val() == 1) {
128
+ $('.panel', $services_form).show();
129
+ } else {
130
+ $('.panel', $services_form).hide();
131
+ }
132
+ });
133
+
134
+ $('.bookly-service-checkbox').on('change', function () {
135
+ var $this = $(this),
136
+ $service = $this.closest('li'),
137
+ $inputs = $service.find('input:not(:checkbox)');
138
+
139
+ $inputs.attr('disabled', !$this.is(':checked'));
140
+
141
+ // Handle package-service connections
142
+ if ($(this).is(':checked') && $service.data('service-type') == 'package') {
143
+ $('li[data-service-type="simple"][data-service-id="' + $service.data('sub-service') + '"] .bookly-service-checkbox', $services_form).prop('checked', true).trigger('change');
144
+ $('.bookly-js-capacity-min', $service).val($('li[data-service-type="simple"][data-service-id="' + $service.data('sub-service') + '"] .bookly-js-capacity-min', $services_form).val());
145
+ $('.bookly-js-capacity-max', $service).val($('li[data-service-type="simple"][data-service-id="' + $service.data('sub-service') + '"] .bookly-js-capacity-max', $services_form).val());
146
+ }
147
+ if (!$(this).is(':checked') && $service.data('service-type') == 'simple') {
148
+ $('li[data-service-type="package"][data-sub-service="' + $service.data('service-id') + '"] .bookly-service-checkbox', $services_form).prop('checked', false).trigger('change');
149
+ }
150
+ });
151
+
152
+ $('.bookly-js-capacity').on('keyup change', function () {
153
+ var $service = $(this).closest('li');
154
+ if ($service.data('service-type') == 'simple') {
155
+ if ($(this).hasClass('bookly-js-capacity-min')) {
156
+ $('li[data-service-type="package"][data-sub-service="' + $service.data('service-id') + '"] .bookly-js-capacity-min', $services_form).val($(this).val());
157
+ } else {
158
+ $('li[data-service-type="package"][data-sub-service="' + $service.data('service-id') + '"] .bookly-js-capacity-max', $services_form).val($(this).val());
159
+ }
160
+ }
161
+ checkCapacityError($(this).closest('.form-group'));
162
+ });
163
+ $('#custom_location_settings', $services_form).trigger('change');
164
+ autoTickCheckboxes();
165
+ $container.data('init', true);
166
+ }
167
+ }
168
+
169
+ Services.prototype.options = {
170
+ get_staff_services: {
171
+ action : 'bookly_get_staff_services',
172
+ staff_id: -1,
173
+ csrf_token: ''
174
+ },
175
+ booklyAlert: window.booklyAlert,
176
+ savingStatus: function (data) {
177
+ $(document.body).trigger('staff_edit.save', data);
178
+ },
179
+ validation: function (has_error, info) {
180
+ $(document.body).trigger('staff_edit.validation', ['staff-services', has_error, info]);
181
+ },
182
+ onLoad: function () {},
183
+ l10n: {}
184
+ };
185
+
186
+ window.BooklyStaffServices = Services;
187
+ });
backend/{modules/staff → components/dialogs/staff/edit}/templates/_break.php RENAMED
File without changes
backend/{modules/staff → components/dialogs/staff/edit}/templates/_breaks.php RENAMED
File without changes
backend/{modules/staff/templates/_details.php → components/dialogs/staff/edit/templates/details.php} RENAMED
@@ -7,10 +7,40 @@ use Bookly\Lib\Config;
7
/** @var Bookly\Lib\Entities\Staff $staff */
8
?>
9
<form class="bookly-js-staff-details">
10
- <div class="form-group">
11
- <label for="bookly-full-name"><?php esc_html_e( 'Full name', 'bookly' ) ?></label>
12
- <input type="text" class="form-control" id="bookly-full-name" name="full_name" value="<?php echo esc_attr( $staff->getFullName() ) ?>"/>
13
</div>
14
<?php if ( Common::isCurrentUserAdmin() ) : ?>
15
<div class="form-group">
16
<label for="bookly-wp-user"><?php esc_html_e( 'User', 'bookly' ) ?></label>
@@ -31,7 +61,7 @@ use Bookly\Lib\Config;
31
<?php endif ?>
32
33
<div class="row">
34
- <div class="col-sm-6">
35