WordPress Online Booking and Scheduling Plugin – Bookly - Version 17.6

Version Description

Download this release

Release Info

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

Code changes from version 17.5 to 17.6

Files changed (148) hide show
  1. backend/Backend.php +16 -13
  2. backend/components/dashboard/appointments/Ajax.php +5 -0
  3. backend/components/dashboard/appointments/Widget.php +1 -1
  4. backend/components/dialogs/appointment/delete/Ajax.php +4 -2
  5. backend/components/dialogs/appointment/edit/Ajax.php +65 -33
  6. backend/components/dialogs/appointment/edit/Dialog.php +4 -2
  7. backend/components/dialogs/appointment/edit/resources/js/ng-appointment.js +198 -59
  8. backend/components/dialogs/appointment/edit/templates/edit.php +38 -16
  9. backend/components/dialogs/customer/edit/resources/js/ng-customer.js +0 -4
  10. backend/components/dialogs/notifications/templates/_types.php +1 -1
  11. backend/components/dialogs/payment/Ajax.php +21 -0
  12. backend/components/dialogs/payment/resources/js/ng-payment_details.js +10 -7
  13. backend/components/dialogs/payment/templates/details.php +34 -24
  14. backend/components/dialogs/queue/Dialog.php +36 -0
  15. backend/components/dialogs/queue/resources/js/queue-dialog.js +70 -0
  16. backend/components/dialogs/queue/templates/dialog.php +30 -0
  17. backend/components/dialogs/service/edit/resources/js/service-edit-dialog.js +2 -0
  18. backend/components/dialogs/service/edit/templates/dialog.php +4 -5
  19. backend/components/dialogs/sms/Dialog.php +1 -0
  20. backend/components/dialogs/sms/resources/js/notification-dialog.js +8 -6
  21. backend/components/dialogs/staff/edit/forms/StaffServices.php +1 -1
  22. backend/components/dialogs/staff/edit/resources/js/staff-edit-dialog.js +34 -34
  23. backend/components/dialogs/staff/edit/resources/js/staff-schedule.js +1 -1
  24. backend/components/dialogs/staff/edit/templates/dialog.php +4 -1
  25. backend/components/dialogs/staff/edit/templates/dialog_body.php +3 -3
  26. backend/components/notices/PoweredBy.php +35 -0
  27. backend/components/notices/PoweredByAjax.php +31 -0
  28. backend/components/notices/resources/js/powered-by.js +12 -0
  29. backend/components/notices/templates/powered_by.php +16 -0
  30. backend/modules/appearance/proxy/Pro.php +4 -4
  31. backend/modules/appearance/proxy/ServiceExtras.php +1 -0
  32. backend/modules/appearance/resources/js/appearance.js +18 -4
  33. backend/modules/appearance/templates/_7_payment.php +21 -19
  34. backend/modules/appearance/templates/index.php +2 -1
  35. backend/modules/appointments/Ajax.php +5 -3
  36. backend/modules/appointments/Page.php +2 -1
  37. backend/modules/appointments/resources/js/appointments.js +32 -6
  38. backend/modules/appointments/templates/index.php +7 -4
  39. backend/modules/calendar/Ajax.php +3 -2
  40. backend/modules/calendar/resources/js/calendar-common.js +4 -1
  41. backend/modules/calendar/templates/calendar.php +1 -0
  42. backend/modules/customers/Ajax.php +43 -0
  43. backend/modules/dashboard/resources/js/dashboard.js +1 -1
  44. backend/modules/debug/Ajax.php +131 -28
  45. backend/modules/debug/Page.php +44 -95
  46. backend/modules/debug/lib/QueryBuilder.php +581 -0
  47. backend/modules/debug/lib/Schema.php +110 -0
  48. backend/modules/debug/resources/js/debug.js +112 -33
  49. backend/modules/debug/templates/index.php +78 -19
  50. backend/modules/messages/Ajax.php +1 -1
  51. backend/modules/messages/Page.php +2 -2
  52. backend/modules/notifications/templates/_test_email_modal.php +1 -1
  53. backend/modules/payments/Page.php +3 -2
  54. backend/modules/payments/resources/js/payments.js +29 -8
  55. backend/modules/payments/templates/index.php +7 -5
  56. backend/modules/services/resources/js/services-list.js +12 -14
  57. backend/modules/services/templates/time.php +6 -6
  58. backend/modules/settings/Page.php +1 -0
  59. backend/modules/settings/templates/_generalForm.php +1 -0
  60. backend/modules/shop/Page.php +2 -2
  61. backend/modules/sms/Ajax.php +61 -0
  62. backend/modules/sms/Page.php +8 -31
  63. backend/modules/sms/resources/js/notifications-list.js +4 -2
  64. backend/modules/sms/resources/js/sms.js +2 -2
  65. backend/modules/staff/Ajax.php +0 -60
  66. backend/modules/staff/resources/js/staff-list.js +10 -16
  67. backend/resources/bootstrap/css/bootstrap-theme.min.css +2 -2
  68. backend/resources/css/select2.min.css +1 -1
  69. backend/resources/js/jCal.js +3 -3
  70. backend/resources/js/select2.full.min.js +1 -1
  71. frontend/modules/booking/Ajax.php +108 -65
  72. frontend/modules/booking/templates/7_payment.php +19 -18
  73. frontend/modules/booking/templates/_progress_tracker.php +6 -7
  74. frontend/modules/booking/templates/short_code.php +6 -0
  75. frontend/resources/css/bookly-main.css +11 -0
  76. languages/bookly-ar.mo +0 -0
  77. languages/bookly-ar.po +7384 -0
  78. languages/bookly-cs_CZ.mo +0 -0
  79. languages/bookly-cs_CZ.po +103 -116
  80. languages/bookly-de_DE.mo +0 -0
  81. languages/bookly-de_DE.po +103 -126
  82. languages/bookly-es_ES.mo +0 -0
  83. languages/bookly-es_ES.po +103 -123
  84. languages/bookly-fr_FR.mo +0 -0
  85. languages/bookly-fr_FR.po +103 -124
  86. languages/bookly-it_IT.mo +0 -0
  87. languages/bookly-it_IT.po +103 -116
  88. languages/bookly-ja.mo +0 -0
  89. languages/bookly-ja.po +103 -124
  90. languages/bookly-nl_NL.mo +0 -0
  91. languages/bookly-nl_NL.po +108 -127
  92. languages/bookly-pt_BR.mo +0 -0
  93. languages/bookly-pt_BR.po +1725 -2150
  94. languages/bookly-pt_PT.mo +0 -0
  95. languages/bookly-pt_PT.po +1725 -2150
  96. languages/bookly-ro_RO.mo +0 -0
  97. languages/bookly-ro_RO.po +103 -118
  98. languages/bookly-ru_RU.mo +0 -0
  99. languages/bookly-ru_RU.po +103 -122
  100. languages/bookly-sv_SE.mo +0 -0
  101. languages/bookly-sv_SE.po +103 -116
  102. languages/bookly-tr_TR.mo +0 -0
  103. languages/bookly-tr_TR.po +103 -109
  104. languages/bookly-zh_TW.mo +0 -0
  105. languages/bookly-zh_TW.po +103 -116
  106. languages/bookly.pot +90 -57
  107. lib/API.php +1 -1
  108. lib/Cart.php +27 -29
  109. lib/CartInfo.php +16 -0
  110. lib/Config.php +14 -0
  111. lib/Installer.php +3 -2
  112. lib/Plugin.php +2 -1
  113. lib/Query.php +13 -9
  114. lib/Routines.php +2 -2
  115. lib/SMS.php +4 -30
  116. lib/Updater.php +30 -2
  117. lib/UserBookingData.php +1 -1
  118. lib/Validator.php +1 -1
  119. lib/base/Entity.php +30 -14
  120. lib/base/Plugin.php +5 -2
  121. lib/data_holders/booking/Order.php +34 -21
  122. lib/data_holders/booking/Series.php +22 -9
  123. lib/entities/Appointment.php +1 -1
  124. lib/entities/Customer.php +4 -2
  125. lib/entities/CustomerAppointment.php +3 -3
  126. lib/entities/Payment.php +7 -3
  127. lib/entities/Service.php +2 -2
  128. lib/entities/Staff.php +3 -3
  129. lib/entities/StaffScheduleItem.php +1 -1
  130. lib/entities/StaffService.php +1 -1
  131. lib/entities/SubService.php +2 -2
  132. lib/notifications/assets/item/Attachments.php +3 -1
  133. lib/notifications/assets/item/Codes.php +3 -2
  134. lib/notifications/assets/item/proxy/Pro.php +17 -0
  135. lib/notifications/assets/test/Codes.php +1 -1
  136. lib/notifications/base/Reminder.php +71 -13
  137. lib/notifications/booking/BaseSender.php +20 -14
  138. lib/notifications/booking/Sender.php +20 -15
  139. lib/notifications/cart/Sender.php +1 -1
  140. lib/notifications/cart/proxy/Pro.php +1 -1
  141. lib/proxy/CustomerGroups.php +4 -3
  142. lib/proxy/RecurringAppointments.php +1 -1
  143. lib/proxy/WaitingList.php +1 -1
  144. lib/slots/Generator.php +29 -18
  145. lib/utils/Common.php +12 -2
  146. lib/utils/Price.php +3 -1
  147. main.php +1 -1
  148. readme.txt +2 -2
backend/Backend.php CHANGED
@@ -27,6 +27,8 @@ abstract class Backend
27
Components\Notices\Nps::render();
28
// Collect stats notice.
29
Components\Notices\CollectStats::render();
30
}
31
// Let add-ons render admin notices.
32
Lib\Proxy\Shared::renderAdminNotices( $bookly_page );
@@ -50,7 +52,8 @@ abstract class Backend
50
global $current_user, $submenu;
51
52
$is_staff = Lib\Entities\Staff::query()->where( 'wp_user_id', $current_user->ID )->count() > 0;
53
- if ( $current_user->has_cap( 'administrator' ) || $current_user->has_cap( 'manage_bookly_appointments' ) || $is_staff ) {
54
$dynamic_position = '80.0000001' . mt_rand( 1, 1000 ); // position always is under `Settings`
55
$badge_number = Modules\Messages\Page::getMessagesCount() +
56
Modules\Shop\Page::getNotSeenCount() +
@@ -79,20 +82,20 @@ abstract class Backend
79
$appearance = __( 'Appearance', 'bookly' );
80
$settings = __( 'Settings', 'bookly' );
81
82
- add_submenu_page( 'bookly-menu', $dashboard, $dashboard, 'manage_options',
83
Modules\Dashboard\Page::pageSlug(), function () { Modules\Dashboard\Page::render(); } );
84
add_submenu_page( 'bookly-menu', $calendar, $calendar, 'read',
85
Modules\Calendar\Page::pageSlug(), function () { Modules\Calendar\Page::render(); } );
86
- if ( $current_user->has_cap( 'manage_options' ) || $current_user->has_cap( 'manage_bookly_appointments' ) ) {
87
add_submenu_page( 'bookly-menu', $appointments, $appointments, 'read',
88
Modules\Appointments\Page::pageSlug(), function () { Modules\Appointments\Page::render(); } );
89
}
90
Lib\Proxy\Locations::addBooklyMenuItem();
91
- if ( $current_user->has_cap( 'manage_options' ) || $current_user->has_cap( 'manage_bookly_appointments' ) ) {
92
Lib\Proxy\Packages::addBooklyMenuItem();
93
}
94
- if ( $current_user->has_cap( 'administrator' ) ) {
95
- add_submenu_page( 'bookly-menu', $staff_members, $staff_members, 'manage_options',
96
Modules\Staff\Page::pageSlug(), function () { Modules\Staff\Page::render(); } );
97
} elseif ( $is_staff ) {
98
if ( get_option( 'bookly_gen_allow_staff_edit_profile' ) == 1 ) {
@@ -100,33 +103,33 @@ abstract class Backend
100
Modules\Staff\Page::pageSlug(), function () { Modules\Staff\Page::render(); } );
101
}
102
}
103
- add_submenu_page( 'bookly-menu', $services, $services, 'manage_options',
104
Modules\Services\Page::pageSlug(), function () { Modules\Services\Page::render(); } );
105
Lib\Proxy\Taxes::addBooklyMenuItem();
106
- if ( $current_user->has_cap( 'manage_options' ) || $current_user->has_cap( 'manage_bookly_appointments' ) ) {
107
add_submenu_page( 'bookly-menu', $customers, $customers, 'read',
108
Modules\Customers\Page::pageSlug(), function () { Modules\Customers\Page::render(); } );
109
}
110
Lib\Proxy\CustomerInformation::addBooklyMenuItem();
111
Lib\Proxy\CustomerGroups::addBooklyMenuItem();
112
- add_submenu_page( 'bookly-menu', $notifications, $notifications, 'manage_options',
113
Modules\Notifications\Page::pageSlug(), function () { Modules\Notifications\Page::render(); } );
114
Modules\Sms\Page::addBooklyMenuItem();
115
- if ( $current_user->has_cap( 'manage_options' ) || $current_user->has_cap( 'manage_bookly_appointments' ) ) {
116
add_submenu_page( 'bookly-menu', $payments, $payments, 'read',
117
Modules\Payments\Page::pageSlug(), function () { Modules\Payments\Page::render(); } );
118
}
119
- add_submenu_page( 'bookly-menu', $appearance, $appearance, 'manage_options',
120
Modules\Appearance\Page::pageSlug(), function () { Modules\Appearance\Page::render(); } );
121
Lib\Proxy\Coupons::addBooklyMenuItem();
122
Lib\Proxy\CustomFields::addBooklyMenuItem();
123
- add_submenu_page( 'bookly-menu', $settings, $settings, 'manage_options',
124
Modules\Settings\Page::pageSlug(), function () { Modules\Settings\Page::render(); } );
125
Modules\Messages\Page::addBooklyMenuItem();
126
Modules\Shop\Page::addBooklyMenuItem();
127
128
if ( isset ( $_GET['page'] ) && $_GET['page'] == 'bookly-debug' ) {
129
- add_submenu_page( 'bookly-menu', 'Debug', 'Debug', 'manage_options',
130
Modules\Debug\Page::pageSlug(), function () { Modules\Debug\Page::render(); } );
131
}
132
if ( ! Lib\Config::proActive() ) {
27
Components\Notices\Nps::render();
28
// Collect stats notice.
29
Components\Notices\CollectStats::render();
30
+ // Show Powered by Bookly notice.
31
+ Components\Notices\PoweredBy::render();
32
}
33
// Let add-ons render admin notices.
34
Lib\Proxy\Shared::renderAdminNotices( $bookly_page );
52
global $current_user, $submenu;
53
54
$is_staff = Lib\Entities\Staff::query()->where( 'wp_user_id', $current_user->ID )->count() > 0;
55
+ $required_capability = Lib\Utils\Common::getRequiredCapability();
56
+ if ( $current_user->has_cap( $required_capability ) || $current_user->has_cap( 'manage_bookly_appointments' ) || $is_staff ) {
57
$dynamic_position = '80.0000001' . mt_rand( 1, 1000 ); // position always is under `Settings`
58
$badge_number = Modules\Messages\Page::getMessagesCount() +
59
Modules\Shop\Page::getNotSeenCount() +
82
$appearance = __( 'Appearance', 'bookly' );
83
$settings = __( 'Settings', 'bookly' );
84
85
+ add_submenu_page( 'bookly-menu', $dashboard, $dashboard, $required_capability,
86
Modules\Dashboard\Page::pageSlug(), function () { Modules\Dashboard\Page::render(); } );
87
add_submenu_page( 'bookly-menu', $calendar, $calendar, 'read',
88
Modules\Calendar\Page::pageSlug(), function () { Modules\Calendar\Page::render(); } );
89
+ if ( $current_user->has_cap( $required_capability ) || $current_user->has_cap( 'manage_bookly_appointments' ) ) {
90
add_submenu_page( 'bookly-menu', $appointments, $appointments, 'read',
91
Modules\Appointments\Page::pageSlug(), function () { Modules\Appointments\Page::render(); } );
92
}
93
Lib\Proxy\Locations::addBooklyMenuItem();
94
+ if ( $current_user->has_cap( $required_capability ) || $current_user->has_cap( 'manage_bookly_appointments' ) ) {
95
Lib\Proxy\Packages::addBooklyMenuItem();
96
}
97
+ if ( $current_user->has_cap( $required_capability ) ) {
98
+ add_submenu_page( 'bookly-menu', $staff_members, $staff_members, $required_capability,
99
Modules\Staff\Page::pageSlug(), function () { Modules\Staff\Page::render(); } );
100
} elseif ( $is_staff ) {
101
if ( get_option( 'bookly_gen_allow_staff_edit_profile' ) == 1 ) {
103
Modules\Staff\Page::pageSlug(), function () { Modules\Staff\Page::render(); } );
104
}
105
}
106
+ add_submenu_page( 'bookly-menu', $services, $services, $required_capability,
107
Modules\Services\Page::pageSlug(), function () { Modules\Services\Page::render(); } );
108
Lib\Proxy\Taxes::addBooklyMenuItem();
109
+ if ( $current_user->has_cap( $required_capability ) || $current_user->has_cap( 'manage_bookly_appointments' ) ) {
110
add_submenu_page( 'bookly-menu', $customers, $customers, 'read',
111
Modules\Customers\Page::pageSlug(), function () { Modules\Customers\Page::render(); } );
112
}
113
Lib\Proxy\CustomerInformation::addBooklyMenuItem();
114
Lib\Proxy\CustomerGroups::addBooklyMenuItem();
115
+ add_submenu_page( 'bookly-menu', $notifications, $notifications, $required_capability,
116
Modules\Notifications\Page::pageSlug(), function () { Modules\Notifications\Page::render(); } );
117
Modules\Sms\Page::addBooklyMenuItem();
118
+ if ( $current_user->has_cap( $required_capability ) || $current_user->has_cap( 'manage_bookly_appointments' ) ) {
119
add_submenu_page( 'bookly-menu', $payments, $payments, 'read',
120
Modules\Payments\Page::pageSlug(), function () { Modules\Payments\Page::render(); } );
121
}
122
+ add_submenu_page( 'bookly-menu', $appearance, $appearance, $required_capability,
123
Modules\Appearance\Page::pageSlug(), function () { Modules\Appearance\Page::render(); } );
124
Lib\Proxy\Coupons::addBooklyMenuItem();
125
Lib\Proxy\CustomFields::addBooklyMenuItem();
126
+ add_submenu_page( 'bookly-menu', $settings, $settings, $required_capability,
127
Modules\Settings\Page::pageSlug(), function () { Modules\Settings\Page::render(); } );
128
Modules\Messages\Page::addBooklyMenuItem();
129
Modules\Shop\Page::addBooklyMenuItem();
130
131
if ( isset ( $_GET['page'] ) && $_GET['page'] == 'bookly-debug' ) {
132
+ add_submenu_page( 'bookly-menu', 'Debug', 'Debug', $required_capability,
133
Modules\Debug\Page::pageSlug(), function () { Modules\Debug\Page::render(); } );
134
}
135
if ( ! Lib\Config::proActive() ) {
backend/components/dashboard/appointments/Ajax.php CHANGED
@@ -49,6 +49,8 @@ class Ajax extends Lib\Base\Ajax
49
->whereBetween( 'ca.created', $start->format( 'Y-m-d' ), $end->format( 'Y-m-d' ) )
50
->groupBy( 'DATE(ca.created), p.id, ca.status' )
51
->fetchArray();
52
// Consider payment for all appointments only 1 time
53
$payment_ids = array();
54
foreach ( $records as $record ) {
@@ -63,6 +65,9 @@ class Ajax extends Lib\Base\Ajax
63
}
64
if ( array_key_exists( $status, $data['totals'] ) ) {
65
$data['totals'][ $status ] += $quantity;
66
}
67
$data['totals']['total'] += $quantity;
68
$data['totals']['revenue'] += $revenue;
49
->whereBetween( 'ca.created', $start->format( 'Y-m-d' ), $end->format( 'Y-m-d' ) )
50
->groupBy( 'DATE(ca.created), p.id, ca.status' )
51
->fetchArray();
52
+
53
+ $custom_statuses = (array) Lib\Proxy\CustomStatuses::getAll();
54
// Consider payment for all appointments only 1 time
55
$payment_ids = array();
56
foreach ( $records as $record ) {
65
}
66
if ( array_key_exists( $status, $data['totals'] ) ) {
67
$data['totals'][ $status ] += $quantity;
68
+ } elseif ( isset ( $custom_statuses[ $status ] ) && $custom_statuses[ $status ]->getBusy() ) {
69
+ // Consider as APPROVED.
70
+ $data['totals']['approved'] += $quantity;
71
}
72
$data['totals']['total'] += $quantity;
73
$data['totals']['revenue'] += $revenue;
backend/components/dashboard/appointments/Widget.php CHANGED
@@ -14,7 +14,7 @@ class Widget extends Lib\Base\Component
14
/** @var \WP_User $current_user */
15
global $current_user;
16
17
- if ( $current_user && $current_user->has_cap( 'manage_options' ) ) {
18
$class = __CLASS__;
19
add_action( 'wp_dashboard_setup', function () use ( $class ) {
20
wp_add_dashboard_widget( strtolower( str_replace( '\\', '-', $class ) ), 'Bookly - ' . __( 'Appointments', 'bookly' ), array( $class, 'renderWidget' ) );
14
/** @var \WP_User $current_user */
15
global $current_user;
16
17
+ if ( $current_user && $current_user->has_cap( Lib\Utils\Common::getRequiredCapability() ) ) {
18
$class = __CLASS__;
19
add_action( 'wp_dashboard_setup', function () use ( $class ) {
20
wp_add_dashboard_widget( strtolower( str_replace( '\\', '-', $class ) ), 'Bookly - ' . __( 'Appointments', 'bookly' ), array( $class, 'renderWidget' ) );
backend/components/dialogs/appointment/delete/Ajax.php CHANGED
@@ -25,6 +25,8 @@ class Ajax extends Lib\Base\Ajax
25
$appointment_id = self::parameter( 'appointment_id' );
26
$reason = self::parameter( 'reason' );
27
28
if ( self::parameter( 'notify' ) ) {
29
$ca_list = Lib\Entities\CustomerAppointment::query()
30
->where( 'appointment_id', $appointment_id )
@@ -51,12 +53,12 @@ class Ajax extends Lib\Base\Ajax
51
$ca->setStatus( Lib\Entities\CustomerAppointment::STATUS_REJECTED );
52
}
53
}
54
- Lib\Notifications\Booking\Sender::sendForCA( $ca, null, array( 'cancellation_reason' => $reason ) );
55
}
56
}
57
58
Lib\Entities\Appointment::find( $appointment_id )->delete();
59
60
- wp_send_json_success();
61
}
62
}
25
$appointment_id = self::parameter( 'appointment_id' );
26
$reason = self::parameter( 'reason' );
27
28
+ $queue = array();
29
+
30
if ( self::parameter( 'notify' ) ) {
31
$ca_list = Lib\Entities\CustomerAppointment::query()
32
->where( 'appointment_id', $appointment_id )
53
$ca->setStatus( Lib\Entities\CustomerAppointment::STATUS_REJECTED );
54
}
55
}
56
+ Lib\Notifications\Booking\Sender::sendForCA( $ca, null, array( 'cancellation_reason' => $reason ), false, $queue );
57
}
58
}
59
60
Lib\Entities\Appointment::find( $appointment_id )->delete();
61
62
+ wp_send_json_success( compact( 'queue' ) );
63
}
64
}
backend/components/dialogs/appointment/edit/Ajax.php CHANGED
@@ -55,6 +55,7 @@ class Ajax extends Lib\Base\Ajax
55
),
56
'extras_consider_duration' => (int) Lib\Proxy\ServiceExtras::considerDuration( true ),
57
'extras_multiply_nop' => (int) get_option( 'bookly_service_extras_multiply_nop', 1 ),
58
);
59
60
// Staff list.
@@ -127,20 +128,25 @@ class Ajax extends Lib\Base\Ajax
127
128
/** @var Lib\Entities\Customer $customer */
129
// Customers list.
130
- foreach ( Lib\Entities\Customer::query()->sortBy( 'full_name' )->find() as $customer ) {
131
- $name = $customer->getFullName();
132
- if ( $customer->getEmail() != '' || $customer->getPhone() != '' ) {
133
- $name .= ' (' . trim( $customer->getEmail() . ', ' . $customer->getPhone(), ', ' ) . ')';
134
- }
135
136
- $result['customers'][] = array(
137
- 'id' => $customer->getId(),
138
- 'name' => $name,
139
- 'status' => Lib\Proxy\CustomerGroups::prepareDefaultAppointmentStatus( get_option( 'bookly_gen_default_appointment_status' ), $customer->getGroupId() ),
140
- 'custom_fields' => array(),
141
- 'timezone' => Lib\Proxy\Pro::getLastCustomerTimezone( $customer->getId() ),
142
- 'number_of_persons' => 1,
143
- );
144
}
145
146
// Time list.
@@ -252,8 +258,13 @@ class Ajax extends Lib\Base\Ajax
252
p.total AS payment_total,
253
p.type AS payment_type,
254
p.details AS payment_details,
255
- p.status AS payment_status' )
256
->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
257
->where( 'ca.appointment_id', $appointment->getId() )
258
->fetchArray();
259
foreach ( $customers as $customer ) {
@@ -285,6 +296,19 @@ class Ajax extends Lib\Base\Ajax
285
}
286
}
287
$custom_fields = (array) json_decode( $customer['custom_fields'], true );
288
$response['data']['customers'][] = array(
289
'id' => $customer['customer_id'],
290
'ca_id' => $customer['id'],
@@ -304,7 +328,8 @@ class Ajax extends Lib\Base\Ajax
304
'payment_id' => $customer['payment_id'],
305
'payment_type' => $customer['payment'] != $customer['payment_total'] ? 'partial' : 'full',
306
'payment_title' => $payment_title,
307
- 'status' => $customer['status'],
308
'timezone' => Lib\Proxy\Pro::getCustomerTimezone( $customer['time_zone'], $customer['time_zone_offset'] ),
309
);
310
}
@@ -332,7 +357,7 @@ class Ajax extends Lib\Base\Ajax
332
$repeat = json_decode( self::parameter( 'repeat', '[]' ), true );
333
$schedule = self::parameter( 'schedule', array() );
334
$customers = json_decode( self::parameter( 'customers', '[]' ), true );
335
- $notification = self::parameter( 'notification', 'no' );
336
$internal_note = self::parameter( 'internal_note' );
337
$created_from = self::parameter( 'created_from' );
338
@@ -409,6 +434,7 @@ class Ajax extends Lib\Base\Ajax
409
if ( ! isset ( $response['errors'] ) ) {
410
$duration = Lib\Slots\DatePoint::fromStr( $end_date )->diff( Lib\Slots\DatePoint::fromStr( $start_date ) );
411
if ( ! $skip_date && $repeat['enabled'] ) {
412
// Series.
413
if ( ! empty ( $schedule ) ) {
414
/** @var DataHolders\Order[] $orders */
@@ -433,7 +459,7 @@ class Ajax extends Lib\Base\Ajax
433
->save();
434
435
// Create order
436
- if ( $notification != 'no' ) {
437
$orders[ $customer['id'] ] = DataHolders\Order::create( Lib\Entities\Customer::find( $customer['id'] ) )
438
->addItem( 0, DataHolders\Series::create( $series ) );
439
}
@@ -486,10 +512,10 @@ class Ajax extends Lib\Base\Ajax
486
Lib\Proxy\Pro::syncGoogleCalendarEvent( $appointment );
487
// Outlook Calendar.
488
Lib\Proxy\OutlookCalendar::syncEvent( $appointment );
489
- // Waiting list.
490
- Lib\Proxy\WaitingList::handleParticipantsChange( $appointment );
491
492
- if ( $notification != 'no' ) {
493
foreach ( $ca_list as $ca ) {
494
$item = DataHolders\Simple::create( $ca )
495
->setService( $service )
@@ -503,13 +529,14 @@ class Ajax extends Lib\Base\Ajax
503
Proxy\RecurringAppointments::createBackendPayment( $series, $customer );
504
}
505
}
506
- if ( $notification != 'no' ) {
507
foreach ( $orders as $order ) {
508
- Lib\Notifications\Booking\Sender::sendForOrder( $order, array(), $notification == 'all' );
509
}
510
}
511
}
512
$response['success'] = true;
513
$response['data'] = array( 'staffId' => $staff_id ); // make FullCalendar refetch events
514
} else {
515
// Single appointment.
@@ -546,28 +573,31 @@ class Ajax extends Lib\Base\Ajax
546
Lib\Proxy\Pro::syncGoogleCalendarEvent( $appointment );
547
// Outlook Calendar.
548
Lib\Proxy\OutlookCalendar::syncEvent( $appointment );
549
- // Waiting list.
550
- Lib\Proxy\WaitingList::handleParticipantsChange( $appointment );
551
552
// Send notifications.
553
- if ( $notification == 'changed_status' ) {
554
- foreach ( $ca_status_changed as $ca ) {
555
- Lib\Notifications\Booking\Sender::sendForCA( $ca, $appointment );
556
- }
557
- } elseif ( $notification == 'all' ) {
558
$ca_list = $appointment->getCustomerAppointments( true );
559
foreach ( $ca_status_changed as $ca ) {
560
- // The value "just_created" was initialized for the objects of this array
561
- Lib\Notifications\Booking\Sender::sendForCA( $ca, $appointment, array(), true );
562
unset( $ca_list[ $ca->getId() ] );
563
}
564
foreach ( $ca_list as $ca ) {
565
- Lib\Notifications\Booking\Sender::sendForCA( $ca, $appointment, array(), true );
566
}
567
}
568
569
$response['success'] = true;
570
$response['data'] = self::_getAppointmentForFC( $staff_id, $appointment->getId() );
571
} else {
572
$response['errors'] = array( 'db' => __( 'Could not save appointment in database.', 'bookly' ) );
573
}
@@ -825,10 +855,12 @@ class Ajax extends Lib\Base\Ajax
825
// Check customers for appointments limit
826
foreach ( $customers as $index => $customer ) {
827
if ( $service->appointmentsLimitReached( $customer['id'], array( $start_date ) ) ) {
828
- $customer_error = Lib\Entities\Customer::find( $customer['id'] );
829
$result['customers_appointments_limit'][] = sprintf( __( '%s has reached the limit of bookings for this service', 'bookly' ), $customer_error->getFullName() );
830
}
831
}
832
}
833
}
834
55
),
56
'extras_consider_duration' => (int) Lib\Proxy\ServiceExtras::considerDuration( true ),
57
'extras_multiply_nop' => (int) get_option( 'bookly_service_extras_multiply_nop', 1 ),
58
+ 'customer_gr_def_app_status' => Lib\Proxy\CustomerGroups::prepareDefaultAppointmentStatuses( array( 0 => get_option( 'bookly_gen_default_appointment_status' ) ) ),
59
);
60
61
// Staff list.
128
129
/** @var Lib\Entities\Customer $customer */
130
// Customers list.
131
+ $customers_count = Lib\Entities\Customer::query( 'c' )->count();
132
+ if ( $customers_count < Lib\Entities\Customer::REMOTE_LIMIT ) {
133
+ foreach ( Lib\Entities\Customer::query()->sortBy( 'full_name' )->find() as $customer ) {
134
+ $name = $customer->getFullName();
135
+ if ( $customer->getEmail() != '' || $customer->getPhone() != '' ) {
136
+ $name .= ' (' . trim( $customer->getEmail() . ', ' . $customer->getPhone(), ', ' ) . ')';
137
+ }
138
139
+ $result['customers'][] = array(
140
+ 'id' => $customer->getId(),
141
+ 'name' => $name,
142
+ 'group_id' => $customer->getGroupId(),
143
+ 'custom_fields' => array(),
144
+ 'timezone' => Lib\Proxy\Pro::getLastCustomerTimezone( $customer->getId() ),
145
+ 'number_of_persons' => 1,
146
+ );
147
+ }
148
+ } else {
149
+ $result['customers'] = false;
150
}
151
152
// Time list.
258
p.total AS payment_total,
259
p.type AS payment_type,
260
p.details AS payment_details,
261
+ p.status AS payment_status,
262
+ c.full_name,
263
+ c.email,
264
+ c.phone,
265
+ c.group_id')
266
->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
267
+ ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
268
->where( 'ca.appointment_id', $appointment->getId() )
269
->fetchArray();
270
foreach ( $customers as $customer ) {
296
}
297
}
298
$custom_fields = (array) json_decode( $customer['custom_fields'], true );
299
+ $name = $customer['full_name'];
300
+ if ( $customer['email'] != '' || $customer['phone'] != '' ) {
301
+ $name .= ' (' . trim( $customer['email'] . ', ' . $customer['phone'], ', ' ) . ')';
302
+ }
303
+ $response['data']['customers_data'][] = array(
304
+ 'id' => $customer['customer_id'],
305
+ 'name' => $name,
306
+ 'group_id' => $customer['group_id'],
307
+ 'status' => $customer['status'],
308
+ 'custom_fields' => array(),
309
+ 'timezone' => Lib\Proxy\Pro::getLastCustomerTimezone( $customer['customer_id'] ),
310
+ 'number_of_persons' => 1,
311
+ );
312
$response['data']['customers'][] = array(
313
'id' => $customer['customer_id'],
314
'ca_id' => $customer['id'],
328
'payment_id' => $customer['payment_id'],
329
'payment_type' => $customer['payment'] != $customer['payment_total'] ? 'partial' : 'full',
330
'payment_title' => $payment_title,
331
+ 'group_id' => $customer['group_id'],
332
+ 'status' => $customer['status'],
333
'timezone' => Lib\Proxy\Pro::getCustomerTimezone( $customer['time_zone'], $customer['time_zone_offset'] ),
334
);
335
}
357
$repeat = json_decode( self::parameter( 'repeat', '[]' ), true );
358
$schedule = self::parameter( 'schedule', array() );
359
$customers = json_decode( self::parameter( 'customers', '[]' ), true );
360
+ $notification = self::parameter( 'notification', false );
361
$internal_note = self::parameter( 'internal_note' );
362
$created_from = self::parameter( 'created_from' );
363
434
if ( ! isset ( $response['errors'] ) ) {
435
$duration = Lib\Slots\DatePoint::fromStr( $end_date )->diff( Lib\Slots\DatePoint::fromStr( $start_date ) );
436
if ( ! $skip_date && $repeat['enabled'] ) {
437
+ $queue = array();
438
// Series.
439
if ( ! empty ( $schedule ) ) {
440
/** @var DataHolders\Order[] $orders */
459
->save();
460
461
// Create order
462
+ if ( $notification ) {
463
$orders[ $customer['id'] ] = DataHolders\Order::create( Lib\Entities\Customer::find( $customer['id'] ) )
464
->addItem( 0, DataHolders\Series::create( $series ) );
465
}
512
Lib\Proxy\Pro::syncGoogleCalendarEvent( $appointment );
513
// Outlook Calendar.
514
Lib\Proxy\OutlookCalendar::syncEvent( $appointment );
515
516
+ if ( $notification ) {
517
+ // Waiting list.
518
+ Lib\Proxy\WaitingList::handleParticipantsChange( $queue, $appointment );
519
foreach ( $ca_list as $ca ) {
520
$item = DataHolders\Simple::create( $ca )
521
->setService( $service )
529
Proxy\RecurringAppointments::createBackendPayment( $series, $customer );
530
}
531
}
532
+ if ( $notification ) {
533
foreach ( $orders as $order ) {
534
+ Lib\Notifications\Booking\Sender::sendForOrder( $order, array(), $notification == 'all', $queue );
535
}
536
}
537
}
538
$response['success'] = true;
539
+ $response['queue'] = array( 'all' => $queue, 'changed_status' => array() );
540
$response['data'] = array( 'staffId' => $staff_id ); // make FullCalendar refetch events
541
} else {
542
// Single appointment.
573
Lib\Proxy\Pro::syncGoogleCalendarEvent( $appointment );
574
// Outlook Calendar.
575
Lib\Proxy\OutlookCalendar::syncEvent( $appointment );
576
+
577
+ $queue_changed_status = array();
578
+ $queue = array();
579
580
// Send notifications.
581
+ if ( $notification ) {
582
+ // Waiting list.
583
+ $queue = Lib\Proxy\WaitingList::handleParticipantsChange( $queue, $appointment );
584
+
585
$ca_list = $appointment->getCustomerAppointments( true );
586
foreach ( $ca_status_changed as $ca ) {
587
+ if ( $appointment_id ) {
588
+ Lib\Notifications\Booking\Sender::sendForCA( $ca, $appointment, array(), false, $queue_changed_status );
589
+ }
590
+ Lib\Notifications\Booking\Sender::sendForCA( $ca, $appointment, array(), true, $queue );
591
unset( $ca_list[ $ca->getId() ] );
592
}
593
foreach ( $ca_list as $ca ) {
594
+ Lib\Notifications\Booking\Sender::sendForCA( $ca, $appointment, array(), true, $queue );
595
}
596
}
597
598
$response['success'] = true;
599
$response['data'] = self::_getAppointmentForFC( $staff_id, $appointment->getId() );
600
+ $response['queue'] = array( 'all' => $queue, 'changed_status' => $queue_changed_status );
601
} else {
602
$response['errors'] = array( 'db' => __( 'Could not save appointment in database.', 'bookly' ) );
603
}
855
// Check customers for appointments limit
856
foreach ( $customers as $index => $customer ) {
857
if ( $service->appointmentsLimitReached( $customer['id'], array( $start_date ) ) ) {
858
+ $customer_error = Lib\Entities\Customer::find( $customer['id'] );
859
$result['customers_appointments_limit'][] = sprintf( __( '%s has reached the limit of bookings for this service', 'bookly' ), $customer_error->getFullName() );
860
}
861
}
862
+
863
+ $result['customers_appointments_limit'] = array_unique( $result['customers_appointments_limit'] );
864
}
865
}
866
backend/components/dialogs/appointment/edit/Dialog.php CHANGED
@@ -41,10 +41,12 @@ class Dialog extends Lib\Base\Component
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' ),
45
'title' => array(
46
- 'edit_appointment' => __( 'Edit appointment', 'bookly' ),
47
- 'new_appointment' => __( 'New appointment', 'bookly' ),
48
),
49
) );
50
41
'datePicker' => Lib\Utils\DateTime::datePickerOptions(),
42
'cf_per_service' => (int) Lib\Config::customFieldsPerService(),
43
'no_result_found' => __( 'No result found', 'bookly' ),
44
+ 'searching' => __( 'Searching', 'bookly' ),
45
'staff_any' => get_option( 'bookly_l10n_option_employee' ),
46
'title' => array(
47
+ 'edit_appointment' => __( 'Edit appointment', 'bookly' ),
48
+ 'new_appointment' => __( 'New appointment', 'bookly' ),
49
+ 'send_notifications' => __( 'Send notifications', 'bookly' ),
50
),
51
) );
52
backend/components/dialogs/appointment/edit/resources/js/ng-appointment.js CHANGED
@@ -27,6 +27,7 @@
27
id : null,
28
staff : null,
29
staff_any : null,
30
service : null,
31
custom_service_name : null,
32
custom_service_price: null,
@@ -53,7 +54,9 @@
53
customers : [],
54
notification : null,
55
series_id : null,
56
- expand_customers_list : false
57
},
58
l10n : {
59
staff_any: BooklyL10nAppDialog.staff_any
@@ -72,8 +75,87 @@
72
ds.form.staff = data.staff[0];
73
}
74
75
ds.form.start_time = data.start_time[0];
76
ds.form.end_time = data.end_time[1];
77
deferred.resolve();
78
},
79
'json'
@@ -145,27 +227,31 @@
145
},
146
resetCustomers : function() {
147
ds.data.customers.forEach(function(customer) {
148
- customer.custom_fields = [];
149
- customer.extras = [];
150
- customer.extras_consider_duration = ds.data.extras_consider_duration;
151
- customer.extras_multiply_nop = ds.data.extras_multiply_nop;
152
- customer.number_of_persons = 1;
153
- customer.notes = null;
154
- customer.collaborative_token = null;
155
- customer.collaborative_service = null;
156
- customer.compound_token = null;
157
- customer.compound_service = null;
158
- customer.payment_id = null;
159
- customer.payment_type = null;
160
- customer.payment_title = null;
161
- customer.payment_create = false;
162
- customer.payment_price = null;
163
- customer.payment_tax = null;
164
- customer.package_id = null;
165
- customer.series_id = null;
166
- customer.ca_id = null;
167
});
168
},
169
getDataForStartTime : function() {
170
var result = ds.data.start_time.slice();
171
if (
@@ -374,7 +460,9 @@
374
},
375
customers : [],
376
internal_note : null,
377
- expand_customers_list : false
378
});
379
$scope.errors = {};
380
dataSource.setEndTimeBasedOnService();
@@ -405,6 +493,9 @@
405
staff = $scope.dataSource.findStaff(response.data.staff_id);
406
$scope.dataSource.data.app_start_time = response.data.start_time;
407
$scope.dataSource.data.app_end_time = response.data.end_time;
408
jQuery.extend($scope.form, {
409
screen : 'main',
410
id : appointment_id,
@@ -613,19 +704,7 @@
613
$scope.onRepeatChange();
614
};
615
616
- $scope.onCustomersChange = function(old_customers, old_nop) {
617
- if (dataSource.form.service && dataSource.form.customers.length > old_customers.length) {
618
- var ids = jQuery.map(old_customers, function(customer) {
619
- return customer.id;
620
- });
621
- var nop = dataSource.form.service.capacity_min - old_nop;
622
- dataSource.form.customers.some(function (item) {
623
- if (jQuery.inArray(item.id, ids) == -1) {
624
- item.number_of_persons = nop > 0 ? nop : 1;
625
- return true;
626
- }
627
- });
628
- }
629
$scope.errors.customers_appointments_limit = [];
630
checkAppointmentErrors();
631
};
@@ -635,6 +714,9 @@
635
};
636
637
$scope.processForm = function() {
638
$scope.loading = true;
639
640
$scope.errors = {};
@@ -700,14 +782,20 @@
700
created_from : typeof BooklySCCalendarL10n !== 'undefined' ? 'staff-cabinet' : 'backend'
701
},
702
function (response) {
703
- $scope.$apply(function($scope) {
704
if (response.success) {
705
if (callback) {
706
// Call callback.
707
callback(response.data);
708
}
709
- // Close the dialog.
710
- $element.children().modal('hide');
711
} else {
712
$scope.errors = response.errors;
713
}
@@ -720,9 +808,65 @@
720
721
// On 'Cancel' button click.
722
$scope.closeDialog = function () {
723
// Close the dialog.
724
$element.children().modal('hide');
725
};
726
727
$scope.statusToString = function (status) {
728
return dataSource.data.status.items[status];
@@ -956,10 +1100,11 @@
956
* Payment Details *
957
**************************************************************************************************************/
958
959
- $scope.attachPaymentModal = function (customer) {
960
var $dialog = jQuery('#bookly-payment-attach-modal');
961
$scope.form.attach = {
962
customer_id : customer.id,
963
payment_method: 'create',
964
payment_price : null,
965
payment_tax : null,
@@ -970,15 +1115,15 @@
970
});
971
};
972
973
- $scope.attachPayment = function (attach_method, price, tax, payment_id, customer_id) {
974
var $dialog = jQuery('#bookly-payment-details-modal');
975
if (attach_method == 'search') {
976
- $dialog.data('payment_id', payment_id).data('payment_bind', true).data('customer_id', customer_id).modal({show: true}).on('hidden.bs.modal', function () {
977
jQuery('body').addClass('modal-open');
978
});
979
} else {
980
- jQuery.each($scope.dataSource.data.customers, function (key, item) {
981
- if (item.id == customer_id) {
982
item.payment_create = true;
983
item.payment_price = price;
984
item.payment_tax = tax;
@@ -988,11 +1133,11 @@
988
}
989
};
990
991
- $scope.callbackPayment = function (payment_action, payment_id, payment_title, customer_id, payment_type) {
992
if (payment_action == 'bind') {
993
// Bind payment
994
- jQuery.each($scope.dataSource.data.customers, function (key, item) {
995
- if (item.id == customer_id) {
996
item.payment_id = payment_id;
997
item.payment_type = payment_type;
998
item.payment_title = payment_title;
@@ -1000,7 +1145,7 @@
1000
});
1001
} else {
1002
// Complete payment
1003
- jQuery.each($scope.dataSource.data.customers, function (key, item) {
1004
if (item.payment_id == payment_id) {
1005
item.payment_type = 'full';
1006
item.payment_title = payment_title;
@@ -1329,15 +1474,6 @@
1329
return input;
1330
};
1331
});
1332
-
1333
- jQuery('#bookly-select2').select2({
1334
- width: '100%',
1335
- theme: 'bootstrap',
1336
- allowClear: false,
1337
- language : {
1338
- noResults: function() { return BooklyL10nAppDialog.no_result_found; }
1339
- }
1340
- });
1341
})();
1342
1343
/**
@@ -1347,16 +1483,19 @@
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) {
1356
$scope.loading = true;
1357
- $dialog
1358
- .find('.modal-title')
1359
- .text(appointment_id ? BooklyL10nAppDialog.title.edit_appointment : BooklyL10nAppDialog.title.new_appointment);
1360
// Populate data source.
1361
$scope.dataSource.loadData().then(function() {
1362
$scope.loading = false;
27
id : null,
28
staff : null,
29
staff_any : null,
30
+ customer_gr_def_app_status: [],
31
service : null,
32
custom_service_name : null,
33
custom_service_price: null,
54
customers : [],
55
notification : null,
56
series_id : null,
57
+ expand_customers_list : false,
58
+ queue_type : false,
59
+ queue : [],
60
},
61
l10n : {
62
staff_any: BooklyL10nAppDialog.staff_any
75
ds.form.staff = data.staff[0];
76
}
77
78
+ if (data.customers === false) {
79
+ ds.data.customers = [];
80
+ ds.data.customers_remote = true;
81
+ // Init select2 remote.
82
+ jQuery('#bookly-appointment-dialog-select2').select2({
83
+ width : '100%',
84
+ theme : 'bootstrap',
85
+ allowClear: false,
86
+ language : {
87
+ noResults: function () {
88
+ return BooklyL10nAppDialog.no_result_found;
89
+ },
90
+ searching: function () {
91
+ return BooklyL10nAppDialog.searching;
92
+ }
93
+ },
94
+ ajax : {
95
+ url : ajaxurl,
96
+ dataType : 'json',
97
+ delay : 250,
98
+ data : function (params) {
99
+ params.page = params.page || 1;
100
+ return {
101
+ action : 'bookly_get_customers_list',
102
+ filter : params.term,
103
+ page : params.page,
104
+ timezone : true,
105
+ csrf_token: BooklyL10nAppDialog.csrf_token
106
+ };
107
+ },
108
+ processResults: function (data) {
109
+ data.results.forEach(function (customer) {
110
+ if (!ds.findCustomer(customer.id)) {
111
+ ds.resetCustomer(customer);
112
+ ds.data.customers.push(customer);
113
+ }
114
+ });
115
+ return {
116
+ results : data.results.map(function (item) {
117
+ return {id: item.id, text: item.name}
118
+ }),
119
+ pagination: data.pagination
120
+ };
121
+ }
122
+ },
123
+ }).on("select2:selecting", function (data) {
124
+ data.preventDefault();
125
+ var $scope = angular.element(jQuery('#bookly-appointment-dialog')).scope();
126
+ $scope.$apply(function ($scope) {
127
+ let clone = {};
128
+ angular.copy($scope.dataSource.data.customers.find(x => x.id === data.params.args.data.id), clone);
129
+ $scope.dataSource.resetCustomer(clone);
130
+ $scope.form.customers.push(clone);
131
+ $scope.onCustomersChange();
132
+ });
133
+ jQuery(this).select2('close');
134
+ });
135
+ } else {
136
+ jQuery('#bookly-appointment-dialog-select2').select2({
137
+ width : '100%',
138
+ theme : 'bootstrap',
139
+ allowClear: false,
140
+ language : {
141
+ noResults: function () {
142
+ return BooklyL10nAppDialog.no_result_found;
143
+ }
144
+ }
145
+ }).on('select2:select select2:unselect', function (data) {
146
+ var $scope = angular.element(jQuery('#bookly-appointment-dialog')).scope();
147
+ $scope.$apply(function ($scope) {
148
+ let clone = {};
149
+ angular.copy($scope.dataSource.data.customers.find(x => x.id === data.params.data.id), clone);
150
+ $scope.dataSource.resetCustomer(clone);
151
+ $scope.form.customers.push(clone);
152
+ $scope.onCustomersChange();
153
+ });
154
+ });
155
+ }
156
ds.form.start_time = data.start_time[0];
157
ds.form.end_time = data.end_time[1];
158
+ ds.form.customer_gr_def_app_status = data.customer_gr_def_app_status;
159
deferred.resolve();
160
},
161
'json'
227
},
228
resetCustomers : function() {
229
ds.data.customers.forEach(function(customer) {
230
+ ds.resetCustomer(customer);
231
});
232
},
233
+ resetCustomer: function(customer) {
234
+ customer.custom_fields = [];
235
+ customer.extras = [];
236
+ customer.extras_consider_duration = ds.data.extras_consider_duration;
237
+ customer.extras_multiply_nop = ds.data.extras_multiply_nop;
238
+ customer.number_of_persons = !ds.form.service || ds.getTotalNumberOfNotCancelledPersons() ? 1 : ds.form.service.capacity_min;
239
+ customer.notes = null;
240
+ customer.collaborative_token = null;
241
+ customer.collaborative_service = null;
242
+ customer.compound_token = null;
243
+ customer.compound_service = null;
244
+ customer.payment_id = null;
245
+ customer.payment_type = null;
246
+ customer.payment_title = null;
247
+ customer.payment_create = false;
248
+ customer.payment_price = null;
249
+ customer.payment_tax = null;
250
+ customer.package_id = null;
251
+ customer.series_id = null;
252
+ customer.ca_id = null;
253
+ customer.status = ds.form.customer_gr_def_app_status[parseInt(customer.group_id||0)];
254
+ },
255
getDataForStartTime : function() {
256
var result = ds.data.start_time.slice();
257
if (
460
},
461
customers : [],
462
internal_note : null,
463
+ expand_customers_list : false,
464
+ queue_type : false,
465
+ queue : [],
466
});
467
$scope.errors = {};
468
dataSource.setEndTimeBasedOnService();
493
staff = $scope.dataSource.findStaff(response.data.staff_id);
494
$scope.dataSource.data.app_start_time = response.data.start_time;
495
$scope.dataSource.data.app_end_time = response.data.end_time;
496
+ if ($scope.dataSource.data.customers_remote) {
497
+ jQuery.extend($scope.dataSource.data.customers, response.data.customers_data);
498
+ }
499
jQuery.extend($scope.form, {
500
screen : 'main',
501
id : appointment_id,
704
$scope.onRepeatChange();
705
};
706
707
+ $scope.onCustomersChange = function() {
708
$scope.errors.customers_appointments_limit = [];
709
checkAppointmentErrors();
710
};
714
};
715
716
$scope.processForm = function() {
717
+ if ($scope.form.screen === 'queue') {
718
+ return $scope.queueSend();
719
+ }
720
$scope.loading = true;
721
722
$scope.errors = {};
782
created_from : typeof BooklySCCalendarL10n !== 'undefined' ? 'staff-cabinet' : 'backend'
783
},
784
function (response) {
785
+ $scope.$apply(function ($scope) {
786
if (response.success) {
787
if (callback) {
788
// Call callback.
789
callback(response.data);
790
}
791
+ $scope.form.queue = response.queue;
792
+ if (response.queue.all.length || response.queue.changed_status.length) {
793
+ $scope.form.queue_type = $scope.form.queue.changed_status.length ? 'changed_status' : 'all'
794
+ $scope.form.screen = 'queue';
795
+ } else {
796
+ // Close the dialog.
797
+ $element.children().modal('hide');
798
+ }
799
} else {
800
$scope.errors = response.errors;
801
}
808
809
// On 'Cancel' button click.
810
$scope.closeDialog = function () {
811
+ if ($scope.form.screen === 'queue') {
812
+ var queue = [];
813
+ jQuery.each($scope.form.queue, function (type, value) {
814
+ jQuery.each(value, function (key, email) {
815
+ queue.push(email);
816
+ });
817
+ });
818
+ jQuery.post(
819
+ ajaxurl,
820
+ {
821
+ action : 'bookly_clear_attachments',
822
+ csrf_token: BooklyL10nAppDialog.csrf_token,
823
+ queue : queue
824
+ },
825
+ 'json'
826
+ );
827
+ }
828
// Close the dialog.
829
$element.children().modal('hide');
830
};
831
+ // On 'Cancel' button click in queue window.
832
+ $scope.queueSend = function () {
833
+ var ladda = Ladda.create(jQuery('button.bookly-js-queue-send').get(0));
834
+ ladda.start();
835
+ var queue = [];
836
+ jQuery.each($scope.form.queue[$scope.form.queue_type], function (key, email) {
837
+ if (email.checked == 1) {
838
+ queue.push(email);
839
+ }
840
+ });
841
+ var queue_full = [];
842
+ jQuery.each($scope.form.queue, function (type, value) {
843
+ jQuery.each(value, function (key, email) {
844
+ queue_full.push(email);
845
+ });
846
+ });
847
+ jQuery.post(
848
+ ajaxurl,
849
+ {
850
+ action : 'bookly_send_queue',
851
+ csrf_token: BooklyL10nAppDialog.csrf_token,
852
+ queue : queue,
853
+ queue_full: queue_full
854
+ },
855
+ function (response) {
856
+ ladda.stop();
857
+ $scope.$apply(function ($scope) {
858
+ if (response.success) {
859
+ // Close the dialog.
860
+ $element.children().modal('hide');
861
+ } else {
862
+ $scope.errors = response.errors;
863
+ }
864
+ $scope.loading = false;
865
+ });
866
+ },
867
+ 'json'
868
+ );
869
+ };
870
871
$scope.statusToString = function (status) {
872
return dataSource.data.status.items[status];
1100
* Payment Details *
1101
**************************************************************************************************************/
1102
1103
+ $scope.attachPaymentModal = function (customer, index ) {
1104
var $dialog = jQuery('#bookly-payment-attach-modal');
1105
$scope.form.attach = {
1106
customer_id : customer.id,
1107
+ customer_index: index,
1108
payment_method: 'create',
1109
payment_price : null,
1110
payment_tax : null,
1115
});
1116
};
1117
1118
+ $scope.attachPayment = function (attach_method, price, tax, payment_id, customer_id, customer_index) {
1119
var $dialog = jQuery('#bookly-payment-details-modal');
1120
if (attach_method == 'search') {
1121
+ $dialog.data('payment_id', payment_id).data('payment_bind', true).data('customer_id', customer_id).data('customer_index', customer_index).modal({show: true}).on('hidden.bs.modal', function () {
1122
jQuery('body').addClass('modal-open');
1123
});
1124
} else {
1125
+ jQuery.each($scope.dataSource.form.customers, function (key, item) {
1126
+ if (item.id == customer_id && key == customer_index) {
1127
item.payment_create = true;
1128
item.payment_price = price;
1129
item.payment_tax = tax;
1133
}
1134
};
1135
1136
+ $scope.callbackPayment = function (payment_action, payment_id, payment_title, customer_id, customer_index, payment_type) {
1137
if (payment_action == 'bind') {
1138
// Bind payment
1139
+ jQuery.each($scope.dataSource.form.customers, function (key, item) {
1140
+ if (item.id == customer_id && key == customer_index) {
1141
item.payment_id = payment_id;
1142
item.payment_type = payment_type;
1143
item.payment_title = payment_title;
1145
});
1146
} else {
1147
// Complete payment
1148
+ jQuery.each($scope.dataSource.form.customers, function (key, item) {
1149
if (item.payment_id == payment_id) {
1150
item.payment_type = 'full';
1151
item.payment_title = payment_title;
1474
return input;
1475
};
1476
});
1477
})();
1478
1479
/**
1483
* @param function callback
1484
*/
1485
var showAppointmentDialog = function (appointment_id, staff_id, start_date, callback) {
1486
+ if (jQuery.fn.tooltip.Constructor.VERSION !== undefined && jQuery.fn.tooltip.Constructor.VERSION.split('.')[0] === '4') {
1487
jQuery('#bookly-tbs .modal.fade').removeClass('fade');
1488
}
1489
var $dialog = jQuery('#bookly-appointment-dialog');
1490
var $scope = angular.element($dialog[0]).scope();
1491
$scope.$apply(function ($scope) {
1492
$scope.loading = true;
1493
+ $scope.form.titles = {
1494
+ new : BooklyL10nAppDialog.title.new_appointment,
1495
+ edit : BooklyL10nAppDialog.title.edit_appointment,
1496
+ queue: BooklyL10nAppDialog.title.send_notifications
1497
+ };
1498
+ $scope.form.title = appointment_id ? BooklyL10nAppDialog.title.edit_appointment : BooklyL10nAppDialog.title.new_appointment;
1499
// Populate data source.
1500
$scope.dataSource.loadData().then(function() {
1501
$scope.loading = false;
backend/components/dialogs/appointment/edit/templates/edit.php CHANGED
@@ -14,7 +14,7 @@ use Bookly\Lib\Entities\CustomerAppointment;
14
<form ng-submit=processForm()>
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
- <div class="modal-title h2"><?php esc_html_e( 'New appointment', 'bookly' ) ?></div>
18
</div>
19
<div ng-show=loading class="modal-body">
20
<div class="bookly-loading"></div>
@@ -203,10 +203,10 @@ use Bookly\Lib\Entities\CustomerAppointment;
203
<div <?php if ( ! Config::waitingListActive() ): ?>ng-show="!form.service || dataSource.getTotalNumberOfNotCancelledPersons() < form.service.capacity_max"<?php endif ?>>
204
<div class="form-group">
205
<div class="input-group">
206
- <select id="bookly-select2" multiple data-placeholder="<?php esc_attr_e( '-- Search customers --', 'bookly' ) ?>"
207
class="form-control"
208
- ng-model="form.customers" ng-options="c.name for c in dataSource.data.customers"
209
- ng-change="onCustomersChange({{form.customers}}, {{dataSource.getTotalNumberOfNotCancelledPersons()}})">
210
</select>
211
<span class="input-group-btn">
212
<a class="btn btn-success" ng-click="openNewCustomerDialog()">
@@ -224,26 +224,48 @@ use Bookly\Lib\Entities\CustomerAppointment;
224
</div>
225
226
<div class=form-group>
227
<label for="bookly-notification"><?php esc_html_e( 'Send notifications', 'bookly' ) ?></label>
228
<p class="help-block"><?php esc_html_e( 'If you have added a new customer to this appointment or changed the appointment status for an existing customer, and for these records you want the corresponding email or SMS notifications to be sent to their recipients, select the "Send if new or status changed" option before clicking Save. You can also send notifications as if all customers were added as new by selecting "Send as for new".', 'bookly' ) ?></p>
229
- <select class="form-control" style="margin-top: 0" ng-model=form.notification id="bookly-notification" ng-init="form.notification = '<?php echo get_user_meta( get_current_user_id(), 'bookly_appointment_form_send_notifications', true ) ?>' || 'no'" >
230
- <option value="no"><?php esc_html_e( 'Don\'t send', 'bookly' ) ?></option>
231
- <option value="changed_status"><?php esc_html_e( 'Send if new or status changed', 'bookly' ) ?></option>
232
- <option value="all"><?php esc_html_e( 'Send as for new', 'bookly' ) ?></option>
233
- </select>
234
</div>
235
-
236
- <div class=form-group>
237
- <label for="bookly-internal-note"><?php esc_html_e( 'Internal note', 'bookly' ) ?></label>
238
- <textarea class="form-control" ng-model=form.internal_note id="bookly-internal-note"></textarea>
239
</div>
240
</div>
241
<?php Proxy\RecurringAppointments::renderSchedule() ?>
242
<div class="modal-footer">
243
<div ng-hide=loading>
244
<?php Proxy\Shared::renderAppointmentDialogFooter() ?>
245
- <?php Buttons::renderSubmit( null, null, null, array( 'ng-hide' => 'form.repeat.enabled && !form.skip_date && form.screen == \'main\'', 'ng-disabled' => '!form.skip_date && form.repeat.enabled && schIsScheduleEmpty()', 'formnovalidate' => '' ) ) ?>
246
- <?php Buttons::renderCustom( null, 'btn-lg btn-default', __( 'Cancel', 'bookly' ), array( 'ng-click' => 'closeDialog()', 'data-dismiss' => 'modal' ) ) ?>
247
</div>
248
</div>
249
</form>
@@ -251,7 +273,7 @@ use Bookly\Lib\Entities\CustomerAppointment;
251
</div>
252
</div>
253
<div customer-dialog=createCustomer(customer)></div>
254
- <div payment-details-dialog="callbackPayment(payment_action, payment_id, payment_title, customer_id, payment_type)"></div>
255
256
<?php Dialogs\Appointment\CustomerDetails\Dialog::render() ?>
257
<?php AttachPaymentProxy\Pro::renderAttachPaymentDialog() ?>
14
<form ng-submit=processForm()>
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
+ <div class="modal-title h2">{{ form.screen == 'queue' ? form.titles.queue : form.title }}</div>
18
</div>
19
<div ng-show=loading class="modal-body">
20
<div class="bookly-loading"></div>
203
<div <?php if ( ! Config::waitingListActive() ): ?>ng-show="!form.service || dataSource.getTotalNumberOfNotCancelledPersons() < form.service.capacity_max"<?php endif ?>>
204
<div class="form-group">
205
<div class="input-group">
206
+ <select id="bookly-appointment-dialog-select2" multiple data-placeholder="<?php esc_attr_e( '-- Search customers --', 'bookly' ) ?>"
207
class="form-control"
208
+ >
209
+ <option ng-repeat="customer in dataSource.data.customers" value="{{customer.id}}">{{customer.name}}</option>
210
</select>
211
<span class="input-group-btn">
212
<a class="btn btn-success" ng-click="openNewCustomerDialog()">
224
</div>
225
226
<div class=form-group>
227
+ <label for="bookly-internal-note"><?php esc_html_e( 'Internal note', 'bookly' ) ?></label>
228
+ <textarea class="form-control" ng-model=form.internal_note id="bookly-internal-note"></textarea>
229
+ </div>
230
+ </div>
231
+ <div ng-hide="loading || form.screen != 'queue'" class="modal-body">
232
+ <div class="form-group" ng-hide="!form.queue.all.length || !form.queue.changed_status.length">
233
<label for="bookly-notification"><?php esc_html_e( 'Send notifications', 'bookly' ) ?></label>
234
<p class="help-block"><?php esc_html_e( 'If you have added a new customer to this appointment or changed the appointment status for an existing customer, and for these records you want the corresponding email or SMS notifications to be sent to their recipients, select the "Send if new or status changed" option before clicking Save. You can also send notifications as if all customers were added as new by selecting "Send as for new".', 'bookly' ) ?></p>
235
+ <div class="radio"><label><input type="radio" name="queue_type" value="changed_status" ng-model=form.queue_type><?php esc_html_e( 'Send if new or status changed', 'bookly' ) ?></label></div>
236
+ <div class="radio"><label><input type="radio" name="queue_type" value="all" ng-model=form.queue_type><?php esc_html_e( 'Send as for new', 'bookly' ) ?></label></div>
237
</div>
238
+ <div ng-repeat="(key, value) in form.queue.all">
239
+ <div class="checkbox bookly-margin-bottom-lg bookly-margin-top-remove" ng-hide="form.queue_type == 'changed_status'">
240
+ <label>
241
+ <input type=checkbox ng-model=value.checked ng-true-value="1" ng-false-value="0" ng-init="value.checked=1"/> <i class="fa fa-fw" ng-class="{'fa-sms':value.gateway == 'sms', 'fa-envelope':value.gateway != 'sms'}"></i> <b>{{value.data.name}}</b> ({{value.address}})<br/>
242
+ {{ value.name }}
243
+ </label>
244
+ </div>
245
+ </div>
246
+ <div ng-repeat="(key, value) in form.queue.changed_status">
247
+ <div class="checkbox bookly-margin-bottom-lg bookly-margin-top-remove" ng-hide="form.queue_type != 'changed_status'">
248
+ <label>
249
+ <input type=checkbox ng-model=value.checked ng-true-value="1" ng-false-value="0" ng-init="value.checked=1"/> <i class="fa fa-fw" ng-class="{'fa-sms':value.gateway == 'sms', 'fa-envelope':value.gateway != 'sms'}"></i> <b>{{value.data.name}}</b> ({{value.address}})<br/>
250
+ {{ value.name }}
251
+ </label>
252
+ </div>
253
</div>
254
</div>
255
<?php Proxy\RecurringAppointments::renderSchedule() ?>
256
+ <div ng-hide="loading || form.screen != 'main'" class="modal-body bookly-padding-top-remove" style="margin-top: -15px;">
257
+ <div class="checkbox bookly-margin-bottom-lg bookly-margin-top-remove">
258
+ <label>
259
+ <input type=checkbox ng-model=form.notification ng-true-value="1" ng-false-value="0" ng-init="form.notification=<?php echo get_user_meta( get_current_user_id(), 'bookly_appointment_form_send_notifications', true ) ?: 0 ?>"/><b><?php esc_html_e( 'Send notifications', 'bookly' ) ?></b>
260
+ </label>
261
+ </div>
262
+ </div>
263
<div class="modal-footer">
264
<div ng-hide=loading>
265
<?php Proxy\Shared::renderAppointmentDialogFooter() ?>
266
+ <?php Buttons::renderSubmit( null, null, null, array( 'ng-hide' => 'form.screen == \'queue\' || (form.repeat.enabled && !form.skip_date && form.screen == \'main\')', 'ng-disabled' => '!form.skip_date && form.repeat.enabled && schIsScheduleEmpty()', 'formnovalidate' => '' ) ) ?>
267
+ <?php Buttons::renderSubmit( null, 'bookly-js-queue-send', esc_html__( 'Send', 'bookly' ), array( 'ng-show' => 'form.screen == \'queue\'' ) ) ?>
268
+ <?php Buttons::renderCustom( null, 'btn-lg btn-default', esc_html__( 'Cancel', 'bookly' ), array( 'ng-click' => 'closeDialog()', 'data-dismiss' => 'modal' ) ) ?>
269
</div>
270
</div>
271
</form>
273
</div>
274
</div>
275
<div customer-dialog=createCustomer(customer)></div>
276
+ <div payment-details-dialog="callbackPayment(payment_action, payment_id, payment_title, customer_id, customer_index, payment_type)"></div>
277
278
<?php Dialogs\Appointment\CustomerDetails\Dialog::render() ?>
279
<?php AttachPaymentProxy\Pro::renderAttachPaymentDialog() ?>
backend/components/dialogs/customer/edit/resources/js/ng-customer.js CHANGED
@@ -68,10 +68,6 @@
68
// Init select2 for wp_users.
69
jQuery('#wp_user')
70
.val(null)
71
- .on('select2:unselecting', function(e) {
72
- e.preventDefault();
73
- jQuery(this).val(null).trigger('change');
74
- })
75
.select2({
76
width: '100%',
77
theme: 'bootstrap',
68
// Init select2 for wp_users.
69
jQuery('#wp_user')
70
.val(null)
71
.select2({
72
width: '100%',
73
theme: 'bootstrap',
backend/components/dialogs/notifications/templates/_types.php CHANGED
@@ -26,7 +26,7 @@ use Bookly\Lib\Config;
26
data-set="instantly"
27
data-recipients='["customer"]'
28
data-icon='<?php echo esc_attr( Notification::getIcon( Notification::TYPE_NEW_BOOKING_COMBINED ) ) ?>'
29
- data-attach='["invoice"]'><?php echo esc_attr( Notification::getTitle( Notification::TYPE_NEW_BOOKING_COMBINED ) ) ?></option>
30
<?php endif ?>
31
<option value="<?php echo Notification::TYPE_CUSTOMER_APPOINTMENT_STATUS_CHANGED ?>"
32
data-set="instantly"
26
data-set="instantly"
27
data-recipients='["customer"]'
28
data-icon='<?php echo esc_attr( Notification::getIcon( Notification::TYPE_NEW_BOOKING_COMBINED ) ) ?>'
29
+ data-attach='["ics","invoice"]'><?php echo esc_attr( Notification::getTitle( Notification::TYPE_NEW_BOOKING_COMBINED ) ) ?></option>
30
<?php endif ?>
31
<option value="<?php echo Notification::TYPE_CUSTOMER_APPOINTMENT_STATUS_CHANGED ?>"
32
data-set="instantly"
backend/components/dialogs/payment/Ajax.php CHANGED
@@ -102,4 +102,25 @@ class Ajax extends Lib\Base\Ajax
102
wp_send_json_success( array( 'payment_title' => $payment_title, 'payment_type' => $payment->getPaid() == $payment->getTotal() ? 'full' : 'partial' ) );
103
}
104
}
105
}
102
wp_send_json_success( array( 'payment_title' => $payment_title, 'payment_type' => $payment->getPaid() == $payment->getTotal() ? 'full' : 'partial' ) );
103
}
104
}
105
+
106
+ /**
107
+ * Extend parent method to control access on staff member level.
108
+ *
109
+ * @param string $action
110
+ * @return bool
111
+ */
112
+ protected static function hasAccess( $action )
113
+ {
114
+ if ( parent::hasAccess( $action ) ) {
115
+ if ( ! Lib\Utils\Common::isCurrentUserAdmin() && $action === 'completePayment' ) {
116
+ $staff = new Lib\Entities\Staff();
117
+
118
+ return $staff->loadBy( array( 'wp_user_id' => get_current_user_id() ) );
119
+ }
120
+
121
+ return true;
122
+ }
123
+
124
+ return false;
125
+ }
126
}
backend/components/dialogs/payment/resources/js/ng-payment_details.js CHANGED
@@ -18,12 +18,14 @@
18
if (payment_id === undefined) {
19
if (e.relatedTarget) {
20
payment_id = e.relatedTarget.getAttribute('data-payment_id');
21
- var payment_bind = e.relatedTarget.getAttribute('data-payment_bind'),
22
- customer_id = e.relatedTarget.getAttribute('data-customer_id');
23
} else if (element.data('payment_id')) {
24
payment_id = element.data('payment_id');
25
- var payment_bind = element.data('payment_bind'),
26
- customer_id = element.data('customer_id');
27
}
28
}
29
jQuery.ajax({
@@ -58,7 +60,7 @@
58
});
59
}
60
// Reload DataTable.
61
- var $table = jQuery('table#bookly-payments-list.dataTable');
62
if ($table.length) {
63
$table.DataTable().ajax.reload();
64
}
@@ -84,7 +86,8 @@
84
payment_id : payment_id,
85
payment_title : response.data.payment_title,
86
payment_type : response.data.payment_type,
87
- customer_id : customer_id
88
});
89
});
90
}
@@ -125,7 +128,7 @@
125
if (response.success) {
126
element.trigger('refresh', [payment_id]);
127
// Reload DataTable.
128
- var $table = jQuery('table#bookly-payments-list.dataTable');
129
if ($table.length) {
130
$table.DataTable().ajax.reload();
131
}
18
if (payment_id === undefined) {
19
if (e.relatedTarget) {
20
payment_id = e.relatedTarget.getAttribute('data-payment_id');
21
+ var payment_bind = e.relatedTarget.getAttribute('data-payment_bind'),
22
+ customer_id = e.relatedTarget.getAttribute('data-customer_id'),
23
+ customer_index = e.relatedTarget.getAttribute('data-customer_index');
24
} else if (element.data('payment_id')) {
25
payment_id = element.data('payment_id');
26
+ var payment_bind = element.data('payment_bind'),
27
+ customer_id = element.data('customer_id'),
28
+ customer_index = element.data('customer_index');
29
}
30
}
31
jQuery.ajax({
60
});
61
}
62
// Reload DataTable.
63
+ var $table = jQuery('table#bookly-payments-list.dataTable, table#bookly-appointments-list.dataTable');
64
if ($table.length) {
65
$table.DataTable().ajax.reload();
66
}
86
payment_id : payment_id,
87
payment_title : response.data.payment_title,
88
payment_type : response.data.payment_type,
89
+ customer_id : customer_id,
90
+ customer_index: customer_index
91
});
92
});
93
}
128
if (response.success) {
129
element.trigger('refresh', [payment_id]);
130
// Reload DataTable.
131
+ var $table = jQuery('table#bookly-payments-list.dataTable, table#bookly-appointments-list.dataTable');
132
if ($table.length) {
133
$table.DataTable().ajax.reload();
134
}
backend/components/dialogs/payment/templates/details.php CHANGED
@@ -1,28 +1,35 @@
1
<?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
use Bookly\Backend\Components\Controls\Buttons;
3
use Bookly\Backend\Modules\Payments\Proxy;
4
use Bookly\Lib\Utils\Price;
5
use Bookly\Lib\Utils\DateTime;
6
use Bookly\Lib\Entities;
7
use Bookly\Lib\Config;
8
/** @var array $show = ['deposit' => int, 'taxes' => int, 'gateway' => bool, 'customer_groups' => bool, 'coupons' => bool] */
9
?>
10
<?php if ( $payment ) : ?>
11
<div class="table-responsive">
12
<table class="table table-bordered">
13
<thead>
14
<tr>
15
- <th width="50%"><?php _e( 'Customer', 'bookly' ) ?></th>
16
- <th width="50%"><?php _e( 'Payment', 'bookly' ) ?></th>
17
</tr>
18
</thead>
19
<tbody>
20
<tr>
21
<td><?php echo esc_html( $payment['customer'] ) ?></td>
22
<td>
23
- <div><?php _e( 'Date', 'bookly' ) ?>: <?php echo DateTime::formatDateTime( $payment['created'] ) ?></div>
24
- <div><?php _e( 'Type', 'bookly' ) ?>: <?php echo Entities\Payment::typeToString( $payment['type'] ) ?></div>
25
- <div><?php _e( 'Status', 'bookly' ) ?>: <?php echo Entities\Payment::statusToString( $payment['status'] ) ?></div>
26
</td>
27
</tr>
28
</tbody>
@@ -33,15 +40,15 @@ use Bookly\Lib\Config;
33
<table class="table table-bordered">
34
<thead>
35
<tr>
36
- <th><?php _e( 'Service', 'bookly' ) ?></th>
37
- <th><?php _e( 'Date', 'bookly' ) ?></th>
38
- <th><?php _e( 'Provider', 'bookly' ) ?></th>
39
<?php if ( $show['deposit'] ): ?>
40
- <th class="text-right"><?php _e( 'Deposit', 'bookly' ) ?></th>
41
<?php endif ?>
42
- <th class="text-right"><?php _e( 'Price', 'bookly' ) ?></th>
43
<?php if ( $show['taxes'] ): ?>
44
- <th class="text-right"><?php _e( 'Tax', 'bookly' ) ?></th>
45
<?php endif ?>
46
</tr>
47
</thead>
@@ -58,7 +65,7 @@ use Bookly\Lib\Config;
58
</ul>
59
<?php endif ?>
60
</td>
61
- <td><?php echo $item['appointment_date'] === null ? __( 'N/A', 'bookly' ) : DateTime::formatDateTime( $item['appointment_date'] ) ?></td>
62
<td><?php echo esc_html( $item['staff_name'] ) ?></td>
63
<?php if ( $show['deposit'] ) : ?>
64
<td class="text-right"><?php echo $item['deposit_format'] ?></td>
@@ -85,7 +92,7 @@ use Bookly\Lib\Config;
85
</td>
86
<?php if ( $show['taxes'] ) : ?>
87
<td class="text-right"><?php echo $item['service_tax'] !== null
88
- ? sprintf( $payment['tax_in_price'] == 'included' ? '(%s)' : '%s', Price::format( $item['service_tax'] ) )
89
: '-' ?></td>
90
<?php endif ?>
91
</tr>
@@ -94,7 +101,7 @@ use Bookly\Lib\Config;
94
<tfoot>
95
<tr>
96
<th style="border-left-color: white; border-bottom-color: white;"></th>
97
- <th colspan="2"><?php _e( 'Subtotal', 'bookly' ) ?></th>
98
<?php if ( $show['deposit'] ) : ?>
99
<th class="text-right"><?php echo Price::format( $payment['subtotal']['deposit'] ) ?></th>
100
<?php endif ?>
@@ -105,7 +112,7 @@ use Bookly\Lib\Config;
105
<tr>
106
<th style="border-left-color: white; border-bottom-color: white;"></th>
107
<th colspan="<?php echo 2 + $show['deposit'] ?>">
108
- <?php _e( 'Coupon discount', 'bookly' ) ?>
109
<?php if ( $payment['coupon'] ) : ?><div><small>(<?php echo $payment['coupon']['code'] ?>)</small></div><?php endif ?>
110
</th>
111
<th class="text-right">
@@ -129,7 +136,7 @@ use Bookly\Lib\Config;
129
<tr>
130
<th style="border-left-color:#fff;border-bottom-color:#fff;"></th>
131
<th colspan="<?php echo 2 + $show['deposit'] ?>">
132
- <?php _e( 'Group discount', 'bookly' ) ?>
133
</th>
134
<th class="text-right">
135
<?php echo $payment['group_discount'] ?: Price::format( 0 ) ?>
@@ -150,13 +157,16 @@ use Bookly\Lib\Config;
150
</tr>
151
<?php endforeach ?>
152
153
- <?php Proxy\Pro::renderManualAdjustmentForm( $show ) ?>
154
155
<?php if ( $show['gateway'] || (float) $payment['price_correction'] ) : ?>
156
<tr>
157
<th style="border-left-color:#fff;border-bottom-color:#fff;"></th>
158
<th colspan="<?php echo 2 + $show['deposit'] ?>">
159
- <?php echo Entities\Payment::typeToString( $payment['type'] ) ?>
160
</th>
161
<th class="text-right">
162
<?php echo Price::format( $payment['price_correction'] ) ?>
@@ -168,7 +178,7 @@ use Bookly\Lib\Config;
168
<?php endif ?>
169
<tr>
170
<th style="border-left-color:#fff;border-bottom-color:#fff;"></th>
171
- <th colspan="<?php echo 2 + $show['deposit'] ?>"><?php _e( 'Total', 'bookly' ) ?></th>
172
<th class="text-right"><?php echo Price::format( $payment['total'] ) ?></th>
173
<?php if ( $show['taxes'] ) : ?>
174
<th class="text-right">
@@ -179,14 +189,14 @@ use Bookly\Lib\Config;
179
<?php if ( $payment['total'] != $payment['paid'] ) : ?>
180
<tr>
181
<th rowspan="2" style="border-left-color:#fff;border-bottom-color:#fff;"></th>
182
- <th colspan="<?php echo 2 + $show['deposit'] ?>"><i><?php _e( 'Paid', 'bookly' ) ?></i></th>
183
<th class="text-right"><i><?php echo Price::format( $payment['paid'] ) ?></i></th>
184
<?php if ( $show['taxes'] ) : ?>
185
<th class="text-right"><i>(<?php echo Price::format( $payment['tax_paid'] ) ?>)</i></th>
186
<?php endif ?>
187
</tr>
188
<tr>
189
- <th colspan="<?php echo 2 + $show['deposit'] ?>"><i><?php _e( 'Due', 'bookly' ) ?></i></th>
190
<th class="text-right">
191
<i><?php echo Price::format( $payment['total'] - $payment['paid'] ) ?></i>
192
</th>
@@ -195,18 +205,18 @@ use Bookly\Lib\Config;
195
<?php endif ?>
196
</tr>
197
<?php endif ?>
198
- <?php if ( Config::proActive() || ( $payment['total'] != $payment['paid'] ) ) : ?>
199
<tr>
200
<th style="border-left-color:#fff;border-bottom-color:#fff;"></th>
201
<th colspan="<?php echo 3 + $show['deposit'] + $show['taxes'] ?>" class="text-right">
202
<div class="bookly-js-details-main-controls">
203
<?php Proxy\Pro::renderManualAdjustmentButton() ?>
204
<?php if ( $payment['total'] != $payment['paid'] ) : ?>
205
- <button type="button" class="btn btn-success ladda-button" id="bookly-complete-payment" data-spinner-size="40" data-style="zoom-in"><i><?php _e( 'Complete payment', 'bookly' ) ?></i></button>
206
<?php endif ?>
207
</div>
208
<div class="bookly-js-details-bind-controls collapse">
209
- <?php Buttons::renderCustom( 'bookly-js-attach-payment', 'btn-success', __( 'Bind payment', 'bookly' ) ) ?>
210
</div>
211
</th>
212
</tr>
1
<?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
use Bookly\Backend\Components\Controls\Buttons;
3
use Bookly\Backend\Modules\Payments\Proxy;
4
+ use Bookly\Lib\Utils\Common;
5
use Bookly\Lib\Utils\Price;
6
use Bookly\Lib\Utils\DateTime;
7
use Bookly\Lib\Entities;
8
use Bookly\Lib\Config;
9
/** @var array $show = ['deposit' => int, 'taxes' => int, 'gateway' => bool, 'customer_groups' => bool, 'coupons' => bool] */
10
+ $can_edit = Common::isCurrentUserAdmin();
11
+ if ( ! $can_edit ) {
12
+ $staff = new Entities\Staff();
13
+
14
+ $can_edit = $staff->loadBy( array( 'wp_user_id' => get_current_user_id() ) ) !== false;
15
+ }
16
?>
17
<?php if ( $payment ) : ?>
18
<div class="table-responsive">
19
<table class="table table-bordered">
20
<thead>
21
<tr>
22
+ <th width="50%"><?php esc_html_e( 'Customer', 'bookly' ) ?></th>
23
+ <th width="50%"><?php esc_html_e( 'Payment', 'bookly' ) ?></th>
24
</tr>
25
</thead>
26
<tbody>
27
<tr>
28
<td><?php echo esc_html( $payment['customer'] ) ?></td>
29
<td>
30
+ <div><?php esc_html_e( 'Date', 'bookly' ) ?>: <?php echo DateTime::formatDateTime( $payment['created'] ) ?></div>
31
+ <div><?php esc_html_e( 'Type', 'bookly' ) ?>: <?php echo Entities\Payment::typeToString( $payment['type'] ) ?></div>
32
+ <div><?php esc_html_e( 'Status', 'bookly' ) ?>: <?php echo Entities\Payment::statusToString( $payment['status'] ) ?></div>
33
</td>
34
</tr>
35
</tbody>
40
<table class="table table-bordered">
41
<thead>
42
<tr>
43
+ <th><?php esc_html_e( 'Service', 'bookly' ) ?></th>
44
+ <th><?php esc_html_e( 'Date', 'bookly' ) ?></th>
45
+ <th><?php esc_html_e( 'Provider', 'bookly' ) ?></th>
46
<?php if ( $show['deposit'] ): ?>
47
+ <th class="text-right"><?php esc_html_e( 'Deposit', 'bookly' ) ?></th>
48
<?php endif ?>
49
+ <th class="text-right"><?php esc_html_e( 'Price', 'bookly' ) ?></th>
50
<?php if ( $show['taxes'] ): ?>
51
+ <th class="text-right"><?php esc_html_e( 'Tax', 'bookly' ) ?></th>
52
<?php endif ?>
53
</tr>
54
</thead>
65
</ul>
66
<?php endif ?>
67
</td>
68
+ <td><?php echo $item['appointment_date'] === null ? esc_html__( 'N/A', 'bookly' ) : DateTime::formatDateTime( $item['appointment_date'] ) ?></td>
69
<td><?php echo esc_html( $item['staff_name'] ) ?></td>
70
<?php if ( $show['deposit'] ) : ?>
71
<td class="text-right"><?php echo $item['deposit_format'] ?></td>
92
</td>
93
<?php if ( $show['taxes'] ) : ?>
94
<td class="text-right"><?php echo $item['service_tax'] !== null
95
+ ? sprintf( $payment['tax_in_price'] === 'included' ? '(%s)' : '%s', Price::format( $item['service_tax'] ) )
96
: '-' ?></td>
97
<?php endif ?>
98
</tr>
101
<tfoot>
102
<tr>
103
<th style="border-left-color: white; border-bottom-color: white;"></th>
104
+ <th colspan="2"><?php esc_html_e( 'Subtotal', 'bookly' ) ?></th>
105
<?php if ( $show['deposit'] ) : ?>
106
<th class="text-right"><?php echo Price::format( $payment['subtotal']['deposit'] ) ?></th>
107
<?php endif ?>
112
<tr>
113
<th style="border-left-color: white; border-bottom-color: white;"></th>
114
<th colspan="<?php echo 2 + $show['deposit'] ?>">
115
+ <?php esc_html_e( 'Coupon discount', 'bookly' ) ?>
116
<?php if ( $payment['coupon'] ) : ?><div><small>(<?php echo $payment['coupon']['code'] ?>)</small></div><?php endif ?>
117
</th>
118
<th class="text-right">
136
<tr>
137
<th style="border-left-color:#fff;border-bottom-color:#fff;"></th>
138
<th colspan="<?php echo 2 + $show['deposit'] ?>">
139
+ <?php esc_html_e( 'Group discount', 'bookly' ) ?>
140
</th>
141
<th class="text-right">
142
<?php echo $payment['group_discount'] ?: Price::format( 0 ) ?>
157
</tr>
158
<?php endforeach ?>
159
160
+ <?php if ( $can_edit ) : ?>
161
+ <?php Proxy\Pro::renderManualAdjustmentForm( $show ) ?>
162
+ <?php endif ?>
163
164
<?php if ( $show['gateway'] || (float) $payment['price_correction'] ) : ?>
165
<tr>
166
<th style="border-left-color:#fff;border-bottom-color:#fff;"></th>
167
<th colspan="<?php echo 2 + $show['deposit'] ?>">
168
+ <?php echo Entities\Payment::typeToString( $payment['gateway'] ) ?> -
169
+ <?php esc_html_e( 'Price correction', 'bookly' ) ?>
170
</th>
171
<th class="text-right">
172
<?php echo Price::format( $payment['price_correction'] ) ?>
178
<?php endif ?>
179
<tr>
180
<th style="border-left-color:#fff;border-bottom-color:#fff;"></th>
181
+ <th colspan="<?php echo 2 + $show['deposit'] ?>"><?php esc_html_e( 'Total', 'bookly' ) ?></th>
182
<th class="text-right"><?php echo Price::format( $payment['total'] ) ?></th>
183
<?php if ( $show['taxes'] ) : ?>
184
<th class="text-right">
189
<?php if ( $payment['total'] != $payment['paid'] ) : ?>
190
<tr>
191
<th rowspan="2" style="border-left-color:#fff;border-bottom-color:#fff;"></th>
192
+ <th colspan="<?php echo 2 + $show['deposit'] ?>"><i><?php esc_html_e( 'Paid', 'bookly' ) ?></i></th>
193
<th class="text-right"><i><?php echo Price::format( $payment['paid'] ) ?></i></th>
194
<?php if ( $show['taxes'] ) : ?>
195
<th class="text-right"><i>(<?php echo Price::format( $payment['tax_paid'] ) ?>)</i></th>
196
<?php endif ?>
197
</tr>
198
<tr>
199
+ <th colspan="<?php echo 2 + $show['deposit'] ?>"><i><?php esc_html_e( 'Due', 'bookly' ) ?></i></th>
200
<th class="text-right">
201
<i><?php echo Price::format( $payment['total'] - $payment['paid'] ) ?></i>
202
</th>
205
<?php endif ?>
206
</tr>
207
<?php endif ?>
208
+ <?php if ( $can_edit && ( Config::proActive() || ( $payment['total'] != $payment['paid'] ) ) ) : ?>
209
<tr>
210
<th style="border-left-color:#fff;border-bottom-color:#fff;"></th>
211
<th colspan="<?php echo 3 + $show['deposit'] + $show['taxes'] ?>" class="text-right">
212
<div class="bookly-js-details-main-controls">
213
<?php Proxy\Pro::renderManualAdjustmentButton() ?>
214
<?php if ( $payment['total'] != $payment['paid'] ) : ?>
215
+ <button type="button" class="btn btn-success ladda-button" id="bookly-complete-payment" data-spinner-size="40" data-style="zoom-in"><i><?php esc_html_e( 'Complete payment', 'bookly' ) ?></i></button>
216
<?php endif ?>
217
</div>
218
<div class="bookly-js-details-bind-controls collapse">
219
+ <?php Buttons::renderCustom( 'bookly-js-attach-payment', 'btn-success', esc_html__( 'Bind payment', 'bookly' ) ) ?>
220
</div>
221
</th>
222
</tr>
backend/components/dialogs/queue/Dialog.php ADDED
@@ -0,0 +1,36 @@
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Queue;
3
+
4
+ use Bookly\Lib as BooklyLib;
5
+
6
+ /**
7
+ * Class Dialog
8
+ * @package Bookly\Backend\Components\Dialogs\Queue
9
+ */
10
+ class Dialog extends BooklyLib\Base\Component
11
+ {
12
+ /**
13
+ * Render notifications queue dialog.
14
+ */
15
+ public static function render()
16
+ {
17
+ self::enqueueStyles( array(
18
+ 'frontend' => array( 'css/ladda.min.css', ),
19
+ 'backend' => array( 'css/fontawesome-all.min.css', 'css/select2.min.css' ),
20
+ ) );
21
+
22
+ self::enqueueScripts( array(
23
+ 'frontend' => array(
24
+ 'js/spin.min.js' => array( 'jquery', ),
25
+ 'js/ladda.min.js' => array( 'jquery', ),
26
+ ),
27
+ 'module' => array( 'js/queue-dialog.js' => array( 'jquery' ), ),
28
+ ) );
29
+
30
+ wp_localize_script( 'bookly-queue-dialog.js', 'BooklyNotificationQueueDialogL10n', array(
31
+ 'csrfToken' => BooklyLib\Utils\Common::getCsrfToken(),
32
+ ) );
33
+
34
+ self::renderTemplate( 'dialog' );
35
+ }
36
+ }
backend/components/dialogs/queue/resources/js/queue-dialog.js ADDED
@@ -0,0 +1,70 @@
1
+ jQuery(function ($) {
2
+ $(document.body).on('bookly.queue_dialog', {},
3
+ function (event, queue, callback) {
4
+ var $dialog = $('#bookly-queue-modal'),
5
+ $queue = $('#bookly-queue', $dialog),
6
+ $template = $('#bookly-notification-template')
7
+ ;
8
+
9
+ $queue.html('');
10
+ queue.forEach(function (notification, index) {
11
+ $queue.append(
12
+ $template.clone().show().html()
13
+ .replace(/{{icon}}/g, notification.gateway == 'sms' ? 'fa-sms' : 'fa-envelope')
14
+ .replace(/{{recipient}}/g, notification.data.name)
15
+ .replace(/{{address}}/g, notification.address)
16
+ .replace(/{{description}}/g, notification.name)
17
+ .replace(/{{index}}/g, index)
18
+ );
19
+ });
20
+ $dialog.off().on('shown.bs.modal', function () {
21
+ $('body').addClass('modal-open');
22
+ }).on('click', '.bookly-js-send', function (e) {
23
+ e.preventDefault();
24
+ var ladda = Ladda.create(this),
25
+ send_queue = [];
26
+ ladda.start();
27
+
28
+ $queue.find('.bookly-js-notification-queue input[type="checkbox"]:checked').each(function () {
29
+ send_queue.push(queue[$(this).data('index')]);
30
+ });
31
+ $.post(
32
+ ajaxurl,
33
+ {
34
+ action : 'bookly_send_queue',
35
+ csrf_token: BooklyNotificationQueueDialogL10n.csrfToken,
36
+ queue : send_queue,
37
+ queue_full: queue
38
+ },
39
+ function (response) {
40
+ ladda.stop();
41
+ if (response.success) {
42
+ // Close the dialog.
43
+ $dialog.modal('hide');
44
+ }
45
+ if (callback) {
46
+ // Call callback.
47
+ callback();
48
+ }
49
+ },
50
+ 'json'
51
+ );
52
+ }).on('click', '.bookly-js-cancel', function (e) {
53
+ e.preventDefault();
54
+ $.post(
55
+ ajaxurl,
56
+ {
57
+ action : 'bookly_clear_attachments',
58
+ csrf_token: BooklyNotificationQueueDialogL10n.csrfToken,
59
+ queue : queue
60
+ },
61
+ 'json'
62
+ );
63
+ if (callback) {
64
+ // Call callback.
65
+ callback();
66
+ }
67
+ }).modal('show');
68
+ }
69
+ );
70
+ });
backend/components/dialogs/queue/templates/dialog.php ADDED
@@ -0,0 +1,30 @@
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ use Bookly\Backend\Components\Controls\Inputs;
4
+ ?>
5
+ <form id="bookly-queue-modal" class="modal fade" tabindex=-1 role="dialog">
6
+ <div class="modal-dialog">
7
+ <div class="modal-content">
8
+ <div class="modal-header">
9
+ <button type="button" class="close" data-dismiss="modal"><span>×</span></button>
10
+ <div class="modal-title h2"><?php esc_html_e( 'Send notifications', 'bookly' ) ?></div>
11
+ </div>
12
+ <div class="modal-body">
13
+ <div id="bookly-queue"></div>
14
+ </div>
15
+ <div class="modal-footer">
16
+ <?php Inputs::renderCsrf() ?>
17
+ <?php Buttons::renderCustom( null, 'bookly-js-send btn-lg btn-success', esc_html__( 'Send', 'bookly' ) ) ?>
18
+ <?php Buttons::renderCustom( null, 'bookly-js-cancel btn-lg btn-default', esc_html__( 'Close', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </form>
23
+ <div id="bookly-notification-template" class="collapse">
24
+ <div class="bookly-js-notification-queue checkbox bookly-margin-bottom-lg bookly-margin-top-remove">
25
+ <label>
26
+ <input type="checkbox" data-index="{{index}}" checked/> <i class="fa fa-fw {{icon}}"></i> <b>{{recipient}}</b> ({{address}})<br/>
27
+ {{description}}
28
+ </label>
29
+ </div>
30
+ </div>
backend/components/dialogs/service/edit/resources/js/service-edit-dialog.js CHANGED
@@ -211,6 +211,8 @@ jQuery(function ($) {
211
updateStaffChoice = false;
212
}
213
submitServiceFrom($serviceDialog, 0);
214
});
215
216
/**
211
updateStaffChoice = false;
212
}
213
submitServiceFrom($serviceDialog, 0);
214
+ }).on('hidden.bs.modal', function () {
215
+ $('body').addClass('modal-open');
216
});
217
218
/**
backend/components/dialogs/service/edit/templates/dialog.php CHANGED
@@ -3,7 +3,6 @@ use Bookly\Backend\Components\Controls\Buttons;
3
use Bookly\Backend\Components\Controls\Inputs;
4
use Bookly\Backend\Modules\Services\Proxy;
5
use Bookly\Lib;
6
-
7
?>
8
<div id="bookly-edit-service-modal" class="modal fade" tabindex=-1 role="dialog">
9
<div class="modal-dialog modal-lg">
@@ -18,20 +17,20 @@ use Bookly\Lib;
18
<li class="active">
19
<a id="bookly-services-general-tab" href="#bookly-services-general" data-toggle="tab">
20
<i class="fa fa-cog fa-fw"></i>
21
- <span class="bookly-nav-tabs-title"><?php _e( 'General', 'bookly' ) ?></span>
22
</a>
23
</li>
24
- <li class="bookly-js-service bookly-js-service-simple">
25
<a id="bookly-services-time-tab" href="#bookly-services-time" data-toggle="tab">
26
<i class="fa fa-clock fa-fw"></i>
27
- <span class="bookly-nav-tabs-title"><?php _e( 'Time', 'bookly' ) ?></span>
28
</a>
29
</li>
30
<?php if ( Lib\Config::proActive() ) : ?>
31
<li class="bookly-js-service bookly-js-service-simple bookly-js-service-collaborative bookly-js-service-compound">
32
<a id="bookly-services-advanced-tab" href="#bookly-services-advanced" data-toggle="tab">
33
<i class="fa fa-cogs fa-fw"></i>
34
- <span class="bookly-nav-tabs-title"><?php _e( 'Advanced', 'bookly' ) ?></span>
35
</a>
36
</li>
37
<?php endif ?>
3
use Bookly\Backend\Components\Controls\Inputs;
4
use Bookly\Backend\Modules\Services\Proxy;
5
use Bookly\Lib;
6
?>
7
<div id="bookly-edit-service-modal" class="modal fade" tabindex=-1 role="dialog">
8
<div class="modal-dialog modal-lg">
17
<li class="active">
18
<a id="bookly-services-general-tab" href="#bookly-services-general" data-toggle="tab">
19
<i class="fa fa-cog fa-fw"></i>
20
+ <span class="bookly-nav-tabs-title"><?php esc_html_e( 'General', 'bookly' ) ?></span>
21
</a>
22
</li>
23
+ <li class="bookly-js-service bookly-js-service-simple bookly-js-service-compound bookly-js-service-collaborative">
24
<a id="bookly-services-time-tab" href="#bookly-services-time" data-toggle="tab">
25
<i class="fa fa-clock fa-fw"></i>
26
+ <span class="bookly-nav-tabs-title"><?php esc_html_e( 'Time', 'bookly' ) ?></span>
27
</a>
28
</li>
29
<?php if ( Lib\Config::proActive() ) : ?>
30
<li class="bookly-js-service bookly-js-service-simple bookly-js-service-collaborative bookly-js-service-compound">
31
<a id="bookly-services-advanced-tab" href="#bookly-services-advanced" data-toggle="tab">
32
<i class="fa fa-cogs fa-fw"></i>
33
+ <span class="bookly-nav-tabs-title"><?php esc_html_e( 'Advanced', 'bookly' ) ?></span>
34
</a>
35
</li>
36
<?php endif ?>
backend/components/dialogs/sms/Dialog.php CHANGED
@@ -35,6 +35,7 @@ class Dialog extends BooklyLib\Base\Component
35
'csrfToken' => BooklyLib\Utils\Common::getCsrfToken(),
36
'recurringActive' => (int) BooklyLib\Config::recurringAppointmentsActive(),
37
'defaultNotification' => self::getDefaultNotification(),
38
'title' => array(
39
'container' => __( 'Sms', 'bookly' ),
40
'new' => __( 'New sms notification', 'bookly' ),
35
'csrfToken' => BooklyLib\Utils\Common::getCsrfToken(),
36
'recurringActive' => (int) BooklyLib\Config::recurringAppointmentsActive(),
37
'defaultNotification' => self::getDefaultNotification(),
38
+ 'sms' => true,
39
'title' => array(
40
'container' => __( 'Sms', 'bookly' ),
41
'new' => __( 'New sms notification', 'bookly' ),
backend/components/dialogs/sms/resources/js/notification-dialog.js CHANGED
@@ -21,7 +21,7 @@ jQuery(function ($) {
21
$codes = $('table.bookly-codes', $modalNotification),
22
$status = $("select[name='notification[settings][status]']", containers.settings),
23
$defaultStatuses,
24
- useTinyMCE = typeof(tinyMCE) !== 'undefined',
25
$textarea = $('#bookly-js-message', containers.message)
26
;
27
@@ -218,12 +218,14 @@ jQuery(function ($) {
218
} else {
219
$defaultStatuses = $status.html();
220
}
221
- if ($status.find('option[value="' + data.settings.status + '"]').length > 0) {
222
- $status.val(data.settings.status);
223
- } else {
224
- var custom_status = data.settings.status.charAt(0).toUpperCase() + data.settings.status.slice(1);
225
226
- $status.append($("<option></option>", {value: data.settings.status, text: custom_status.replace(/\-/g, ' ')})).val(data.settings.status);
227
}
228
229
$("input[name='notification[settings][services][any]'][value='" + data.settings.services.any + "']", containers.settings).prop('checked', true);
21
$codes = $('table.bookly-codes', $modalNotification),
22
$status = $("select[name='notification[settings][status]']", containers.settings),
23
$defaultStatuses,
24
+ useTinyMCE = !BooklyNotificationDialogL10n.sms && typeof(tinyMCE) !== 'undefined',
25
$textarea = $('#bookly-js-message', containers.message)
26
;
27
218
} else {
219
$defaultStatuses = $status.html();
220
}
221
+ if (data.settings.status !== null) {
222
+ if ($status.find('option[value="' + data.settings.status + '"]').length > 0) {
223
+ $status.val(data.settings.status);
224
+ } else {
225
+ var custom_status = data.settings.status.charAt(0).toUpperCase() + data.settings.status.slice(1);
226
227
+ $status.append($("<option></option>", {value: data.settings.status, text: custom_status.replace(/\-/g, ' ')})).val(data.settings.status);
228
+ }
229
}
230
231
$("input[name='notification[settings][services][any]'][value='" + data.settings.services.any + "']", containers.settings).prop('checked', true);
backend/components/dialogs/staff/edit/forms/StaffServices.php CHANGED
@@ -70,7 +70,7 @@ class StaffServices extends Lib\Base\Form
70
public function save()
71
{
72
$staff_id = $this->data['staff_id'];
73
- $location_id = array_key_exists( 'location_id', $this->data ) ? $this->data['location_id'] : null;
74
if ( $staff_id ) {
75
Lib\Entities\StaffService::query()
76
->delete()
70
public function save()
71
{
72
$staff_id = $this->data['staff_id'];
73
+ $location_id = array_key_exists( 'location_id', $this->data ) && $this->data['location_id'] ? $this->data['location_id'] : null;
74
if ( $staff_id ) {
75
Lib\Entities\StaffService::query()
76
->delete()
backend/components/dialogs/staff/edit/resources/js/staff-edit-dialog.js CHANGED
@@ -39,6 +39,9 @@ jQuery(function ($) {
39
editStaff(staff_id);
40
}
41
42
function editStaff(staff_id) {
43
$modalTitle.html(staff_id ? BooklyStaffEditDialogL10n.editStaff : BooklyStaffEditDialogL10n.createStaff);
44
$('#bookly-staff-delete', $modalFooter).toggle(staff_id != 0);
@@ -53,7 +56,7 @@ jQuery(function ($) {
53
booklyAlert(response.data.alert);
54
$modalFooter.show();
55
holidays = response.data.holidays;
56
- let $details_container = $('#bookly-details-container', $modalBody),
57
$services_container = $('#bookly-services-container', $modalBody),
58
$schedule_container = $('#bookly-schedule-container', $modalBody),
59
$holidays_container = $('#bookly-holidays-container', $modalBody),
@@ -83,40 +86,38 @@ jQuery(function ($) {
83
/**
84
* Delete staff member.
85
*/
86
- $modalFooter
87
- .on('click', '#bookly-staff-delete', function (e) {
88
- e.preventDefault();
89
90
- var ladda = Ladda.create(this),
91
- data = {
92
- action: 'bookly_remove_staff',
93
- 'staff_ids[]': staff_id,
94
- csrf_token: BooklyStaffEditDialogL10n.csrfToken
95
- };
96
- ladda.start();
97
-
98
- var delete_staff = function (ajaxurl, data) {
99
- $.post(ajaxurl, data, function (response) {
100
- ladda.stop();
101
- if (!response.success) {
102
- switch (response.data.action) {
103
- case 'show_modal':
104
- $deleteCascadeModal.modal('show');
105
- break;
106
- case 'confirm':
107
- if (confirm(BooklyStaffEditDialogL10n.areYouSure)) {
108
- delete_staff(ajaxurl, $.extend(data, {force_delete: true}));
109
- }
110
- break;
111
}
112
- } else {
113
- $modal.modal('hide');
114
- $staffList.DataTable().ajax.reload();
115
- }
116
- });
117
- };
118
119
- delete_staff(ajaxurl, data);
120
});
121
122
$modalBody
@@ -213,8 +214,7 @@ jQuery(function ($) {
213
.on('click', '.bookly-js-delete', function () {
214
$modalBody.html('<div class="bookly-loading"></div>');
215
ladda = Ladda.create(this);
216
- ladda.start();
217
- delete_staff(ajaxurl, $.extend(data, {force_delete: true}));
218
$deleteCascadeModal.modal('hide');
219
ladda.stop();
220
})
39
editStaff(staff_id);
40
}
41
42
+ /**
43
+ * Edit staff member.
44
+ */
45
function editStaff(staff_id) {
46
$modalTitle.html(staff_id ? BooklyStaffEditDialogL10n.editStaff : BooklyStaffEditDialogL10n.createStaff);
47
$('#bookly-staff-delete', $modalFooter).toggle(staff_id != 0);
56
booklyAlert(response.data.alert);
57
$modalFooter.show();
58
holidays = response.data.holidays;
59
+ let $details_container = $('#bookly-details-container', $modalBody),
60
$services_container = $('#bookly-services-container', $modalBody),
61
$schedule_container = $('#bookly-schedule-container', $modalBody),
62
$holidays_container = $('#bookly-holidays-container', $modalBody),
86
/**
87
* Delete staff member.
88
*/
89
+ function deleteStaff(_data, ladda) {
90
+ ladda.start();
91
+ let data = $.extend({
92
+ action : 'bookly_remove_staff',
93
+ 'staff_ids[]': staff_id,
94
+ csrf_token : BooklyStaffEditDialogL10n.csrfToken
95
+ }, _data);
96
97
+ $.post(ajaxurl, data, function (response) {
98
+ ladda.stop();
99
+ if (!response.success) {
100
+ switch (response.data.action) {
101
+ case 'show_modal':
102
+ $deleteCascadeModal.modal('show');
103
+ break;
104
+ case 'confirm':
105
+ if (confirm(BooklyStaffEditDialogL10n.areYouSure)) {
106
+ deleteStaff( $.extend(data, {force_delete: true}), ladda);
107
}
108
+ break;
109
+ }
110
+ } else {
111
+ $modal.modal('hide');
112
+ $staffList.DataTable().ajax.reload();
113
+ }
114
+ });
115
+ };
116
117
+ $modalFooter
118
+ .on('click', '#bookly-staff-delete', function (e) {
119
+ e.preventDefault();
120
+ deleteStaff( {}, Ladda.create(this));
121
});
122
123
$modalBody
214
.on('click', '.bookly-js-delete', function () {
215
$modalBody.html('<div class="bookly-loading"></div>');
216
ladda = Ladda.create(this);
217
+ deleteStaff({force_delete: true}, ladda);
218
$deleteCascadeModal.modal('hide');
219
ladda.stop();
220
})
backend/components/dialogs/staff/edit/resources/js/staff-schedule.js CHANGED
@@ -106,7 +106,7 @@ jQuery(function ($) {
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) {
106
$.ajax({
107
url: ajaxurl,
108
type: 'POST',
109
+ data: {action: 'bookly_staff_cabinet_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) {
backend/components/dialogs/staff/edit/templates/dialog.php CHANGED
@@ -1,5 +1,6 @@
1
<?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
use Bookly\Backend\Components\Controls\Buttons;
3
use Bookly\Lib\Utils\Common;
4
5
/** @var Bookly\Lib\Entities\Staff $staff */
@@ -17,7 +18,9 @@ use Bookly\Lib\Utils\Common;
17
<div class="modal-footer">
18
<?php if ( Common::isCurrentUserAdmin() ) : ?>
19
<?php Buttons::renderDelete( 'bookly-staff-delete', 'btn-lg pull-left bookly-js-hide-on-loading' ) ?>
20
- <?php Buttons::renderCustom( null, 'btn-lg btn-danger ladda-button bookly-js-staff-archive pull-left bookly-js-hide-on-loading', esc_html__( 'Archive', 'bookly' ), array(), '<i class="fa fa-archive"></i> {caption}' ) ?>
21
<?php endif ?>
22
<span class="bookly-js-errors text-danger" style="max-width: 353px;display: inline-grid;"></span>
23
<?php Buttons::renderCustom( null, 'btn-lg btn-success bookly-js-save bookly-js-hide-on-loading', esc_html__( 'Save', 'bookly' ) ) ?>
1
<?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
use Bookly\Backend\Components\Controls\Buttons;
3
+ use Bookly\Lib\Config;
4
use Bookly\Lib\Utils\Common;
5
6
/** @var Bookly\Lib\Entities\Staff $staff */
18
<div class="modal-footer">
19
<?php if ( Common::isCurrentUserAdmin() ) : ?>
20
<?php Buttons::renderDelete( 'bookly-staff-delete', 'btn-lg pull-left bookly-js-hide-on-loading' ) ?>
21
+ <?php if ( Config::proActive() ) : ?>
22
+ <?php Buttons::renderCustom( null, 'btn-lg btn-danger ladda-button bookly-js-staff-archive pull-left bookly-js-hide-on-loading', esc_html__( 'Archive', 'bookly' ), array(), '<i class="fa fa-archive"></i> {caption}' ) ?>
23
+ <?php endif ?>
24
<?php endif ?>
25
<span class="bookly-js-errors text-danger" style="max-width: 353px;display: inline-grid;"></span>
26
<?php Buttons::renderCustom( null, 'btn-lg btn-success bookly-js-save bookly-js-hide-on-loading', esc_html__( 'Save', 'bookly' ) ) ?>
backend/components/dialogs/staff/edit/templates/dialog_body.php CHANGED
@@ -24,9 +24,9 @@ use Bookly\Backend\Modules\Staff\Proxy;
24
</li>
25
<?php Proxy\Shared::renderStaffTab( $staff ) ?>
26
<li>
27
- <a id="bookly-holidays-tab" href="#daysoff" data-toggle="tab">
28
<i class="fas fa-calendar fa-fw"></i>
29
- <span class="bookly-nav-tabs-title"><?php esc_html_e( 'Days off', 'bookly' ) ?></span>
30
</a>
31
</li>
32
</ul>
@@ -47,7 +47,7 @@ use Bookly\Backend\Modules\Staff\Proxy;
47
<div class="tab-pane" id="special_days">
48
<div id="bookly-special-days-container" style="display: none"></div>
49
</div>
50
- <div class="tab-pane" id="daysoff">
51
<div id="bookly-holidays-container" style="display: none"></div>
52
</div>
53
</div>
24
</li>
25
<?php Proxy\Shared::renderStaffTab( $staff ) ?>
26
<li>
27
+ <a id="bookly-holidays-tab" href="#days_off" data-toggle="tab">
28
<i class="fas fa-calendar fa-fw"></i>
29
+ <span class="bookly-nav-tabs-title"><?php esc_html_e( 'Days Off', 'bookly' ) ?></span>
30
</a>
31
</li>
32
</ul>
47
<div class="tab-pane" id="special_days">
48
<div id="bookly-special-days-container" style="display: none"></div>
49
</div>
50
+ <div class="tab-pane" id="days_off">
51
<div id="bookly-holidays-container" style="display: none"></div>
52
</div>
53
</div>
backend/components/notices/PoweredBy.php ADDED
@@ -0,0 +1,35 @@
1
+ <?php
2
+ namespace Bookly\Backend\Components\Notices;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class PoweredBy
8
+ * @package Bookly\Backend\Components\Notices
9
+ */
10
+ class PoweredBy extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render show Powered by Bookly notice.
14
+ */
15
+ public static function render()
16
+ {
17
+ if ( Lib\Utils\Common::isCurrentUserAdmin()
18
+ && ! get_option( 'bookly_app_show_powered_by' )
19
+ && ! get_user_meta( get_current_user_id(), 'bookly_dismiss_powered_by_notice' )
20
+ ) {
21
+ self::enqueueStyles( array(
22
+ 'frontend' => array( 'css/ladda.min.css', ),
23
+ ) );
24
+ self::enqueueScripts( array(
25
+ 'frontend' => array(
26
+ 'js/spin.min.js' => array( 'jquery' ),
27
+ 'js/ladda.min.js' => array( 'jquery' ),
28
+ ),
29
+ 'module' => array( 'js/powered-by.js' => array( 'bookly-ladda.min.js', ), ),
30
+ ) );
31
+
32
+ self::renderTemplate( 'powered_by' );
33
+ }
34
+ }
35
+ }
backend/components/notices/PoweredByAjax.php ADDED
@@ -0,0 +1,31 @@
1
+ <?php
2
+ namespace Bookly\Backend\Components\Notices;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class PoweredByAjax
8
+ * @package Bookly\Backend\Components\Notices
9
+ */
10
+ class PoweredByAjax extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * Dismiss Powered by Bookly by notice.
14
+ */
15
+ public static function dismissPoweredByNotice()
16
+ {
17
+ update_user_meta( get_current_user_id(), 'bookly_dismiss_powered_by_notice', 1 );
18
+
19
+ wp_send_json_success();
20
+ }
21
+
22
+ /**
23
+ * Enable show Powered by Bookly.
24
+ */
25
+ public static function enableShowPoweredBy()
26
+ {
27
+ update_option( 'bookly_app_show_powered_by', '1' );
28
+
29
+ wp_send_json_success();
30
+ }
31
+ }
backend/components/notices/resources/js/powered-by.js ADDED
@@ -0,0 +1,12 @@
1
+ jQuery(function ($) {
2
+ var $notice = $('#bookly-js-powered-by');
3
+ $notice.on('close.bs.alert', function () {
4
+ $.post(ajaxurl, {action: $notice.data('action'), csrf_token: BooklySupportL10n.csrfToken});
5
+ }).on('click', '#bookly-js-show-powered-by', function () {
6
+ var ladda = Ladda.create(this);
7
+ ladda.start();
8
+ $.post(ajaxurl, {action: 'bookly_enable_show_powered_by', csrf_token: BooklySupportL10n.csrfToken}, function (response) {
9
+ $notice.alert('close');
10
+ });
11
+ });
12
+ });
backend/components/notices/templates/powered_by.php ADDED
@@ -0,0 +1,16 @@
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div id="bookly-tbs" class="wrap">
3
+ <div id="bookly-js-powered-by" class="alert alert-info bookly-tbs-body bookly-flexbox" data-action="bookly_dismiss_powered_by_notice">
4
+ <div class="bookly-flex-row">
5
+ <div class="bookly-flex-cell" style="width:39px"><i class="alert-icon"></i></div>
6
+ <div class="bookly-flex-cell">
7
+ <button type="button" class="close" data-dismiss="alert">&times;</button>
8
+ <?php esc_html_e( 'Allow the plugin to set a Powered by Bookly notice on the booking widget to spread information about the plugin. This will allow the team to improve the product and enhance its functionality.', 'bookly' ) ?>
9
+ <div class="bookly-margin-top-md">
10
+ <button type="button" class="btn btn-default" data-dismiss="alert"><?php esc_html_e( 'Disagree', 'bookly' ) ?></button>
11
+ <button type="button" class="btn btn-success" id="bookly-js-show-powered-by"><?php esc_html_e( 'Agree', 'bookly' ) ?></button>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ </div>
backend/modules/appearance/proxy/Pro.php CHANGED
@@ -7,15 +7,15 @@ use Bookly\Lib;
7
* Class Pro
8
* @package Bookly\Backend\Modules\Appearance\Proxy
9
*
10