WordPress Online Booking and Scheduling Plugin – Bookly - Version 16.0

Version Description

Download this release

Release Info

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

Code changes from version 14.5.1 to 16.0

Files changed (242) hide show
  1. autoload.php +4 -4
  2. backend/Backend.php +86 -94
  3. backend/components/appearance/Codes.php +41 -0
  4. backend/components/appearance/Editable.php +109 -0
  5. backend/components/appearance/proxy/Pro.php +16 -0
  6. backend/components/appearance/proxy/Shared.php +15 -0
  7. backend/components/controls/Buttons.php +127 -0
  8. backend/components/controls/Inputs.php +22 -0
  9. backend/components/dialogs/appointment/attach_payment/proxy/Pro.php +15 -0
  10. backend/components/dialogs/appointment/attach_payment/proxy/Taxes.php +15 -0
  11. backend/components/dialogs/appointment/customer_details/Dialog.php +19 -0
  12. backend/components/dialogs/appointment/customer_details/proxy/Files.php +15 -0
  13. backend/components/dialogs/appointment/customer_details/proxy/Pro.php +15 -0
  14. backend/components/dialogs/appointment/customer_details/proxy/Shared.php +15 -0
  15. backend/{modules/calendar/templates/_customer_details_dialog.php → components/dialogs/appointment/customer_details/templates/customer_details.php} +18 -12
  16. backend/components/dialogs/appointment/delete/Ajax.php +56 -0
  17. backend/components/dialogs/appointment/delete/Dialog.php +33 -0
  18. backend/{modules/calendar → components/dialogs/appointment/delete}/resources/js/delete_dialog.js +0 -0
  19. backend/{modules/calendar/templates/_delete_dialog.php → components/dialogs/appointment/delete/templates/delete.php} +4 -8
  20. backend/components/dialogs/appointment/edit/Ajax.php +808 -0
  21. backend/{modules/calendar/Components.php → components/dialogs/appointment/edit/Dialog.php} +14 -35
  22. backend/components/dialogs/appointment/edit/proxy/Pro.php +17 -0
  23. backend/components/dialogs/appointment/edit/proxy/RecurringAppointments.php +17 -0
  24. backend/components/dialogs/appointment/edit/proxy/Shared.php +19 -0
  25. backend/components/dialogs/appointment/edit/proxy/Tasks.php +15 -0
  26. backend/{modules/calendar/resources/js/ng-appointment_dialog.js → components/dialogs/appointment/edit/resources/js/ng-appointment.js} +289 -109
  27. backend/{modules/calendar/templates/_appointment_dialog.php → components/dialogs/appointment/edit/templates/edit.php} +88 -74
  28. backend/components/dialogs/common/CascadeDelete.php +30 -0
  29. backend/components/dialogs/common/UnsavedChanges.php +30 -0
  30. backend/components/dialogs/common/templates/delete_cascade.php +21 -0
  31. backend/components/dialogs/common/templates/unsaved_changes.php +21 -0
  32. backend/{modules/customers/Components.php → components/dialogs/customer/Edit.php} +20 -18
  33. backend/components/dialogs/customer/EditAjax.php +103 -0
  34. backend/{modules/customers → components/dialogs/customer}/forms/Customer.php +12 -4
  35. backend/components/dialogs/customer/proxy/CustomerGroups.php +16 -0
  36. backend/components/dialogs/customer/proxy/CustomerInformation.php +16 -0
  37. backend/components/dialogs/customer/proxy/Pro.php +16 -0
  38. backend/{modules/customers/resources/js/ng-customer_dialog.js → components/dialogs/customer/resources/js/ng-customer.js} +89 -19
  39. backend/{modules/customers/templates/_customer_dialog.php → components/dialogs/customer/templates/edit.php} +28 -26
  40. backend/components/dialogs/payment/Ajax.php +105 -0
  41. backend/components/dialogs/payment/Dialog.php +32 -0
  42. backend/components/dialogs/payment/resources/js/ng-payment_details.js +151 -0
  43. backend/components/dialogs/payment/templates/details.php +217 -0
  44. backend/{modules/payments/templates/_payment_details_dialog.php → components/dialogs/payment/templates/dialog.php} +4 -2
  45. backend/components/dialogs/special_price/proxy/SpecialHours.php +14 -0
  46. backend/components/notices/CollectStats.php +47 -0
  47. backend/components/notices/CollectStatsAjax.php +41 -0
  48. backend/components/notices/Limitation.php +19 -0
  49. backend/components/notices/LiteRebranding.php +27 -0
  50. backend/components/notices/LiteRebrandingAjax.php +21 -0
  51. backend/components/notices/Nps.php +48 -0
  52. backend/components/notices/NpsAjax.php +40 -0
  53. backend/components/notices/Subscribe.php +38 -0
  54. backend/components/notices/SubscribeAjax.php +36 -0
  55. backend/components/notices/proxy/Pro.php +15 -0
  56. backend/{modules/support → components/notices}/resources/css/bootstrap-stars.css +0 -0
  57. backend/components/notices/resources/js/collect-stats.js +9 -0
  58. backend/{modules/support → components/notices}/resources/js/jquery.barrating.min.js +0 -0
  59. backend/components/notices/resources/js/lite-rebranding.js +6 -0
  60. backend/{modules/support → components/notices}/resources/js/nps.js +0 -0
  61. backend/{modules/support → components/notices}/resources/js/subscribe.js +0 -0
  62. backend/components/notices/templates/collect_stats.php +23 -0
  63. backend/components/notices/templates/limitation.php +3 -0
  64. backend/components/notices/templates/lite_rebranding.php +12 -0
  65. backend/{modules/support/templates/_nps_notice.php → components/notices/templates/nps.php} +8 -4
  66. backend/{modules/support/templates/_subscribe_notice.php → components/notices/templates/subscribe.php} +1 -1
  67. backend/components/settings/Image.php +30 -0
  68. backend/components/settings/Inputs.php +76 -0
  69. backend/components/settings/Menu.php +23 -0
  70. backend/components/settings/Payments.php +41 -0
  71. backend/components/settings/Selects.php +80 -0
  72. backend/components/settings/proxy/Pro.php +15 -0
  73. backend/components/settings/proxy/Taxes.php +15 -0
  74. backend/components/settings/templates/image.php +48 -0
  75. backend/components/settings/templates/price_correction.php +18 -0
  76. backend/components/support/Buttons.php +74 -0
  77. backend/components/support/ButtonsAjax.php +78 -0
  78. backend/components/support/lib/Urls.php +11 -0
  79. backend/{modules → components}/support/resources/js/support.js +0 -0
  80. backend/{modules → components}/support/templates/_email_to_support.php +7 -3
  81. backend/{modules/support/templates/_buttons.php → components/support/templates/buttons.php} +8 -5
  82. backend/components/tiny_mce/Tools.php +58 -0
  83. backend/components/tiny_mce/proxy/DepositPayments.php +15 -0
  84. backend/components/tiny_mce/proxy/GroupBooking.php +15 -0
  85. backend/components/tiny_mce/proxy/Shared.php +18 -0
  86. backend/components/tiny_mce/proxy/SpecialDays.php +15 -0
  87. backend/components/tiny_mce/proxy/SpecialHours.php +15 -0
  88. backend/{modules → components}/tiny_mce/resources/images/calendar.png +0 -0
  89. backend/{modules/tiny_mce/templates/popup.php → components/tiny_mce/templates/bookly_form.php} +55 -26
  90. backend/modules/appearance/Ajax.php +132 -0
  91. backend/modules/appearance/Components.php +0 -21
  92. backend/modules/appearance/Controller.php +0 -218
  93. backend/modules/appearance/Page.php +103 -0
  94. backend/modules/appearance/lib/Helper.php +0 -69
  95. backend/modules/appearance/proxy/Cart.php +17 -0
  96. backend/modules/appearance/proxy/ChainAppointments.php +14 -0
  97. backend/modules/appearance/proxy/Coupons.php +16 -0
  98. backend/modules/appearance/proxy/CustomDuration.php +15 -0
  99. backend/modules/appearance/proxy/CustomFields.php +15 -0
  100. backend/modules/appearance/proxy/CustomerInformation.php +15 -0
  101. backend/modules/appearance/proxy/DepositPayments.php +15 -0
  102. backend/modules/appearance/proxy/Files.php +16 -0
  103. backend/modules/appearance/proxy/GoogleMapsAddress.php +15 -0
  104. backend/modules/appearance/proxy/GroupBooking.php +16 -0
  105. backend/modules/appearance/proxy/Locations.php +16 -0
  106. backend/modules/appearance/proxy/MultiplyAppointments.php +16 -0
  107. backend/modules/appearance/proxy/Pro.php +23 -0
  108. backend/modules/appearance/proxy/RecurringAppointments.php +18 -0
  109. backend/modules/appearance/proxy/ServiceExtras.php +18 -0
  110. backend/modules/appearance/proxy/Shared.php +19 -0
  111. backend/modules/appearance/proxy/WaitingList.php +15 -0
  112. backend/modules/appearance/resources/js/appearance.js +319 -10
  113. backend/modules/appearance/resources/js/bootstrap-editable.bookly.js +50 -37
  114. backend/modules/appearance/templates/_1_service.php +23 -46
  115. backend/modules/appearance/templates/_3_time.php +173 -140
  116. backend/modules/appearance/templates/_5_cart.php +0 -144
  117. backend/modules/appearance/templates/_6_details.php +31 -16
  118. backend/modules/appearance/templates/_7_payment.php +17 -27
  119. backend/modules/appearance/templates/_8_complete.php +5 -5
  120. backend/modules/appearance/templates/_card_payment.php +4 -4
  121. backend/modules/appearance/templates/_codes.php +0 -20
  122. backend/modules/appearance/templates/_custom_css.php +5 -10
  123. backend/modules/appearance/templates/_progress_tracker.php +21 -17
  124. backend/modules/appearance/templates/index.php +64 -53
  125. backend/modules/appointments/Ajax.php +195 -0
  126. backend/modules/appointments/Controller.php +0 -253
  127. backend/modules/appointments/Page.php +98 -0
  128. backend/modules/appointments/proxy/Pro.php +18 -0
  129. backend/modules/appointments/proxy/Ratings.php +16 -0
  130. backend/modules/appointments/proxy/Shared.php +15 -0
  131. backend/modules/appointments/proxy/Tasks.php +15 -0
  132. backend/modules/appointments/resources/js/appointments.js +130 -36
  133. backend/modules/appointments/templates/_export_dialog.php +0 -46
  134. backend/modules/appointments/templates/index.php +27 -16
  135. backend/modules/calendar/Ajax.php +185 -0
  136. backend/modules/calendar/Controller.php +0 -932
  137. backend/modules/calendar/Page.php +229 -0
  138. backend/modules/calendar/proxy/AdvancedGoogleCalendar.php +15 -0
  139. backend/modules/calendar/proxy/Locations.php +15 -0
  140. backend/modules/calendar/proxy/Shared.php +17 -0
  141. backend/modules/calendar/resources/js/calendar-common.js +52 -14
  142. backend/modules/calendar/resources/js/calendar.js +135 -17
  143. backend/modules/calendar/templates/calendar.php +34 -22
  144. backend/modules/customers/Ajax.php +243 -0
  145. backend/modules/customers/Controller.php +0 -249
  146. backend/modules/customers/Page.php +63 -0
  147. backend/modules/customers/proxy/CustomerGroups.php +20 -0
  148. backend/modules/customers/proxy/CustomerInformation.php +15 -0
  149. backend/modules/customers/proxy/Pro.php +20 -0
  150. backend/modules/customers/proxy/Shared.php +15 -0
  151. backend/modules/customers/resources/js/customers.js +239 -88
  152. backend/modules/customers/templates/_import.php +0 -44
  153. backend/modules/customers/templates/index.php +76 -30
  154. backend/modules/debug/Ajax.php +151 -0
  155. backend/modules/debug/Controller.php +0 -274
  156. backend/modules/debug/Page.php +152 -0
  157. backend/modules/debug/templates/index.php +5 -3
  158. backend/modules/message/Controller.php +0 -100
  159. backend/modules/messages/Ajax.php +63 -0
  160. backend/modules/messages/Page.php +73 -0
  161. backend/modules/{message → messages}/resources/js/message.js +0 -0
  162. backend/modules/{message → messages}/templates/index.php +4 -2
  163. backend/modules/notifications/Ajax.php +102 -0
  164. backend/modules/notifications/Components.php +0 -357
  165. backend/modules/notifications/Controller.php +0 -133
  166. backend/modules/notifications/Page.php +75 -0
  167. backend/modules/notifications/forms/Notifications.php +47 -30
  168. backend/modules/notifications/lib/Codes.php +296 -0
  169. backend/modules/notifications/proxy/Invoices.php +14 -0
  170. backend/modules/notifications/proxy/Pro.php +15 -0
  171. backend/modules/notifications/proxy/Shared.php +19 -0
  172. backend/modules/notifications/resources/js/ng-app.js +2 -2
  173. backend/modules/notifications/resources/js/notification.js +142 -13
  174. backend/modules/notifications/templates/_custom_notification.php +0 -1
  175. backend/modules/notifications/templates/_test_email_notifications_modal.php +9 -6
  176. backend/modules/notifications/templates/index.php +41 -52
  177. backend/modules/payments/Ajax.php +117 -0
  178. backend/modules/payments/Components.php +0 -34
  179. backend/modules/payments/Controller.php +0 -257
  180. backend/modules/payments/Page.php +96 -0
  181. backend/modules/payments/proxy/Invoices.php +15 -0
  182. backend/modules/payments/proxy/Pro.php +15 -0
  183. backend/modules/payments/proxy/Shared.php +15 -0
  184. backend/modules/payments/resources/js/ng-payment_details_dialog.js +0 -69
  185. backend/modules/payments/resources/js/payments.js +68 -33
  186. backend/modules/payments/templates/details.php +0 -137
  187. backend/modules/payments/templates/index.php +39 -10
  188. backend/modules/services/Ajax.php +204 -0
  189. backend/modules/services/Controller.php +0 -332
  190. backend/modules/services/Page.php +133 -0
  191. backend/modules/services/forms/Category.php +3 -3
  192. backend/modules/services/forms/Service.php +24 -41
  193. backend/modules/services/proxy/CompoundServices.php +15 -0
  194. backend/modules/services/proxy/CustomDuration.php +18 -0
  195. backend/modules/services/proxy/CustomerGroups.php +16 -0
  196. backend/modules/services/proxy/GroupBooking.php +15 -0
  197. backend/modules/services/proxy/Packages.php +15 -0
  198. backend/modules/services/proxy/Pro.php +19 -0
  199. backend/modules/services/proxy/Shared.php +24 -0
  200. backend/modules/services/proxy/Taxes.php +15 -0
  201. backend/modules/services/resources/js/service.js +95 -46
  202. backend/modules/services/templates/_list.php +54 -115
  203. backend/modules/services/templates/index.php +11 -5
  204. backend/modules/settings/Ajax.php +60 -0
  205. backend/modules/settings/Components.php +0 -30
  206. backend/modules/settings/Controller.php +0 -299
  207. backend/modules/settings/Page.php +194 -0
  208. backend/modules/settings/forms/BusinessHours.php +3 -3
  209. backend/modules/settings/forms/Payments.php +0 -37
  210. backend/modules/settings/proxy/AdvancedGoogleCalendar.php +16 -0
  211. backend/modules/settings/proxy/DepositPayments.php +15 -0
  212. backend/modules/settings/proxy/PaypalPaymentsStandard.php +16 -0
  213. backend/modules/settings/proxy/Pro.php +27 -0
  214. backend/modules/settings/proxy/ProSettings.php +46 -0
  215. backend/modules/settings/proxy/Shared.php +21 -0
  216. backend/modules/settings/proxy/Taxes.php +15 -0
  217. backend/modules/settings/resources/js/collect-stats-notice.js +0 -13
  218. backend/modules/settings/resources/js/settings.js +177 -134
  219. backend/modules/settings/templates/_calendarForm.php +23 -10
  220. backend/modules/settings/templates/_calendar_codes.php +4 -2
  221. backend/modules/settings/templates/_cartForm.php +0 -33
  222. backend/modules/settings/templates/_collect_stats_notice.php +0 -24
  223. backend/modules/settings/templates/_companyForm.php +11 -7
  224. backend/modules/settings/templates/_customers.php +21 -14
  225. backend/modules/settings/templates/_facebookForm.php +21 -0
  226. backend/modules/settings/templates/_generalForm.php +19 -28
  227. backend/modules/settings/templates/_googleCalendarForm.php +0 -36
  228. backend/modules/settings/templates/_hoursForm.php +9 -6
  229. backend/modules/settings/templates/_payment_local.php +16 -0
  230. backend/modules/settings/templates/_paymentsForm.php +16 -46
  231. backend/modules/settings/templates/_urlForm.php +16 -27
  232. backend/modules/settings/templates/_woocommerce.php +0 -45
  233. backend/modules/settings/templates/_woocommerce_codes.php +0 -13
  234. backend/modules/settings/templates/index.php +13 -22
  235. backend/modules/shop/Ajax.php +90 -0
  236. backend/modules/shop/Page.php +70 -0
  237. backend/modules/shop/resources/css/shop.css +15 -0
  238. backend/modules/shop/resources/js/shop.js +54 -0
  239. backend/modules/shop/templates/index.php +69 -0
  240. backend/modules/sms/Ajax.php +219 -0
  241. backend/modules/sms/Components.php +0 -348
  242. backend/modules/sms/Controller.php +0 -23
autoload.php CHANGED
@@ -1,12 +1,12 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
 
3
  /**
4
- * Bookly Lite autoload.
5
  * @param $class
6
  */
7
- function bookly_lite_loader( $class )
8
  {
9
- if ( preg_match( '/^BooklyLite\\\\(.+)?([^\\\\]+)$/U', ltrim( $class, '\\' ), $match ) ) {
10
  $file = __DIR__ . DIRECTORY_SEPARATOR
11
  . strtolower( str_replace( '\\', DIRECTORY_SEPARATOR, preg_replace( '/([a-z])([A-Z])/', '$1_$2', $match[1] ) ) )
12
  . $match[2]
@@ -16,4 +16,4 @@ function bookly_lite_loader( $class )
16
  }
17
  }
18
  }
19
- spl_autoload_register( 'bookly_lite_loader', true, true );
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
 
3
  /**
4
+ * Bookly autoload.
5
  * @param $class
6
  */
7
+ function bookly_loader( $class )
8
  {
9
+ if ( preg_match( '/^Bookly\\\\(.+)?([^\\\\]+)$/U', ltrim( $class, '\\' ), $match ) ) {
10
  $file = __DIR__ . DIRECTORY_SEPARATOR
11
  . strtolower( str_replace( '\\', DIRECTORY_SEPARATOR, preg_replace( '/([a-z])([A-Z])/', '$1_$2', $match[1] ) ) )
12
  . $match[2]
16
  }
17
  }
18
  }
19
+ spl_autoload_register( 'bookly_loader', true, true );
backend/Backend.php CHANGED
@@ -1,126 +1,118 @@
1
  <?php
2
- namespace BooklyLite\Backend;
3
 
4
- use BooklyLite\Frontend;
5
- use BooklyLite\Lib;
6
 
7
  /**
8
  * Class Backend
9
- * @package BooklyLite\Backend
10
  */
11
- class Backend
12
  {
13
  /**
14
- * Constructor.
15
  */
16
- public function __construct()
17
  {
18
- // Backend controllers.
19
- $this->apearanceController = Modules\Appearance\Controller::getInstance();
20
- $this->appointmentsController = Modules\Appointments\Controller::getInstance();
21
- $this->calendarController = Modules\Calendar\Controller::getInstance();
22
- $this->customerController = Modules\Customers\Controller::getInstance();
23
- $this->debugController = Modules\Debug\Controller::getInstance();
24
- $this->notificationsController = Modules\Notifications\Controller::getInstance();
25
- $this->paymentController = Modules\Payments\Controller::getInstance();
26
- $this->serviceController = Modules\Services\Controller::getInstance();
27
- $this->settingsController = Modules\Settings\Controller::getInstance();
28
- $this->supportController = Modules\Support\Controller::getInstance();
29
- $this->smsController = Modules\Sms\Controller::getInstance();
30
- $this->staffController = Modules\Staff\Controller::getInstance();
31
- $this->messageController = Modules\Message\Controller::getInstance();
32
 
33
- // Frontend controllers that work via admin-ajax.php.
34
- $this->bookingController = Frontend\Modules\Booking\Controller::getInstance();
35
- $this->customerProfileController = Frontend\Modules\CustomerProfile\Controller::getInstance();
36
-
37
- add_action( 'admin_menu', array( $this, 'addAdminMenu' ) );
38
- add_action( 'wp_loaded', array( $this, 'init' ) );
39
- add_action( 'admin_init', array( $this, 'addTinyMCEPlugin' ) );
40
- }
41
-
42
- /**
43
- * Init.
44
- */
45
- public function init()
46
- {
47
- if ( ! session_id() ) {
48
- @session_start();
49
- }
50
- }
51
-
52
- public function addTinyMCEPlugin()
53
- {
54
- new Modules\TinyMce\Plugin();
55
  }
56
 
57
  /**
58
  * Admin menu.
59
  */
60
- public function addAdminMenu()
61
  {
62
  /** @var \WP_User $current_user */
63
  global $current_user, $submenu;
64
 
65
  if ( $current_user->has_cap( 'administrator' ) || Lib\Entities\Staff::query()->where( 'wp_user_id', $current_user->ID )->count() ) {
66
  $dynamic_position = '80.0000001' . mt_rand( 1, 1000 ); // position always is under `Settings`
67
- add_menu_page( 'Bookly Lite', 'Bookly Lite', 'read', 'bookly-menu', '',
68
- plugins_url( 'resources/images/menu.png', __FILE__ ), $dynamic_position );
69
-
70
- // Translated submenu pages.
71
- $calendar = __( 'Calendar', 'bookly' );
72
- $appointments = __( 'Appointments', 'bookly' );
73
- $staff_members = __( 'Staff Members', 'bookly' );
74
- $services = __( 'Services', 'bookly' );
75
- $sms = __( 'SMS Notifications', 'bookly' );
76
- $notifications = __( 'Email Notifications', 'bookly' );
77
- $customers = __( 'Customers', 'bookly' );
78
- $payments = __( 'Payments', 'bookly' );
79
- $appearance = __( 'Appearance', 'bookly' );
80
- $settings = __( 'Settings', 'bookly' );
81
- $messages = __( 'Messages', 'bookly' );
82
 
83
- add_submenu_page( 'bookly-menu', $calendar, $calendar, 'read',
84
- Modules\Calendar\Controller::page_slug, array( $this->calendarController, 'index' ) );
85
- add_submenu_page( 'bookly-menu', $appointments, $appointments, 'manage_options',
86
- Modules\Appointments\Controller::page_slug, array( $this->appointmentsController, 'index' ) );
87
- Lib\Proxy\Locations::addBooklyMenuItem();
88
- Lib\Proxy\Packages::addBooklyMenuItem();
89
- if ( $current_user->has_cap( 'administrator' ) ) {
90
- add_submenu_page( 'bookly-menu', $staff_members, $staff_members, 'manage_options',
91
- Modules\Staff\Controller::page_slug, array( $this->staffController, 'index' ) );
92
  } else {
93
- if ( get_option( 'bookly_gen_allow_staff_edit_profile' ) == 1 ) {
94
- add_submenu_page( 'bookly-menu', __( 'Profile', 'bookly' ), __( 'Profile', 'bookly' ), 'read',
95
- Modules\Staff\Controller::page_slug, array( $this->staffController, 'index' ) );
96
- }
97
  }
98
- add_submenu_page( 'bookly-menu', $services, $services, 'manage_options',
99
- Modules\Services\Controller::page_slug, array( $this->serviceController, 'index' ) );
100
- add_submenu_page( 'bookly-menu', $customers, $customers, 'manage_options',
101
- Modules\Customers\Controller::page_slug, array( $this->customerController, 'index' ) );
102
- add_submenu_page( 'bookly-menu', $notifications, $notifications, 'manage_options',
103
- Modules\Notifications\Controller::page_slug, array( $this->notificationsController, 'index' ) );
104
- add_submenu_page( 'bookly-menu', $sms, $sms, 'manage_options',
105
- Modules\Sms\Controller::page_slug, array( $this->smsController, 'index' ) );
106
- add_submenu_page( 'bookly-menu', $payments, $payments, 'manage_options',
107
- Modules\Payments\Controller::page_slug, array( $this->paymentController, 'index' ) );
108
- add_submenu_page( 'bookly-menu', $appearance, $appearance, 'manage_options',
109
- Modules\Appearance\Controller::page_slug, array( $this->apearanceController, 'index' ) );
110
- Lib\Proxy\Coupons::addBooklyMenuItem();
111
- Lib\Proxy\CustomFields::addBooklyMenuItem();
112
- add_submenu_page( 'bookly-menu', $settings, $settings, 'manage_options',
113
- Modules\Settings\Controller::page_slug, array( $this->settingsController, 'index' ) );
114
- add_submenu_page( 'bookly-menu', $messages, $messages, 'manage_options',
115
- Modules\Message\Controller::page_slug, array( $this->messageController, 'index' ) );
116
 
117
- if ( isset ( $_GET['page'] ) && $_GET['page'] == 'bookly-debug' ) {
118
- add_submenu_page( 'bookly-menu', 'Debug', 'Debug', 'manage_options',
119
- Modules\Debug\Controller::page_slug, array( $this->debugController, 'index' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  }
121
 
122
  unset ( $submenu['bookly-menu'][0] );
123
  }
124
  }
125
-
126
  }
1
  <?php
2
+ namespace Bookly\Backend;
3
 
4
+ use Bookly\Lib;
 
5
 
6
  /**
7
  * Class Backend
8
+ * @package Bookly\Backend
9
  */
10
+ abstract class Backend
11
  {
12
  /**
13
+ * Register hooks.
14
  */
15
+ public static function registerHooks()
16
  {
17
+ add_action( 'admin_menu', array( __CLASS__, 'addAdminMenu' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ add_action( 'admin_notices', function () {
20
+ $bookly_page = isset ( $_REQUEST['page'] ) && strpos( $_REQUEST['page'], 'bookly-' ) === 0;
21
+ if ( $bookly_page ) {
22
+ // Subscribe notice.
23
+ Components\Notices\Subscribe::render();
24
+ // Subscribe notice.
25
+ Components\Notices\LiteRebranding::render();
26
+ // NPS notice.
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 );
33
+ }, 10, 0 );
 
 
 
 
 
 
 
34
  }
35
 
36
  /**
37
  * Admin menu.
38
  */
39
+ public static function addAdminMenu()
40
  {
41
  /** @var \WP_User $current_user */
42
  global $current_user, $submenu;
43
 
44
  if ( $current_user->has_cap( 'administrator' ) || Lib\Entities\Staff::query()->where( 'wp_user_id', $current_user->ID )->count() ) {
45
  $dynamic_position = '80.0000001' . mt_rand( 1, 1000 ); // position always is under `Settings`
46
+ $badge_number = Modules\Messages\Page::getMessagesCount() +
47
+ Modules\Shop\Page::getNotSeenCount() +
48
+ Lib\SMS::getUndeliveredSmsCount()
49
+ ;
 
 
 
 
 
 
 
 
 
 
 
50
 
51
+ if ( $badge_number ) {
52
+ add_menu_page( 'Bookly', sprintf( 'Bookly <span class="update-plugins count-%d"><span class="update-count">%d</span></span>', $badge_number, $badge_number ), 'read', 'bookly-menu', '',
53
+ plugins_url( 'resources/images/menu.png', __FILE__ ), $dynamic_position );
 
 
 
 
 
 
54
  } else {
55
+ add_menu_page( 'Bookly', 'Bookly', 'read', 'bookly-menu', '',
56
+ plugins_url( 'resources/images/menu.png', __FILE__ ), $dynamic_position );
 
 
57
  }
58
+ if ( Lib\Proxy\Pro::graceExpired() ) {
59
+ Lib\Proxy\Pro::addLicenseBooklyMenuItem();
60
+ } else {
61
+ // Translated submenu pages.
62
+ $calendar = __( 'Calendar', 'bookly' );
63
+ $appointments = __( 'Appointments', 'bookly' );
64
+ $staff_members = __( 'Staff Members', 'bookly' );
65
+ $services = __( 'Services', 'bookly' );
66
+ $notifications = __( 'Email Notifications', 'bookly' );
67
+ $customers = __( 'Customers', 'bookly' );
68
+ $payments = __( 'Payments', 'bookly' );
69
+ $appearance = __( 'Appearance', 'bookly' );
70
+ $settings = __( 'Settings', 'bookly' );
 
 
 
 
 
71
 
72
+ add_submenu_page( 'bookly-menu', $calendar, $calendar, 'read',
73
+ Modules\Calendar\Page::pageSlug(), function () { Modules\Calendar\Page::render(); } );
74
+ add_submenu_page( 'bookly-menu', $appointments, $appointments, 'manage_options',
75
+ Modules\Appointments\Page::pageSlug(), function () { Modules\Appointments\Page::render(); } );
76
+ Lib\Proxy\Locations::addBooklyMenuItem();
77
+ Lib\Proxy\Packages::addBooklyMenuItem();
78
+ if ( $current_user->has_cap( 'administrator' ) ) {
79
+ add_submenu_page( 'bookly-menu', $staff_members, $staff_members, 'manage_options',
80
+ Modules\Staff\Page::pageSlug(), function () { Modules\Staff\Page::render(); } );
81
+ } else {
82
+ if ( get_option( 'bookly_gen_allow_staff_edit_profile' ) == 1 ) {
83
+ add_submenu_page( 'bookly-menu', __( 'Profile', 'bookly' ), __( 'Profile', 'bookly' ), 'read',
84
+ Modules\Staff\Page::pageSlug(), function () { Modules\Staff\Page::render(); } );
85
+ }
86
+ }
87
+ add_submenu_page( 'bookly-menu', $services, $services, 'manage_options',
88
+ Modules\Services\Page::pageSlug(), function () { Modules\Services\Page::render(); } );
89
+ Lib\Proxy\Taxes::addBooklyMenuItem();
90
+ add_submenu_page( 'bookly-menu', $customers, $customers, 'manage_options',
91
+ Modules\Customers\Page::pageSlug(), function () { Modules\Customers\Page::render(); } );
92
+ Lib\Proxy\CustomerInformation::addBooklyMenuItem();
93
+ Lib\Proxy\CustomerGroups::addBooklyMenuItem();
94
+ add_submenu_page( 'bookly-menu', $notifications, $notifications, 'manage_options',
95
+ Modules\Notifications\Page::pageSlug(), function () { Modules\Notifications\Page::render(); } );
96
+ Modules\Sms\Page::addBooklyMenuItem();
97
+ add_submenu_page( 'bookly-menu', $payments, $payments, 'manage_options',
98
+ Modules\Payments\Page::pageSlug(), function () { Modules\Payments\Page::render(); } );
99
+ add_submenu_page( 'bookly-menu', $appearance, $appearance, 'manage_options',
100
+ Modules\Appearance\Page::pageSlug(), function () { Modules\Appearance\Page::render(); } );
101
+ Lib\Proxy\Coupons::addBooklyMenuItem();
102
+ Lib\Proxy\CustomFields::addBooklyMenuItem();
103
+ add_submenu_page( 'bookly-menu', $settings, $settings, 'manage_options',
104
+ Modules\Settings\Page::pageSlug(), function () { Modules\Settings\Page::render(); } );
105
+ Modules\Messages\Page::addBooklyMenuItem();
106
+ Modules\Shop\Page::addBooklyMenuItem();
107
+ Lib\Proxy\Pro::addAnalyticsBooklyMenuItem();
108
+
109
+ if ( isset ( $_GET['page'] ) && $_GET['page'] == 'bookly-debug' ) {
110
+ add_submenu_page( 'bookly-menu', 'Debug', 'Debug', 'manage_options',
111
+ Modules\Debug\Page::pageSlug(), function () { Modules\Debug\Page::render(); } );
112
+ }
113
  }
114
 
115
  unset ( $submenu['bookly-menu'][0] );
116
  }
117
  }
 
118
  }
backend/components/appearance/Codes.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Appearance;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Codes
8
+ * @package Bookly\Backend\Components\Appearance
9
+ */
10
+ class Codes
11
+ {
12
+ /**
13
+ * Get HTML for appearance codes.
14
+ *
15
+ * @param int $step
16
+ * @param bool $extra_codes
17
+ * @return string
18
+ */
19
+ public static function getHtml( $step = null, $extra_codes = false )
20
+ {
21
+ $codes = array(
22
+ array( 'code' => 'appointments_count', 'description' => __( 'total quantity of appointments in cart', 'bookly' ), 'flags' => array( 'step' => 7, 'extra_codes' => true ) ),
23
+ array( 'code' => 'booking_number', 'description' => __( 'booking number', 'bookly' ), 'flags' => array( 'step' => 8, 'extra_codes' => true ) ),
24
+ array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) ),
25
+ array( 'code' => 'login_form', 'description' => __( 'login form', 'bookly' ), 'flags' => array( 'step' => 6, 'extra_codes' => true ) ),
26
+ array( 'code' => 'number_of_persons', 'description' => __( 'number of persons', 'bookly' ) ),
27
+ array( 'code' => 'service_date', 'description' => __( 'date of service', 'bookly' ), 'flags' => array( 'step' => '>3' ) ),
28
+ array( 'code' => 'service_duration', 'description' => __( 'duration of service', 'bookly' ) ),
29
+ array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) ),
30
+ array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) ),
31
+ array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ) ),
32
+ array( 'code' => 'service_time', 'description' => __( 'time of service', 'bookly' ), 'flags' => array( 'step' => '>3' ) ),
33
+ array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) ),
34
+ array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) ),
35
+ array( 'code' => 'staff_photo', 'description' => __( 'photo of staff', 'bookly' ), 'flags' => array( 'step' => '>1' ) ),
36
+ array( 'code' => 'total_price', 'description' => __( 'total price of booking', 'bookly' ) ),
37
+ );
38
+
39
+ return Lib\Utils\Common::codes( Proxy\Shared::prepareCodes( $codes ), array( 'step' => $step, 'extra_codes' => isset ( $extra_codes ) ) );
40
+ }
41
+ }
backend/components/appearance/Editable.php ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Appearance;
3
+
4
+ /**
5
+ * Class Editable
6
+ * @package Bookly\Backend\Components\Appearance
7
+ */
8
+ class Editable
9
+ {
10
+ /**
11
+ * Render editable string (single line).
12
+ *
13
+ * @param array $options
14
+ * @param bool $echo
15
+ * @return string
16
+ */
17
+ public static function renderString( array $options, $echo = true )
18
+ {
19
+ return self::_renderEditable( $options, 'span', $echo );
20
+ }
21
+
22
+ /**
23
+ * Render editable label.
24
+ *
25
+ * @param array $options
26
+ * @param bool $echo
27
+ * @return string
28
+ */
29
+ public static function renderLabel( array $options, $echo = true )
30
+ {
31
+ return self::_renderEditable( $options, 'label', $echo );
32
+ }
33
+
34
+ /**
35
+ * Render editable text (multi-line).
36
+ *
37
+ * @param string $option_name
38
+ * @param string $codes
39
+ * @param string $placement
40
+ * @param string $title
41
+ */
42
+ public static function renderText( $option_name, $codes = '', $placement = 'bottom', $title = '' )
43
+ {
44
+ $option_value = get_option( $option_name );
45
+
46
+ printf( '<span class="bookly-js-editable bookly-js-option %s editable-pre-wrapped" data-type="bookly" data-fieldType="textarea" data-values="%s" data-codes="%s" data-title="%s" data-placement="%s">%s</span>',
47
+ $option_name,
48
+ esc_attr( json_encode( array( $option_name => $option_value ) ) ),
49
+ esc_attr( $codes ),
50
+ esc_attr( $title ),
51
+ $placement,
52
+ esc_html( $option_value )
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Render editable number.
58
+ *
59
+ * @param string $option_name
60
+ * @param int $min
61
+ * @param int $step
62
+ */
63
+ public static function renderNumber( $option_name, $min = 1, $step = 1 )
64
+ {
65
+ $option_value = get_option( $option_name );
66
+
67
+ printf( '<span class="bookly-js-editable bookly-js-option %s editable-pre-wrapped" data-type="bookly" data-fieldType="number" data-values="%s" data-min="%s" data-step="%s">%s</span>',
68
+ $option_name,
69
+ esc_attr( json_encode( array( $option_name => $option_value ) ) ),
70
+ esc_attr( $min ),
71
+ esc_attr( $step ),
72
+ esc_html( $option_value )
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Render editable element.
78
+ *
79
+ * @param array $options
80
+ * @param string $tag
81
+ * @param bool $echo
82
+ * @return string|void
83
+ */
84
+ private static function _renderEditable( array $options, $tag, $echo = true )
85
+ {
86
+ $data = array();
87
+ foreach ( $options as $option_name ) {
88
+ $data[ $option_name ] = get_option( $option_name );
89
+ }
90
+
91
+ $class = $options[0];
92
+ $data_values = esc_attr( json_encode( $data ) );
93
+ $content = esc_html( $data[ $options[0] ] );
94
+
95
+ $template = '<{tag} class="bookly-js-editable bookly-js-option {class}" data-type="bookly" data-values="{data-values}">{content}</{tag}>';
96
+ $html = strtr( $template, array(
97
+ '{tag}' => $tag,
98
+ '{class}' => $class,
99
+ '{data-values}' => $data_values,
100
+ '{content}' => $content,
101
+ ) );
102
+
103
+ if ( ! $echo ) {
104
+ return $html;
105
+ }
106
+
107
+ echo $html;
108
+ }
109
+ }
backend/components/appearance/proxy/Pro.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static void renderAddress() Render inputs for address fields in appearance.
11
+ * @method static void renderBirthday() Render inputs for birthday fields in appearance.
12
+ */
13
+ abstract class Pro extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/components/appearance/proxy/Shared.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Shared
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static array prepareCodes( array $codes ) Alter array of codes to be displayed in Bookly Appearance.
11
+ */
12
+ abstract class Shared extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/components/controls/Buttons.php ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Controls;
3
+
4
+ /**
5
+ * Class Buttons
6
+ * @package Bookly\Backend\Components\Controls
7
+ */
8
+ class Buttons
9
+ {
10
+ /**
11
+ * Render custom button.
12
+ *
13
+ * @param string $id
14
+ * @param string $class
15
+ * @param string $caption
16
+ * @param array $attributes
17
+ * @param string $caption_template
18
+ */
19
+ public static function renderCustom( $id = null, $class = 'btn-success', $caption = null, array $attributes = array(), $caption_template = '{caption}' )
20
+ {
21
+ if ( $caption === null ) {
22
+ $caption = __( 'Save', 'bookly' );
23
+ }
24
+
25
+ $caption = strtr( $caption_template, array( '{caption}' => esc_html( $caption ) ) );
26
+
27
+ echo self::_createButton( 'button', $id, $class, null, $caption, $attributes );
28
+ }
29
+
30
+ /**
31
+ * Render delete button.
32
+ *
33
+ * @param string $id
34
+ * @param string $extra_class
35
+ * @param string $caption
36
+ * @param array $attributes
37
+ */
38
+ public static function renderDelete( $id = 'bookly-delete', $extra_class = null, $caption = null, array $attributes = array() )
39
+ {
40
+ if ( $caption === null ) {
41
+ $caption = __( 'Delete', 'bookly' );
42
+ }
43
+
44
+ echo self::_createButton(
45
+ 'button',
46
+ $id,
47
+ 'btn-danger',
48
+ $extra_class,
49
+ '<i class="glyphicon glyphicon-trash"></i> ' . esc_html( $caption ),
50
+ $attributes
51
+ );
52
+ }
53
+
54
+ /**
55
+ * Render reset button.
56
+ *
57
+ * @param string $id
58
+ * @param string $extra_class
59
+ * @param string $caption
60
+ * @param array $attributes
61
+ */
62
+ public static function renderReset( $id = null, $extra_class = null, $caption = null, array $attributes = array() )
63
+ {
64
+ if ( $caption === null ) {
65
+ $caption = __( 'Reset', 'bookly' );
66
+ }
67
+
68
+ echo self::_createButton( 'reset', $id, 'btn-lg btn-default', $extra_class, esc_html( $caption ), $attributes );
69
+ }
70
+
71
+ /**
72
+ * Render submit button.
73
+ *
74
+ * @param string $id
75
+ * @param string $extra_class
76
+ * @param string $caption
77
+ * @param array $attributes
78
+ */
79
+ public static function renderSubmit( $id = 'bookly-save', $extra_class = null, $caption = null, array $attributes = array() )
80
+ {
81
+ if ( $caption === null ) {
82
+ $caption = __( 'Save', 'bookly' );
83
+ }
84
+
85
+ echo self::_createButton( 'submit', $id, 'btn-lg btn-success', $extra_class, esc_html( $caption ), $attributes );
86
+ }
87
+
88
+ /**
89
+ * Create button.
90
+ *
91
+ * @param string $type
92
+ * @param string $id
93
+ * @param string $class
94
+ * @param string $extra_class
95
+ * @param string $caption_html
96
+ * @param array $attributes
97
+ * @return string
98
+ */
99
+ private static function _createButton( $type, $id, $class, $extra_class, $caption_html, array $attributes )
100
+ {
101
+ $classes = array( 'btn ladda-button' );
102
+ if ( $class != '' ) {
103
+ $classes[] = $class;
104
+ }
105
+ if ( $extra_class != '' ) {
106
+ $classes[] = $extra_class;
107
+ }
108
+
109
+ if ( $id !== null ) {
110
+ $attributes['id'] = $id;
111
+ }
112
+ $attributes_str = '';
113
+ foreach ( $attributes as $attr => $value ) {
114
+ $attributes_str .= sprintf( ' %s="%s"', $attr, esc_attr( $value ) );
115
+ }
116
+
117
+ return strtr(
118
+ '<button type="{type}" class="{class}" data-spinner-size="40" data-style="zoom-in"{attributes}><span class="ladda-label">{caption}</span></button>',
119
+ array(
120
+ '{type}' => $type,
121
+ '{class}' => implode( ' ', $classes ),
122
+ '{attributes}' => $attributes_str,
123
+ '{caption}' => $caption_html,
124
+ )
125
+ );
126
+ }
127
+ }
backend/components/controls/Inputs.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Controls;
3
+
4
+ use Bookly\Lib\Utils\Common;
5
+
6
+ /**
7
+ * Class Inputs
8
+ * @package Bookly\Backend\Components\Controls
9
+ */
10
+ class Inputs
11
+ {
12
+ /**
13
+ * Add hidden input with CSRF token.
14
+ */
15
+ public static function renderCsrf()
16
+ {
17
+ printf(
18
+ '<input type="hidden" name="csrf_token" value="%s" />',
19
+ esc_attr( Common::getCsrfToken() )
20
+ );
21
+ }
22
+ }
backend/components/dialogs/appointment/attach_payment/proxy/Pro.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\AttachPayment\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Components\Dialogs\Appointment\AttachPayment\Proxy
9
+ *
10
+ * @method static void renderAttachPaymentDialog() Render attach payment dialog.
11
+ */
12
+ abstract class Pro extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/components/dialogs/appointment/attach_payment/proxy/Taxes.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\AttachPayment\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Taxes
8
+ * @package Bookly\Backend\Components\Dialogs\Appointment\AttachPayment\Proxy
9
+ *
10
+ * @method static void renderAttachPayment()
11
+ */
12
+ abstract class Taxes extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/components/dialogs/appointment/customer_details/Dialog.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\CustomerDetails;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Dialog
8
+ * @package Bookly\Backend\Components\Dialogs\Appointment\CustomerDetails
9
+ */
10
+ class Dialog extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render customer details dialog.
14
+ */
15
+ public static function render()
16
+ {
17
+ static::renderTemplate( 'customer_details' );
18
+ }
19
+ }
backend/components/dialogs/appointment/customer_details/proxy/Files.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\CustomerDetails\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Files
8
+ * @package Bookly\Backend\Components\Dialogs\Appointment
9
+ *
10
+ * @method static void renderCustomField( \stdClass $custom_field ) Render files in Customer Details
11
+ */
12
+ abstract class Files extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/components/dialogs/appointment/customer_details/proxy/Pro.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\CustomerDetails\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Components\Dialogs\Appointment
9
+ *
10
+ * @method static void renderTimeZoneSwitcher() Render time zone switcher in Customer Details
11
+ */
12
+ abstract class Pro extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/components/dialogs/appointment/customer_details/proxy/Shared.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\CustomerDetails\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Shared
8
+ * @package Bookly\Backend\Components\Dialogs\Appointment
9
+ *
10
+ * @method static void renderDetails()
11
+ */
12
+ abstract class Shared extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/{modules/calendar/templates/_customer_details_dialog.php → components/dialogs/appointment/customer_details/templates/customer_details.php} RENAMED
@@ -1,8 +1,9 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Entities\CustomerAppointment;
3
- use BooklyLite\Lib\Utils\Common;
4
- use BooklyLite\Lib\Config;
5
- use BooklyLite\Lib\Proxy;
 
6
  ?>
7
  <div id="bookly-customer-details-dialog" class="modal fade" tabindex=-1 role="dialog">
8
  <div class="modal-dialog">
@@ -23,11 +24,16 @@ use BooklyLite\Lib\Proxy;
23
  <?php if ( Config::waitingListActive() ) : ?>
24
  <option value="<?php echo CustomerAppointment::STATUS_WAITLISTED ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_WAITLISTED ) ) ?></option>
25
  <?php endif ?>
 
 
 
26
  </select>
27
  </div>
28
- <div class="form-group" style="display: none">
 
29
  <select class="bookly-custom-field form-control" id="bookly-number-of-persons"></select>
30
  </div>
 
31
  <?php if ( Config::showNotes() ): ?>
32
  <div class="form-group">
33
  <label for="bookly-appointment-notes"><?php echo Common::getTranslatedOption( 'bookly_l10n_label_notes' ) ?></label>
@@ -35,14 +41,14 @@ use BooklyLite\Lib\Proxy;
35
  </div>
36
  <?php endif ?>
37
 
38
- <?php Proxy\CustomFields::renderCustomerDetails() ?>
39
- <?php Proxy\ServiceExtras::renderCustomerDetails() ?>
40
  </div>
41
  <div class="modal-footer">
42
- <?php Common::customButton( null, 'btn-lg btn-lg btn-success', __( 'Apply', 'bookly' ), array( 'ng-click' => 'saveCustomFields()' ) ) ?>
43
- <?php Common::customButton( null, 'btn btn-lg btn-default', __( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
44
  </div>
45
  </form>
46
- </div><!-- /.modal-content -->
47
- </div><!-- /.modal-dialog -->
48
- </div><!-- /.modal -->
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ use Bookly\Backend\Components\Dialogs\Appointment\CustomerDetails\Proxy;
4
+ use Bookly\Lib\Entities\CustomerAppointment;
5
+ use Bookly\Lib\Utils\Common;
6
+ use Bookly\Lib\Config;
7
  ?>
8
  <div id="bookly-customer-details-dialog" class="modal fade" tabindex=-1 role="dialog">
9
  <div class="modal-dialog">
24
  <?php if ( Config::waitingListActive() ) : ?>
25
  <option value="<?php echo CustomerAppointment::STATUS_WAITLISTED ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_WAITLISTED ) ) ?></option>
26
  <?php endif ?>
27
+ <?php if ( Config::tasksActive() ) : ?>
28
+ <option value="<?php echo CustomerAppointment::STATUS_DONE ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_DONE ) ) ?></option>
29
+ <?php endif ?>
30
  </select>
31
  </div>
32
+ <div class="form-group" <?php if ( ! Config::groupBookingActive() ) echo ' style="display:none"' ?>>
33
+ <label for="bookly-number-of-persons"><?php _e( 'Number of persons', 'bookly' ) ?></label>
34
  <select class="bookly-custom-field form-control" id="bookly-number-of-persons"></select>
35
  </div>
36
+ <?php Proxy\Pro::renderTimeZoneSwitcher() ?>
37
  <?php if ( Config::showNotes() ): ?>
38
  <div class="form-group">
39
  <label for="bookly-appointment-notes"><?php echo Common::getTranslatedOption( 'bookly_l10n_label_notes' ) ?></label>
41
  </div>
42
  <?php endif ?>
43
 
44
+ <?php Proxy\Shared::renderDetails() ?>
45
+
46
  </div>
47
  <div class="modal-footer">
48
+ <?php Buttons::renderCustom( null, 'btn-lg btn-success', __( 'Apply', 'bookly' ), array( 'ng-click' => 'saveCustomFields()' ) ) ?>
49
+ <?php Buttons::renderCustom( null, 'btn-lg btn-default', __( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
50
  </div>
51
  </form>
52
+ </div>
53
+ </div>
54
+ </div>
backend/components/dialogs/appointment/delete/Ajax.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\Delete;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Lib\DataHolders\Booking as DataHolders;
6
+
7
+ /**
8
+ * Class Ajax
9
+ * @package Bookly\Backend\Components\Dialogs\Appointment\Delete
10
+ */
11
+ class Ajax extends Lib\Base\Ajax
12
+ {
13
+ /**
14
+ * @inheritdoc
15
+ */
16
+ protected static function permissions()
17
+ {
18
+ return array( '_default' => 'user' );
19
+ }
20
+
21
+ /**
22
+ * Delete single appointment.
23
+ */
24
+ public static function deleteAppointment()
25
+ {
26
+ $appointment_id = self::parameter( 'appointment_id' );
27
+ $reason = self::parameter( 'reason' );
28
+
29
+ if ( self::parameter( 'notify' ) ) {
30
+ $ca_list = Lib\Entities\CustomerAppointment::query()
31
+ ->where( 'appointment_id', $appointment_id )
32
+ ->find();
33
+ /** @var Lib\Entities\CustomerAppointment $ca */
34
+ foreach ( $ca_list as $ca ) {
35
+ switch ( $ca->getStatus() ) {
36
+ case Lib\Entities\CustomerAppointment::STATUS_PENDING:
37
+ case Lib\Entities\CustomerAppointment::STATUS_WAITLISTED:
38
+ $ca->setStatus( Lib\Entities\CustomerAppointment::STATUS_REJECTED );
39
+ break;
40
+ case Lib\Entities\CustomerAppointment::STATUS_APPROVED:
41
+ $ca->setStatus( Lib\Entities\CustomerAppointment::STATUS_CANCELLED );
42
+ break;
43
+ }
44
+ Lib\Notifications\Sender::sendSingle(
45
+ DataHolders\Simple::create( $ca ),
46
+ null,
47
+ array( 'cancellation_reason' => $reason )
48
+ );
49
+ }
50
+ }
51
+
52
+ Lib\Entities\Appointment::find( $appointment_id )->delete();
53
+
54
+ wp_send_json_success();
55
+ }
56
+ }
backend/components/dialogs/appointment/delete/Dialog.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\Delete;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Dialog
8
+ * @package Bookly\Backend\Components\Dialogs\Appointment
9
+ */
10
+ class Dialog extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render delete appointment dialog.
14
+ */
15
+ public static function render()
16
+ {
17
+ static::enqueueStyles( array(
18
+ 'frontend' => array( 'css/ladda.min.css', ),
19
+ ) );
20
+
21
+ static::enqueueScripts( array(
22
+ 'frontend' => array(
23
+ 'js/spin.min.js' => array( 'jquery' ),
24
+ 'js/ladda.min.js' => array( 'jquery' ),
25
+ ),
26
+ 'module' => array(
27
+ 'js/delete_dialog.js' => array( 'jquery' ),
28
+ ),
29
+ ) );
30
+
31
+ static::renderTemplate( 'delete' );
32
+ }
33
+ }
backend/{modules/calendar → components/dialogs/appointment/delete}/resources/js/delete_dialog.js RENAMED
File without changes
backend/{modules/calendar/templates/_delete_dialog.php → components/dialogs/appointment/delete/templates/delete.php} RENAMED
@@ -1,10 +1,6 @@
1
- <?php
2
- /**
3
- * Template for delete appointment dialog
4
- */
5
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
6
  ?>
7
-
8
  <div id="bookly-delete-dialog" class="modal fade" tabindex=-1 role="dialog">
9
  <div class="modal-dialog">
10
  <div class="modal-content">
@@ -24,8 +20,8 @@ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
24
  </div>
25
  </div>
26
  <div class="modal-footer">
27
- <?php \BooklyLite\Lib\Utils\Common::deleteButton(); ?>
28
- <?php \BooklyLite\Lib\Utils\Common::customButton( null, 'btn-default', __( 'Cancel', 'bookly' ), array( 'ng-click' => 'closeDialog()', 'data-dismiss' => 'modal' ) ) ?>
29
  </div>
30
  </div>
31
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
 
 
 
3
  ?>
 
4
  <div id="bookly-delete-dialog" class="modal fade" tabindex=-1 role="dialog">
5
  <div class="modal-dialog">
6
  <div class="modal-content">
20
  </div>
21
  </div>
22
  <div class="modal-footer">
23
+ <?php Buttons::renderDelete(); ?>
24
+ <?php Buttons::renderCustom( null, 'btn-default', __( 'Cancel', 'bookly' ), array( 'ng-click' => 'closeDialog()', 'data-dismiss' => 'modal' ) ) ?>
25
  </div>
26
  </div>
27
  </div>
backend/components/dialogs/appointment/edit/Ajax.php ADDED
@@ -0,0 +1,808 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\Edit;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Lib\DataHolders\Booking as DataHolders;
6
+ use Bookly\Backend\Modules\Calendar;
7
+
8
+ /**
9
+ * Class Ajax
10
+ * @package Bookly\Backend\Components\Dialogs\Appointment\Edit
11
+ */
12
+ class Ajax extends Lib\Base\Ajax
13
+ {
14
+ /**
15
+ * @inheritdoc
16
+ */
17
+ protected static function permissions()
18
+ {
19
+ return array( '_default' => 'user' );
20
+ }
21
+
22
+ /**
23
+ * Get data needed for appointment form initialisation.
24
+ */
25
+ public static function getDataForAppointmentForm()
26
+ {
27
+ $type = self::parameter( 'type', false ) == 'package'
28
+ ? Lib\Entities\Service::TYPE_PACKAGE
29
+ : Lib\Entities\Service::TYPE_SIMPLE;
30
+
31
+ $result = array(
32
+ 'staff' => array(),
33
+ 'customers' => array(),
34
+ 'start_time' => array(),
35
+ 'end_time' => array(),
36
+ 'app_start_time' => null, // Appointment start time which may not be in the list of start times.
37
+ 'app_end_time' => null, // Appointment end time which may not be in the list of end times.
38
+ 'week_days' => array(),
39
+ 'time_interval' => Lib\Config::getTimeSlotLength(),
40
+ 'status' => array(
41
+ 'items' => array(
42
+ 'pending' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_PENDING ),
43
+ 'approved' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_APPROVED ),
44
+ 'cancelled' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_CANCELLED ),
45
+ 'rejected' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_REJECTED ),
46
+ 'waitlisted' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_WAITLISTED ),
47
+ 'done' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_DONE ),
48
+ )
49
+ ),
50
+ );
51
+
52
+ // Staff list.
53
+ $staff = Lib\Entities\Staff::query()->findOne();
54
+ $staff_members = $staff ? Lib\Proxy\Pro::prepareStaffMembers( array( $staff ) ) : array();
55
+ $max_duration = 0;
56
+
57
+ foreach ( $staff_members as $staff_member ) {
58
+ $services = array();
59
+ if ( $type == Lib\Entities\Service::TYPE_SIMPLE ) {
60
+ $services = Proxy\Pro::addCustomService( $services );
61
+ }
62
+ foreach ( $staff_member->getStaffServices( $type ) as $staff_service ) {
63
+ $sub_services = $staff_service->service->getSubServices();
64
+ if ( $type == Lib\Entities\Service::TYPE_SIMPLE || ! empty( $sub_services ) ) {
65
+ if ( $staff_service->getLocationId() === null || Lib\Proxy\Locations::prepareStaffLocationId( $staff_service->getLocationId(), $staff_service->getStaffId() ) == $staff_service->getLocationId() ) {
66
+ if ( ! in_array( $staff_service->service->getId(), array_map( function ( $service ) { return $service['id']; }, $services ) ) ) {
67
+ $services[] = array(
68
+ 'id' => $staff_service->service->getId(),
69
+ 'title' => sprintf(
70
+ '%s (%s)',
71
+ $staff_service->service->getTitle(),
72
+ Lib\Utils\DateTime::secondsToInterval( $staff_service->service->getDuration() )
73
+ ),
74
+ 'duration' => $staff_service->service->getDuration(),
75
+ 'units_min' => $staff_service->service->getUnitsMin(),
76
+ 'units_max' => $staff_service->service->getUnitsMax(),
77
+ 'locations' => array(
78
+ ( $staff_service->getLocationId() ?: 0 ) => array(
79
+ 'capacity_min' => Lib\Config::groupBookingActive() ? $staff_service->getCapacityMin() : 1,
80
+ 'capacity_max' => Lib\Config::groupBookingActive() ? $staff_service->getCapacityMax() : 1,
81
+ ),
82
+ ),
83
+ );
84
+ $max_duration = max( $max_duration, $staff_service->service->getUnitsMax() * $staff_service->service->getDuration() );
85
+ } else {
86
+ array_walk( $services, function ( &$item ) use ( $staff_service ) {
87
+ if ( $item['id'] == $staff_service->service->getId() ) {
88
+ $item['locations'][ $staff_service->getLocationId() ?: 0 ] = array(
89
+ 'capacity_min' => Lib\Config::groupBookingActive() ? $staff_service->getCapacityMin() : 1,
90
+ 'capacity_max' => Lib\Config::groupBookingActive() ? $staff_service->getCapacityMax() : 1,
91
+ );
92
+ }
93
+ } );
94
+ }
95
+ }
96
+ }
97
+ }
98
+ $locations = array();
99
+ foreach ( (array) Lib\Proxy\Locations::findByStaffId( $staff_member->getId() ) as $location ) {
100
+ $locations[] = array(
101
+ 'id' => $location->getId(),
102
+ 'name' => $location->getName(),
103
+ );
104
+ }
105
+ $result['staff'][] = array(
106
+ 'id' => $staff_member->getId(),
107
+ 'full_name' => $staff_member->getFullName(),
108
+ 'services' => $services,
109
+ 'locations' => $locations,
110
+ );
111
+ }
112
+
113
+ /** @var Lib\Entities\Customer $customer */
114
+ // Customers list.
115
+ foreach ( Lib\Entities\Customer::query()->sortBy( 'full_name' )->find() as $customer ) {
116
+ $name = $customer->getFullName();
117
+ if ( $customer->getEmail() != '' || $customer->getPhone() != '' ) {
118
+ $name .= ' (' . trim( $customer->getEmail() . ', ' . $customer->getPhone(), ', ' ) . ')';
119
+ }
120
+
121
+ $result['customers'][] = array(
122
+ 'id' => $customer->getId(),
123
+ 'name' => $name,
124
+ 'status' => Lib\Proxy\CustomerGroups::prepareDefaultAppointmentStatus( get_option( 'bookly_gen_default_appointment_status' ), $customer->getGroupId() ),
125
+ 'custom_fields' => array(),
126
+ 'timezone' => Lib\Proxy\Pro::getLastCustomerTimezone( $customer->getId() ),
127
+ 'number_of_persons' => 1,
128
+ );
129
+ }
130
+
131
+ // Time list.
132
+ $ts_length = Lib\Config::getTimeSlotLength();
133
+ $time_start = 0;
134
+ $time_end = max( $max_duration + DAY_IN_SECONDS, DAY_IN_SECONDS * 2 );
135
+
136
+ // Run the loop.
137
+ while ( $time_start <= $time_end ) {
138
+ $slot = array(
139
+ 'value' => Lib\Utils\DateTime::buildTimeString( $time_start, false ),
140
+ 'title' => Lib\Utils\DateTime::formatTime( $time_start ),
141
+ );
142
+ if ( $time_start < DAY_IN_SECONDS ) {
143
+ $result['start_time'][] = $slot;
144
+ }
145
+ $result['end_time'][] = $slot;
146
+ $time_start += $ts_length;
147
+ }
148
+
149
+ $days_times = Lib\Config::getDaysAndTimes();
150
+ $weekdays = array( 1 => 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', );
151
+ foreach ( $days_times['days'] as $index => $abbrev ) {
152
+ $result['week_days'][] = $weekdays[ $index ];
153
+ }
154
+
155
+ if ( $type == Lib\Entities\Service::TYPE_PACKAGE ) {
156
+ $result = Proxy\Shared::prepareDataForPackage( $result );
157
+ }
158
+
159
+ wp_send_json( $result );
160
+ }
161
+
162
+ /**
163
+ * Get appointment data when editing an appointment.
164
+ */
165
+ public static function getDataForAppointment()
166
+ {
167
+ $response = array( 'success' => false, 'data' => array( 'customers' => array() ) );
168
+
169
+ $appointment = new Lib\Entities\Appointment();
170
+ if ( $appointment->load( self::parameter( 'id' ) ) ) {
171
+ $response['success'] = true;
172
+
173
+ $query = Lib\Entities\Appointment::query( 'a' )
174
+ ->select( 'SUM(ca.number_of_persons) AS total_number_of_persons,
175
+ a.staff_id,
176
+ a.staff_any,
177
+ a.service_id,
178
+ a.custom_service_name,
179
+ a.custom_service_price,
180
+ a.start_date,
181
+ a.end_date,
182
+ a.internal_note,
183
+ a.series_id,
184
+ a.location_id' )
185
+ ->leftJoin( 'CustomerAppointment', 'ca', 'ca.appointment_id = a.id' )
186
+ ->leftJoin( 'StaffService', 'ss', 'ss.staff_id = a.staff_id AND ss.service_id = a.service_id AND ss.location_id = a.location_id' )
187
+ ->where( 'a.id', $appointment->getId() );
188
+ if ( Lib\Config::groupBookingActive() ) {
189
+ $query->addSelect( 'ss.capacity_min AS min_capacity, ss.capacity_max AS max_capacity' );
190
+ } else {
191
+ $query->addSelect( '1 AS min_capacity, 1 AS max_capacity' );
192
+ }
193
+
194
+ $info = $query->fetchRow();
195
+ $response['data']['total_number_of_persons'] = $info['total_number_of_persons'];
196
+ $response['data']['min_capacity'] = $info['min_capacity'];
197
+ $response['data']['max_capacity'] = $info['max_capacity'];
198
+ $response['data']['start_date'] = $info['start_date'];
199
+ $response['data']['end_date'] = $info['end_date'];
200
+ $response['data']['start_time'] = $info['start_date'] ?
201
+ array(
202
+ 'value' => date( 'H:i', strtotime( $info['start_date'] ) ),
203
+ 'title' => Lib\Utils\DateTime::formatTime( $info['start_date'] ),
204
+ ) : null;
205
+ $response['data']['end_time'] = $info['end_date'] ?
206
+ array(
207
+ 'value' => date( 'H:i', strtotime( $info['end_date'] ) ),
208
+ 'title' => Lib\Utils\DateTime::formatTime( $info['end_date'] ),
209
+ ) : null;
210
+ $response['data']['staff_id'] = $info['staff_id'];
211
+ $response['data']['staff_any'] = (int) $info['staff_any'];
212
+ $response['data']['service_id'] = $info['service_id'];
213
+ $response['data']['custom_service_name'] = $info['custom_service_name'];
214
+ $response['data']['custom_service_price'] = (float) $info['custom_service_price'];
215
+ $response['data']['internal_note'] = $info['internal_note'];
216
+ $response['data']['series_id'] = $info['series_id'];
217
+ $response['data']['location_id'] = $info['location_id'];
218
+
219
+ $customers = Lib\Entities\CustomerAppointment::query( 'ca' )
220
+ ->select( 'ca.id,
221
+ ca.customer_id,
222
+ ca.package_id,
223
+ ca.custom_fields,
224
+ ca.extras,
225
+ ca.number_of_persons,
226
+ ca.notes,
227
+ ca.status,
228
+ ca.payment_id,
229
+ ca.compound_service_id,
230
+ ca.compound_token,
231
+ ca.time_zone,
232
+ ca.time_zone_offset,
233
+ p.paid AS payment,
234
+ p.total AS payment_total,
235
+ p.type AS payment_type,
236
+ p.details AS payment_details,
237
+ p.status AS payment_status' )
238
+ ->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
239
+ ->where( 'ca.appointment_id', $appointment->getId() )
240
+ ->fetchArray();
241
+ foreach ( $customers as $customer ) {
242
+ $payment_title = '';
243
+ if ( $customer['payment'] !== null ) {
244
+ $payment_title = Lib\Utils\Price::format( $customer['payment'] );
245
+ if ( $customer['payment'] != $customer['payment_total'] ) {
246
+ $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Price::format( $customer['payment_total'] ) );
247
+ }
248
+ $payment_title .= sprintf(
249
+ ' %s <span%s>%s</span>',
250
+ Lib\Entities\Payment::typeToString( $customer['payment_type'] ),
251
+ $customer['payment_status'] == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
252
+ Lib\Entities\Payment::statusToString( $customer['payment_status'] )
253
+ );
254
+ }
255
+ $compound_service = '';
256
+ if ( $customer['compound_service_id'] !== null ) {
257
+ $service = new Lib\Entities\Service();
258
+ if ( $service->load( $customer['compound_service_id'] ) ) {
259
+ $compound_service = $service->getTranslatedTitle();
260
+ }
261
+ }
262
+ $custom_fields = (array) json_decode( $customer['custom_fields'], true );
263
+ $response['data']['customers'][] = array(
264
+ 'id' => $customer['customer_id'],
265
+ 'ca_id' => $customer['id'],
266
+ 'package_id' => $customer['package_id'],
267
+ 'compound_service' => $compound_service,
268
+ 'compound_token' => $customer['compound_token'],
269
+ 'custom_fields' => $custom_fields,
270
+ 'files' => Lib\Proxy\Files::getFileNamesForCustomFields( $custom_fields ),
271
+ 'extras' => (array) json_decode( $customer['extras'], true ),
272
+ 'number_of_persons' => $customer['number_of_persons'],
273
+ 'notes' => $customer['notes'],
274
+ 'payment_id' => $customer['payment_id'],
275
+ 'payment_type' => $customer['payment'] != $customer['payment_total'] ? 'partial' : 'full',
276
+ 'payment_title' => $payment_title,
277
+ 'status' => $customer['status'],
278
+ 'timezone' => Lib\Proxy\Pro::getCustomerTimezone( $customer['time_zone'], $customer['time_zone_offset'] ),
279
+ );
280
+ }
281
+ }
282
+
283
+ wp_send_json( $response );
284
+ }
285
+
286
+ /**
287
+ * Save appointment form (for both create and edit).
288
+ */
289
+ public static function saveAppointmentForm()
290
+ {
291
+ $response = array( 'success' => false );
292
+
293
+ $appointment_id = (int) self::parameter( 'id', 0 );
294
+ $staff_id = (int) self::parameter( 'staff_id', 0 );
295
+ $service_id = (int) self::parameter( 'service_id', -1 );
296
+ $custom_service_name = trim( self::parameter( 'custom_service_name' ) );
297
+ $custom_service_price = trim( self::parameter( 'custom_service_price' ) );
298
+ $location_id = (int) self::parameter( 'location_id', 0 );
299
+ $skip_date = self::parameter( 'skip_date', 0 );
300
+ $start_date = self::parameter( 'start_date' );
301
+ $end_date = self::parameter( 'end_date' );
302
+ $repeat = json_decode( self::parameter( 'repeat', '[]' ), true );
303
+ $schedule = self::parameter( 'schedule', array() );
304
+ $customers = json_decode( self::parameter( 'customers', '[]' ), true );
305
+ $notification = self::parameter( 'notification', 'no' );
306
+ $internal_note = self::parameter( 'internal_note' );
307
+ $created_from = self::parameter( 'created_from' );
308
+
309
+ if ( ! $service_id ) {
310
+ // Custom service.
311
+ $service_id = null;
312
+ }
313
+ if ( $service_id || $custom_service_name == '' ) {
314
+ $custom_service_name = null;
315
+ }
316
+ if ( $service_id || $custom_service_price == '' ) {
317
+ $custom_service_price = null;
318
+ }
319
+ if ( ! $location_id ) {
320
+ $location_id = null;
321
+ }
322
+
323
+ // Check for errors.
324
+ if ( ! $skip_date ) {
325
+ if ( ! $start_date ) {
326
+ $response['errors']['time_interval'] = __( 'Start time must not be empty', 'bookly' );
327
+ } elseif ( ! $end_date ) {
328
+ $response['errors']['time_interval'] = __( 'End time must not be empty', 'bookly' );
329
+ } elseif ( $start_date == $end_date ) {
330
+ $response['errors']['time_interval'] = __( 'End time must not be equal to start time', 'bookly' );
331
+ }
332
+ }
333
+
334
+ if ( $service_id == -1 ) {
335
+ $response['errors']['service_required'] = true;
336
+ } else if ( $service_id === null && $custom_service_name === null ) {
337
+ $response['errors']['custom_service_name_required'] = true;
338
+ }
339
+ $total_number_of_persons = 0;
340
+ $max_extras_duration = 0;
341
+ foreach ( $customers as $i => $customer ) {
342
+ if ( $customer['status'] == Lib\Entities\CustomerAppointment::STATUS_PENDING ||
343
+ $customer['status'] == Lib\Entities\CustomerAppointment::STATUS_APPROVED
344
+ ) {
345
+ $total_number_of_persons += $customer['number_of_persons'];
346
+ $extras_duration = Lib\Proxy\ServiceExtras::getTotalDuration( $customer['extras'] );
347
+ if ( $extras_duration > $max_extras_duration ) {
348
+ $max_extras_duration = $extras_duration;
349
+ }
350
+ }
351
+ $customers[ $i ]['created_from'] = ( $created_from == 'backend' ) ? 'backend' : 'frontend';
352
+ }
353
+ if ( $service_id ) {
354
+ $staff_service = new Lib\Entities\StaffService();
355
+ $staff_service->loadBy( array(
356
+ 'staff_id' => $staff_id,
357
+ 'service_id' => $service_id,
358
+ 'location_id' => $location_id ?: null,
359
+ ) );
360
+ if ( ! $staff_service->isLoaded() ) {
361
+ $staff_service->loadBy( array(
362
+ 'staff_id' => $staff_id,
363
+ 'service_id' => $service_id,
364
+ 'location_id' => null,
365
+ ) );
366
+ }
367
+ if ( $total_number_of_persons > $staff_service->getCapacityMax() ) {
368
+ $response['errors']['overflow_capacity'] = sprintf(
369
+ __( 'The number of customers should not be more than %d', 'bookly' ),
370
+ $staff_service->getCapacityMax()
371
+ );
372
+ }
373
+ }
374
+
375
+ // If no errors then try to save the appointment.
376
+ if ( ! isset ( $response['errors'] ) ) {
377
+ $duration = Lib\Slots\DatePoint::fromStr( $end_date )->diff( Lib\Slots\DatePoint::fromStr( $start_date ) );
378
+ if ( ! $skip_date && $repeat['enabled'] ) {
379
+ // Series.
380
+ if ( ! empty ( $schedule ) ) {
381
+ // Create new series.
382
+ $series = new Lib\Entities\Series();
383
+ $series
384
+ ->setRepeat( self::parameter( 'repeat' ) )
385
+ ->setToken( Lib\Utils\Common::generateToken( get_class( $series ), 'token' ) )
386
+ ->save();
387
+
388
+ if ( $notification != 'no' ) {
389
+ // Create order per each customer to send notifications.
390
+ /** @var DataHolders\Order[] $orders */
391
+ $orders = array();
392
+ foreach ( $customers as $customer ) {
393
+ $order = DataHolders\Order::create( Lib\Entities\Customer::find( $customer['id'] ) )
394
+ ->addItem( 0, DataHolders\Series::create( $series ) )
395
+ ;
396
+ $orders[ $customer['id'] ] = $order;
397
+ }
398
+ }
399
+
400
+ if ( $service_id ) {
401
+ $service = Lib\Entities\Service::find( $service_id );
402
+ } else {
403
+ $service = new Lib\Entities\Service();
404
+ $service
405
+ ->setTitle( $custom_service_name )
406
+ ->setDuration( $duration )
407
+ ->setPrice( $custom_service_price )
408
+ ;
409
+ }
410
+
411
+ foreach ( $schedule as $slot ) {
412
+ $slot = json_decode( $slot );
413
+ $appointment = new Lib\Entities\Appointment();
414
+ $appointment
415
+ ->setSeries( $series )
416
+ ->setLocationId( $location_id )
417
+ ->setStaffId( $staff_id )
418
+ ->setServiceId( $service_id )
419
+ ->setCustomServiceName( $custom_service_name )
420
+ ->setCustomServicePrice( $custom_service_price )
421
+ ->setStartDate( $slot[0][2] )
422
+ ->setEndDate( Lib\Slots\DatePoint::fromStr( $slot[0][2] )->modify( $duration )->format( 'Y-m-d H:i:s' ) )
423
+ ->setInternalNote( $internal_note )
424
+ ->setExtrasDuration( $max_extras_duration )
425
+ ;
426
+
427
+ if ( $appointment->save() !== false ) {
428
+ // Save customer appointments.
429
+ $ca_list = $appointment->saveCustomerAppointments( $customers );
430
+ // Google Calendar.
431
+ Lib\Proxy\Pro::syncGoogleCalendarEvent( $appointment );
432
+ // Waiting list.
433
+ Lib\Proxy\WaitingList::handleParticipantsChange( $appointment );
434
+
435
+ if ( $notification != 'no' ) {
436
+ foreach ( $ca_list as $ca ) {
437
+ $item = DataHolders\Simple::create( $ca )
438
+ ->setService( $service )
439
+ ->setAppointment( $appointment )
440
+ ;
441
+ $orders[ $ca->getCustomerId() ]->getItem( 0 )->addItem( $item );
442
+ }
443
+ }
444
+ }
445
+ }
446
+ foreach ( $customers as $customer ) {
447
+ if ( $customer['payment_create'] === true ) {
448
+ Proxy\RecurringAppointments::createBackendPayment( $series, $customer );
449
+ }
450
+ }
451
+
452
+ if ( $notification != 'no' ) {
453
+ foreach ( $orders as $order ) {
454
+ Lib\Proxy\RecurringAppointments::sendRecurring( $order->getItem( 0 ), $order );
455
+ }
456
+ }
457
+ }
458
+ $response['success'] = true;
459
+ $response['data'] = array( 'staffId' => $staff_id ); // make FullCalendar refetch events
460
+ } else {
461
+ // Single appointment.
462
+ $appointment = new Lib\Entities\Appointment();
463
+ if ( $appointment_id ) {
464
+ // Edit.
465
+ $appointment->load( $appointment_id );
466
+ if ( $appointment->getStaffId() != $staff_id ) {
467
+ $appointment->setStaffAny( 0 );
468
+ }
469
+ }
470
+ $appointment
471
+ ->setLocationId( $location_id )
472
+ ->setStaffId( $staff_id )
473
+ ->setServiceId( $service_id )
474
+ ->setCustomServiceName( $custom_service_name )
475
+ ->setCustomServicePrice( $custom_service_price )
476
+ ->setStartDate( $skip_date ? null : $start_date )
477
+ ->setEndDate( $skip_date ? null : $end_date )
478
+ ->setInternalNote( $internal_note )
479
+ ->setExtrasDuration( $max_extras_duration );
480
+
481
+ if ( $appointment->save() !== false ) {
482
+ // Save customer appointments.
483
+ $ca_status_changed = $appointment->saveCustomerAppointments( $customers );
484
+
485
+ if ( $appointment->getSeriesId() ) {
486
+ foreach ( $customers as $customer ) {
487
+ if ( $customer['payment_create'] === true ) {
488
+ Proxy\RecurringAppointments::createBackendPayment( Lib\Entities\Series::find( $appointment->getSeriesId() ), $customer );
489
+ }
490
+ }
491
+ }
492
+
493
+ // Google Calendar.
494
+ Lib\Proxy\Pro::syncGoogleCalendarEvent( $appointment );
495
+ // Waiting list.
496
+ Lib\Proxy\WaitingList::handleParticipantsChange( $appointment );
497
+
498
+ // Send notifications.
499
+ if ( $notification == 'changed_status' ) {
500
+ foreach ( $ca_status_changed as $ca ) {
501
+ Lib\Notifications\Sender::sendSingle( DataHolders\Simple::create( $ca )->setAppointment( $appointment ) );
502
+ }
503
+ } elseif ( $notification == 'all' ) {
504
+ $ca_list = $appointment->getCustomerAppointments( true );
505
+ foreach ( $ca_status_changed as $ca ) {
506
+ // The value "just_created" was initialized for the objects of this array
507
+ Lib\Notifications\Sender::sendSingle( DataHolders\Simple::create( $ca )->setAppointment( $appointment ) );
508
+ unset( $ca_list[ $ca->getId() ] );
509
+ }
510
+ foreach ( $ca_list as $ca ) {
511
+ Lib\Notifications\Sender::sendSingle( DataHolders\Simple::create( $ca )->setAppointment( $appointment ) );
512
+ }
513
+ }
514
+
515
+ $response['success'] = true;
516
+ $response['data'] = self::_getAppointmentForFC( $staff_id, $appointment->getId() );
517
+ } else {
518
+ $response['errors'] = array( 'db' => __( 'Could not save appointment in database.', 'bookly' ) );
519
+ }
520
+ }
521
+ }
522
+ update_user_meta( get_current_user_id(), 'bookly_appointment_form_send_notifications', $notification );
523
+
524
+ wp_send_json( $response );
525
+ }
526
+
527
+ /**
528
+ * Check whether appointment settings produce errors.
529
+ */
530
+ public static function checkAppointmentErrors()
531
+ {
532
+ $start_date = self::parameter( 'start_date' );
533
+ $end_date = self::parameter( 'end_date' );
534
+ $staff_id = (int) self::parameter( 'staff_id' );
535
+ $service_id = (int) self::parameter( 'service_id' );
536
+ $appointment_id = (int) self::parameter( 'appointment_id' );
537
+ $appointment_duration = strtotime( $end_date ) - strtotime( $start_date );
538
+ $customers = json_decode( self::parameter( 'customers', '[]' ), true );
539
+ $service = Lib\Entities\Service::find( $service_id );
540
+ $service_duration = $service ? $service->getDuration() : 0;
541
+
542
+ $result = array(
543
+ 'date_interval_not_available' => false,
544
+ 'date_interval_warning' => false,
545
+ 'interval_not_in_staff_schedule' => false,
546
+ 'interval_not_in_service_schedule' => false,
547
+ 'customers_appointments_limit' => array(),
548
+ );
549
+
550
+ $max_extras_duration = 0;
551
+ foreach ( $customers as $customer ) {
552
+ if ( $customer['status'] == Lib\Entities\CustomerAppointment::STATUS_PENDING ||
553
+ $customer['status'] == Lib\Entities\CustomerAppointment::STATUS_APPROVED
554
+ ) {
555
+ $extras_duration = Lib\Proxy\ServiceExtras::getTotalDuration( $customer['extras'] );
556
+ if ( $extras_duration > $max_extras_duration ) {
557
+ $max_extras_duration = $extras_duration;
558
+ }
559
+ }
560
+ }
561
+ if ( $start_date && $end_date ) {
562
+ $total_end_date = $end_date;
563
+ if ( $max_extras_duration > 0 ) {
564
+ $total_end_date = date_create( $end_date )->modify( '+' . $max_extras_duration . ' sec' )->format( 'Y-m-d H:i:s' );
565
+ }
566
+ if ( ! self::_dateIntervalIsAvailableForAppointment( $start_date, $total_end_date, $staff_id, $appointment_id ) ) {
567
+ $result['date_interval_not_available'] = true;
568
+ }
569
+
570
+ // Check if selected interval fit into staff schedule.
571
+ $interval_valid = true;
572
+ if ( $staff_id && $start_date ) {
573
+ $staff = Lib\Entities\Staff::find( $staff_id );
574
+ if ( $service_duration >= DAY_IN_SECONDS ) {
575
+ // For services with duration 24+ hours check holidays and days off
576
+ for ( $day = 0; $day < $service_duration / DAY_IN_SECONDS; $day ++ ) {
577
+ $work_date = date_create( $start_date )->modify( sprintf( '%s days', $day ) );
578
+ $week_day = $work_date->format( 'w' ) + 1;
579
+ // Check staff schedule for days off
580
+ if ( $staff->isOnHoliday( $work_date ) ||
581
+ ! Lib\Entities\StaffScheduleItem::query()
582
+ ->select( 'id' )
583
+ ->where( 'staff_id', $staff_id )
584
+ ->where( 'day_index', $week_day )
585
+ ->whereNot( 'start_time', null )
586
+ ->fetchRow()
587
+ ) {
588
+ $interval_valid = false;
589
+ break;
590
+ }
591
+ }
592
+ } else {
593
+ // Check day before and current day to get night schedule from previous day.
594
+ $interval_valid = false;
595
+ for ( $day = 0; $day <= 1; $day ++ ) {
596
+ $day_start_date = date_create( $start_date )->modify( sprintf( '%s days', $day - 1 ) );
597
+ $day_end_date = date_create( $end_date )->modify( sprintf( '%s days', $day - 1 ) );
598
+ if ( ! $staff->isOnHoliday( $day_start_date ) ) {
599
+ $day_start_hour = ( 1 - $day ) * 24 + $day_start_date->format( 'G' );
600
+ $day_end_hour = ( 1 - $day ) * 24 + $day_end_date->format( 'G' );
601
+ $day_start_time = sprintf( '%02d:%02d:00', $day_start_hour, $day_start_date->format( 'i' ) );
602
+ $day_end_time = sprintf( '%02d:%02d:00', $day_end_hour >= $day_start_hour ? $day_end_hour : $day_end_hour + 24, $day_end_date->format( 'i' ) );
603
+
604
+ $special_days = (array) Lib\Proxy\SpecialDays::getSchedule( array( $staff_id ), $day_start_date, $day_start_date );
605
+ if ( ! empty( $special_days ) ) {
606
+ // Check if interval fit into special day schedule.
607
+ $special_day = current( $special_days );
608
+ if ( ( $special_day['start_time'] <= $day_start_time ) && ( $special_day['end_time'] >= $day_end_time ) ) {
609
+ if ( ! ( $special_day['break_start'] && ( $special_day['break_start'] < $day_end_time ) && ( $special_day['break_end'] > $day_start_time ) ) ) {
610
+ $interval_valid = true;
611
+ break;
612
+ }
613
+ }
614
+ } else {
615
+ // Check if interval fit into regular staff working schedule.
616
+ $week_day = $day_start_date->format( 'w' ) + 1;
617
+ $ssi = Lib\Entities\StaffScheduleItem::query()
618
+ ->select( 'id' )
619
+ ->where( 'staff_id', $staff_id )
620
+ ->where( 'day_index', $week_day )
621
+ ->whereNot( 'start_time', null )
622
+ ->whereLte( 'start_time', $day_start_time )
623
+ ->whereGte( 'end_time', $day_end_time )
624
+ ->fetchRow();
625
+ if ( $ssi ) {
626
+ // Check if interval not intercept with breaks.
627
+ if ( Lib\Entities\ScheduleItemBreak::query()
628
+ ->where( 'staff_schedule_item_id', $ssi['id'] )
629
+ ->whereLt( 'start_time', $day_end_time )
630
+ ->whereGt( 'end_time', $day_start_time )
631
+ ->count() == 0
632
+ ) {
633
+ $interval_valid = true;
634
+ break;
635
+ }
636
+ }
637
+ }
638
+ }
639
+ }
640
+ }
641
+ }
642
+ if ( ! $interval_valid ) {
643
+ $result['interval_not_in_staff_schedule'] = true;
644
+ }
645
+ if ( $service ) {
646
+ if ( $service_duration >= DAY_IN_SECONDS ) {
647
+ // For services with duration 24+ hours check days off
648
+ $service_schedule = (array) Lib\Proxy\ServiceSchedule::getSchedule( $service_id );
649
+ $interval_valid = true;
650
+
651
+ // Check service schedule and service special days
652
+ for ( $day = 0; $day < $service_duration / DAY_IN_SECONDS; $day ++ ) {
653
+ $work_date = date_create( $start_date )->modify( sprintf( '%s days', $day ) );
654
+ $week_day = $work_date->format( 'w' ) + 1;
655
+ // Check service schedule for days off
656
+ $service_schedule_valid = true;
657
+ if ( Lib\Config::serviceScheduleActive() ) {
658
+ $service_schedule_valid = false;
659
+ foreach ( $service_schedule as $day_schedule ) {
660
+ if ( $day_schedule['day_index'] == $week_day && $day_schedule['start_time'] ) {
661
+ $service_schedule_valid = true;
662
+ break;
663
+ }
664
+ }
665
+ }
666
+ if ( ! $service_schedule_valid ) {
667
+ $interval_valid = false;
668
+ break;
669
+ }
670
+ // Check service special days for days off
671
+ $service_special_days_valid = true;
672
+ if ( Lib\Config::specialDaysActive() ) {
673
+ $special_days = (array) Lib\Proxy\SpecialDays::getServiceSchedule( $service_id, $work_date, $work_date );
674
+ if ( ! empty( $special_days ) ) {
675
+ $service_special_days_valid = false;
676
+ $schedule = current( $special_days );
677
+ if ( $schedule['start_time'] ) {
678
+ $service_special_days_valid = true;
679
+ }
680
+ }
681
+ }
682
+ if ( ! $service_special_days_valid ) {
683
+ $interval_valid = false;
684
+ break;
685
+ }
686
+ }
687
+ if ( ! $interval_valid ) {
688
+ $result['interval_not_in_service_schedule'] = true;
689
+ }
690
+ // Check staff schedule and staff special days
691
+ $interval_valid = true;
692
+ for ( $day = 0; $day < $service_duration / DAY_IN_SECONDS; $day ++ ) {
693
+ $work_date = date_create( $start_date )->modify( sprintf( '%s days', $day ) );
694
+ $week_day = $work_date->format( 'w' ) + 1;
695
+ if ( Lib\Entities\StaffScheduleItem::query()
696
+ ->where( 'staff_id', $staff_id )
697
+ ->where( 'day_index', $week_day )
698
+ ->whereNot( 'start_time', null )
699
+ ->count() == 0
700
+ ) {
701
+ $interval_valid = false;
702
+ break;
703
+ }
704
+ }
705
+ if ( ! $interval_valid ) {
706
+ $result['interval_not_in_staff_schedule'] = true;
707
+ }
708
+ } else {
709
+ // Check if selected interval fit into service schedule.
710
+ $interval_valid = false;
711
+ // Check day before and current day to get night schedule from previous day.
712
+ for ( $day = 0; $day <= 1; $day ++ ) {
713
+ $day_start_date = date_create( $start_date )->modify( sprintf( '%s days', $day - 1 ) );
714
+ $day_end_date = date_create( $end_date )->modify( sprintf( '%s days', $day - 1 ) );
715
+
716
+ $day_start_hour = ( 1 - $day ) * 24 + $day_start_date->format( 'G' );
717
+ $day_end_hour = ( 1 - $day ) * 24 + $day_end_date->format( 'G' );
718
+ $day_start_time = sprintf( '%02d:%02d:00', $day_start_hour, $day_start_date->format( 'i' ) );
719
+ $day_end_time = sprintf( '%02d:%02d:00', $day_end_hour >= $day_start_hour ? $day_end_hour : $day_end_hour + 24, $day_end_date->format( 'i' ) );
720
+
721
+ $special_days = (array) Lib\Proxy\SpecialDays::getServiceSchedule( $service_id, $day_start_date, $day_start_date );
722
+ if ( ! empty( $special_days ) ) {
723
+ // Check if interval fit into special day schedule.
724
+ $special_day = current( $special_days );
725
+ if ( ( $special_day['start_time'] <= $day_start_time ) && ( $special_day['end_time'] >= $day_end_time ) ) {
726
+ if ( ! ( $special_day['break_start'] && ( $special_day['break_start'] < $day_end_time ) && ( $special_day['break_end'] > $day_start_time ) ) ) {
727
+ $interval_valid = true;
728
+ break;
729
+ }
730
+ }
731
+ } else {
732
+ // Check if interval fit into service working schedule.
733
+ $schedule = (array) Lib\Proxy\ServiceSchedule::getSchedule( $service_id );
734
+ if ( ! empty ( $schedule ) ) {
735
+ $week_day = $day_start_date->format( 'w' ) + 1;
736
+ foreach ( $schedule as $schedule_day ) {
737
+ if ( $schedule_day['day_index'] == $week_day ) {
738
+ if ( ( $schedule_day['start_time'] <= $day_start_time ) && ( $schedule_day['end_time'] >= $day_end_time ) ) {
739
+ $interval_valid = true;
740
+ if ( $schedule_day['break_start'] && ( $schedule_day['break_start'] < $day_end_time ) && ( $schedule_day['break_end'] > $day_start_time ) ) {
741
+ $interval_valid = false;
742
+ break;
743
+ }
744
+ }
745
+ }
746
+ }
747
+ } else {
748
+ $interval_valid = true;
749
+ break;
750
+ }
751
+ }
752
+ }
753
+ if ( ! $interval_valid ) {
754
+ $result['interval_not_in_service_schedule'] = true;
755
+ }
756
+ // Service duration interval is not equal to.
757
+ $result['date_interval_warning'] = ! ( $appointment_duration >= $service->getMinDuration() && $appointment_duration <= $service->getMaxDuration() && ( $service_duration == 0 || $appointment_duration % $service_duration == 0 ) );
758
+ }
759
+
760
+ // Check customers for appointments limit
761
+ foreach ( $customers as $index => $customer ) {
762
+ if ( $service->appointmentsLimitReached( $customer['id'], array( $start_date ) ) ) {
763
+ $customer_error = Lib\Entities\Customer::find( $customer['id'] );
764
+ $result['customers_appointments_limit'][] = sprintf( __( '%s has reached the limit of bookings for this service', 'bookly' ), $customer_error->getFullName() );
765
+ }
766
+ }
767
+ }
768
+ }
769
+
770
+ wp_send_json( $result );
771
+ }
772
+
773
+ /**
774
+ * Get appointment for FullCalendar.
775
+ *
776
+ * @param integer $staff_id
777
+ * @param int $appointment_id
778
+ * @return array
779
+ */
780
+ private static function _getAppointmentForFC( $staff_id, $appointment_id )
781
+ {
782
+ $query = Lib\Entities\Appointment::query( 'a' )
783
+ ->where( 'a.id', $appointment_id );
784
+
785
+ $appointments = Calendar\Page::buildAppointmentsForFC( $staff_id, $query );
786
+
787
+ return $appointments[0];
788
+ }
789
+
790
+ /**
791
+ * Check whether interval is available for given appointment.
792
+ *
793
+ * @param $start_date
794
+ * @param $end_date
795
+ * @param $staff_id
796
+ * @param $appointment_id
797
+ * @return bool
798
+ */
799
+ private static function _dateIntervalIsAvailableForAppointment( $start_date, $end_date, $staff_id, $appointment_id )
800
+ {
801
+ return Lib\Entities\Appointment::query( 'a' )
802
+ ->whereNot( 'a.id', $appointment_id )
803
+ ->where( 'a.staff_id', $staff_id )
804
+ ->whereLt( 'a.start_date', $end_date )
805
+ ->whereGt( 'a.end_date', $start_date )
806
+ ->count() == 0;
807
+ }
808
+ }
backend/{modules/calendar/Components.php → components/dialogs/appointment/edit/Dialog.php} RENAMED
@@ -1,28 +1,27 @@
1
  <?php
2
- namespace BooklyLite\Backend\Modules\Calendar;
3
 
4
- use BooklyLite\Lib;
5
 
6
  /**
7
- * Class Components
8
- * @package BooklyLite\Backend\Modules\Calendar
9
  */
10
- class Components extends Lib\Base\Components
11
  {
12
  /**
13
- * Render appointment dialog.
14
- * @throws \Exception
15
  */
16
- public function renderAppointmentDialog()
17
  {
18
  global $wp_locale;
19
 
20
- $this->enqueueStyles( array(
21
- 'backend' => array( 'css/jquery-ui-theme/jquery-ui.min.css', 'css/select2.min.css' ),
22
  'frontend' => array( 'css/ladda.min.css', ),
23
  ) );
24
 
25
- $this->enqueueScripts( array(
26
  'backend' => array(
27
  'js/angular.min.js' => array( 'jquery' ),
28
  'js/angular-ui-date-0.0.8.js' => array( 'bookly-angular.min.js' ),
@@ -35,11 +34,11 @@ class Components extends Lib\Base\Components
35
  'js/ladda.min.js' => array( 'jquery' ),
36
  ),
37
  'module' => array(
38
- 'js/ng-appointment_dialog.js' => array( 'bookly-angular-ui-date-0.0.8.js', 'jquery-ui-datepicker' ),
39
  )
40
  ) );
41
 
42
- wp_localize_script( 'bookly-ng-appointment_dialog.js', 'BooklyL10nAppDialog', array(
43
  'csrf_token' => Lib\Utils\Common::getCsrfToken(),
44
  'dateOptions' => array(
45
  'dateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_JQUERY_DATEPICKER ),
@@ -58,28 +57,8 @@ class Components extends Lib\Base\Components
58
  ),
59
  ) );
60
 
61
- $this->render( '_appointment_dialog' );
62
- }
63
-
64
- /**
65
- * Render delete appointment dialog
66
- */
67
- public function renderDeleteDialog()
68
- {
69
- $this->enqueueStyles( array(
70
- 'frontend' => array( 'css/ladda.min.css', ),
71
- ) );
72
 
73
- $this->enqueueScripts( array(
74
- 'frontend' => array(
75
- 'js/spin.min.js' => array( 'jquery' ),
76
- 'js/ladda.min.js' => array( 'jquery' ),
77
- ),
78
- 'module' => array(
79
- 'js/delete_dialog.js' => array( 'jquery' ),
80
- ),
81
- ) );
82
- $this->render( '_delete_dialog' );
83
  }
84
-
85
  }
1
  <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\Edit;
3
 
4
+ use Bookly\Lib;
5
 
6
  /**
7
+ * Class Edit
8
+ * @package Bookly\Backend\Components\Dialogs\Appointment\Edit
9
  */
10
+ class Dialog extends Lib\Base\Component
11
  {
12
  /**
13
+ * Render create/edit appointment dialog.
 
14
  */
15
+ public static function render()
16
  {
17
  global $wp_locale;
18
 
19
+ self::enqueueStyles( array(
20
+ 'backend' => array( 'css/jquery-ui-theme/jquery-ui.min.css', 'css/select2.min.css', 'css/fontawesome-all.min.css' ),
21
  'frontend' => array( 'css/ladda.min.css', ),
22
  ) );
23
 
24
+ self::enqueueScripts( array(
25
  'backend' => array(
26
  'js/angular.min.js' => array( 'jquery' ),
27
  'js/angular-ui-date-0.0.8.js' => array( 'bookly-angular.min.js' ),
34
  'js/ladda.min.js' => array( 'jquery' ),
35
  ),
36
  'module' => array(
37
+ 'js/ng-appointment.js' => array( 'bookly-angular-ui-date-0.0.8.js', 'jquery-ui-datepicker' ),
38
  )
39
  ) );
40
 
41
+ wp_localize_script( 'bookly-ng-appointment.js', 'BooklyL10nAppDialog', array(
42
  'csrf_token' => Lib\Utils\Common::getCsrfToken(),
43
  'dateOptions' => array(
44
  'dateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_JQUERY_DATEPICKER ),
57
  ),
58
  ) );
59
 
60
+ Proxy\Shared::enqueueAssets();
 
 
 
 
 
 
 
 
 
 
61
 
62
+ self::renderTemplate( 'edit' );
 
 
 
 
 
 
 
 
 
63
  }
 
64
  }
backend/components/dialogs/appointment/edit/proxy/Pro.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\Edit\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Components\Dialogs\Appointment\Edit\Proxy
9
+ *
10
+ * @method static array addCustomService( array $services ) Add custom service to given array of services.
11
+ * @method static void renderAttachPaymentButton() Render attach payment button.
12
+ * @method static void renderCustomServiceFields() Render custom service name and price fields.
13
+ */
14
+ abstract class Pro extends Lib\Base\Proxy
15
+ {
16
+
17
+ }
backend/components/dialogs/appointment/edit/proxy/RecurringAppointments.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\Edit\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class RecurringAppointments
8
+ * @package Bookly\Backend\Components\Dialogs\Appointment\Edit\Proxy
9
+ *
10
+ * @method static void createBackendPayment( Lib\Entities\Series $series, array $customer ) Create payment for series.
11
+ * @method static void renderSchedule() Render schedule in edit appointment dialog.
12
+ * @method static void renderSubForm() Add Recurring sub form in edit appointment dialog.
13
+ */
14
+ abstract class RecurringAppointments extends Lib\Base\Proxy
15
+ {
16
+
17
+ }
backend/components/dialogs/appointment/edit/proxy/Shared.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\Edit\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Shared
8
+ * @package Bookly\Backend\Components\Dialogs\Appointment\Edit\Proxy
9
+ *
10
+ * @method static array prepareDataForPackage( array $result )
11
+ * @method static void renderAppointmentDialogCustomersList() Render content in AppointmentForm for customers.
12
+ * @method static void renderAppointmentDialogFooter() Render buttons in appointments dialog footer.
13
+ * @method static void renderComponents()
14
+ * @method static void enqueueAssets()
15
+ */
16
+ abstract class Shared extends Lib\Base\Proxy
17
+ {
18
+
19
+ }
backend/components/dialogs/appointment/edit/proxy/Tasks.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Appointment\Edit\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class RecurringAppointments
8
+ * @package Bookly\Backend\Components\Dialogs\Appointment\Edit\Proxy
9
+ *
10
+ * @method static void renderSkipDate()
11
+ */
12
+ abstract class Tasks extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/{modules/calendar/resources/js/ng-appointment_dialog.js → components/dialogs/appointment/edit/resources/js/ng-appointment.js} RENAMED
@@ -5,33 +5,35 @@
5
  /**
6
  * DataSource service.
7
  */
8
- module.factory('dataSource', function($q, $rootScope, $filter) {
9
  var ds = {
10
  loaded : false,
11
  data : {
12
- staff : [],
13
- customers : [],
14
- start_time : [],
15
- end_time : [],
16
- time_interval : 900,
17
- status : {
18
- items: [],
19
- default: null
 
20
  }
21
  },
22
  form : {
23
- screen : null,
24
- id : null,
25
- staff : null,
26
- staff_any : null,
27
- service : null,
28
- custom_service_name : null,
29
- custom_service_price : null,
30
- location : null,
31
- date : null,
32
- start_time : null,
33
- end_time : null,
34
- repeat : {
 
35
  enabled : null,
36
  repeat : null,
37
  daily : { every : null },
@@ -67,6 +69,7 @@
67
  if (data.staff.length) {
68
  ds.form.staff = data.staff[0];
69
  }
 
70
  ds.form.start_time = data.start_time[0];
71
  ds.form.end_time = data.end_time[1];
72
  deferred.resolve();
@@ -117,13 +120,12 @@
117
  }
118
  return result;
119
  },
120
- findTime : function(source, date) {
121
  var result = null,
122
- value_to_find = $filter('date')(date, 'HH:mm'),
123
- time = source == 'start' ? ds.data.start_time : ds.data.end_time;
124
-
125
  jQuery.each(time, function(key, item) {
126
- if (item.value >= value_to_find) {
127
  result = item;
128
  return false;
129
  }
@@ -144,55 +146,107 @@
144
  ds.data.customers.forEach(function(customer) {
145
  customer.custom_fields = [];
146
  customer.extras = [];
147
- customer.status = ds.data.status.default;
148
  customer.number_of_persons = 1;
149
  customer.notes = null;
150
  customer.compound_token = null;
151
  customer.payment_id = null;
152
  customer.payment_type = null;
153
  customer.payment_title = null;
 
 
 
154
  customer.package_id = null;
 
155
  });
156
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  getDataForEndTime : function() {
158
  var result = [];
159
  if (ds.form.start_time) {
160
- var start_time = ds.form.start_time.value.split(':'),
161
- end = (24 + parseInt(start_time[0])) + ':' + start_time[1];
162
- jQuery.each(ds.data.end_time, function(key, item) {
163
- if (item.value > end) {
164
- return false;
 
 
 
 
 
 
 
 
 
165
  }
166
- if (item.value > ds.form.start_time.value) {
167
- result.push(item);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  }
169
- });
170
  }
171
  return result;
172
  },
173
  setEndTimeBasedOnService : function() {
174
- var i = jQuery.inArray(ds.form.start_time, ds.data.start_time),
175
- d = ds.form.service ? ds.form.service.duration : ds.data.time_interval;
176
  if (d < 86400) {
177
- if (i !== -1) {
178
- for (; i < ds.data.end_time.length; ++i) {
179
- d -= ds.data.time_interval;
180
- if (d < 0) {
181
- break;
182
- }
183
- }
184
- ds.form.end_time = ds.data.end_time[i];
185
- }
186
  }
187
  },
188
  getStartAndEndDates : function() {
 
 
 
 
 
 
189
  var start_date = moment(ds.form.date.getTime()),
190
  end_date = moment(ds.form.date.getTime()),
191
  start_time = [0,0],
192
  end_time = [0,0]
193
  ;
194
  if (ds.form.service && ds.form.service.duration >= 86400) {
195
- end_date.add(ds.form.service.duration, 'seconds');
 
 
 
 
 
 
 
 
 
196
  } else {
197
  start_time = ds.form.start_time.value.split(':');
198
  end_time = ds.form.end_time.value.split(':');
@@ -201,7 +255,6 @@
201
  start_date.minutes(start_time[1]);
202
  end_date.hours(end_time[0]);
203
  end_date.minutes(end_time[1]);
204
-
205
  return {
206
  start_date : start_date.format('YYYY-MM-DD HH:mm:00'),
207
  end_date : end_date.format('YYYY-MM-DD HH:mm:00')
@@ -234,6 +287,14 @@
234
  });
235
 
236
  return result;
 
 
 
 
 
 
 
 
237
  }
238
  };
239
 
@@ -243,7 +304,7 @@
243
  /**
244
  * Controller for 'create/edit appointment' dialog form.
245
  */
246
- module.controller('appointmentDialogCtrl', function($scope, $element, dataSource, $filter) {
247
  // Set up initial data.
248
  $scope.$calendar = null;
249
  // Set up data source.
@@ -252,7 +313,7 @@
252
  // Error messages.
253
  $scope.errors = {};
254
  // Callback to be called after editing appointment.
255
- var callback = null;
256
 
257
  /**
258
  * Prepare the form for new event.
@@ -267,6 +328,8 @@
267
  service = staff && staff.services.length == 2 ? staff.services[1] : null,
268
  location = staff && staff.locations.length == 1 ? staff.locations[0] : null
269
  ;
 
 
270
  jQuery.extend($scope.form, {
271
  screen : 'main',
272
  id : null,
@@ -277,8 +340,10 @@
277
  custom_service_price : 0,
278
  location : location,
279
  date : start_date.clone().local().toDate(),
 
280
  start_time : dataSource.findTime('start', start_date.format('HH:mm')),
281
  end_time : null,
 
282
  series_id : null,
283
  repeat : {
284
  enabled : 0,
@@ -307,6 +372,9 @@
307
  $scope.prepareCustomFields();
308
  $scope.dataSource.resetCustomers();
309
  $scope.onRepeatChange();
 
 
 
310
  };
311
 
312
  /**
@@ -320,47 +388,61 @@
320
  function(response) {
321
  $scope.$apply(function($scope) {
322
  if (response.success) {
323
- var start_date = moment(response.data.start_date),
324
- end_date = moment(response.data.end_date),
325
  staff = $scope.dataSource.findStaff(response.data.staff_id);
 
 
326
  jQuery.extend($scope.form, {
327
- screen : 'main',
328
- id : appointment_id,
329
- staff : staff,
330
- staff_any : response.data.staff_any ? staff : null,
331
- service : $scope.dataSource.findService(response.data.staff_id, response.data.service_id),
332
- custom_service_name : response.data.custom_service_name,
333
- custom_service_price : response.data.custom_service_price,
334
- location : $scope.dataSource.findLocation(response.data.staff_id, response.data.location_id),
335
- date : start_date.clone().local().toDate(),
336
- start_time : $scope.dataSource.findTime('start', start_date.format('HH:mm')),
337
- end_time : start_date.format('YYYY-MM-DD') == end_date.format('YYYY-MM-DD')
338
- ? $scope.dataSource.findTime('end', end_date.format('HH:mm'))
339
- : $scope.dataSource.findTime('end', (24 + end_date.hour()) + end_date.format(':mm')),
340
- repeat : {
341
- enabled : 0,
342
- repeat : 'daily',
343
- daily : { every: 1 },
344
- weekly : { on : [] },
345
- biweekly : { on : [] },
346
- monthly : { on : 'day', day : '1', weekday : 'mon' },
347
- until : start_date.clone().add(1, 'month').format('YYYY-MM-DD')
348
  },
349
- schedule : {
350
- items : [],
351
- edit : 0,
352
- page : 0,
353
- another_time : []
354
  },
355
- customers : [],
356
- internal_note : response.data.internal_note,
357
- series_id : response.data.series_id,
358
- expand_customers_list : false
359
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
360
 
361
  $scope.prepareExtras();
362
  $scope.prepareCustomFields();
363
  $scope.dataSource.resetCustomers();
 
364
  $scope.onRepeatChange();
365
 
366
  var customers_ids = [];
@@ -379,11 +461,16 @@
379
  clone.extras = item.extras;
380
  clone.status = item.status;
381
  clone.custom_fields = item.custom_fields;
 
382
  clone.number_of_persons = item.number_of_persons;
 
383
  clone.notes = item.notes;
384
  clone.payment_id = item.payment_id;
385
  clone.payment_type = item.payment_type;
386
  clone.payment_title = item.payment_title;
 
 
 
387
  clone.compound_token = item.compound_token;
388
  clone.compound_service = item.compound_service;
389
  $scope.form.customers.push(clone);
@@ -414,12 +501,13 @@
414
  });
415
  }
416
  customers.push({
417
- id: item.id,
418
- ca_id: item.ca_id,
419
- custom_fields: item.custom_fields,
420
- extras: customer_extras,
421
- number_of_persons: item.number_of_persons,
422
- status: item.status
 
423
  });
424
  });
425
 
@@ -451,9 +539,28 @@
451
  $scope.dataSource.setEndTimeBasedOnService();
452
  $scope.prepareExtras();
453
  $scope.prepareCustomFields();
 
454
  checkAppointmentErrors();
455
  };
456
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
  $scope.onStaffChange = function() {
458
  if ($scope.form.staff.services.length == 2) {
459
  $scope.form.service = $scope.form.staff.services[1];
@@ -462,6 +569,7 @@
462
  $scope.form.service = null;
463
  }
464
  $scope.form.location = $scope.form.staff.locations.length == 1 ? $scope.form.staff.locations[0] : null;
 
465
  };
466
 
467
  $scope.onStartTimeChange = function() {
@@ -495,6 +603,10 @@
495
  checkAppointmentErrors();
496
  };
497
 
 
 
 
 
498
  $scope.processForm = function() {
499
  $scope.loading = true;
500
 
@@ -527,8 +639,13 @@
527
  custom_fields : item.custom_fields,
528
  extras : customer_extras,
529
  number_of_persons : item.number_of_persons,
 
530
  notes : item.notes,
531
- status : item.status
 
 
 
 
532
  });
533
  });
534
  jQuery.post(
@@ -542,6 +659,7 @@
542
  custom_service_name : $scope.form.custom_service_name,
543
  custom_service_price : $scope.form.custom_service_price,
544
  location_id : $scope.form.location ? $scope.form.location.id : undefined,
 
545
  start_date : dates.start_date,
546
  end_date : dates.end_date,
547
  repeat : JSON.stringify($scope.form.repeat),
@@ -603,12 +721,16 @@
603
  custom_fields : customer.custom_fields,
604
  extras : customer.extras,
605
  status : customer.status,
 
606
  number_of_persons : nop,
607
  notes : null,
608
  compound_token : null,
609
  payment_id : null,
610
  payment_type : null,
611
- payment_title : null
 
 
 
612
  };
613
 
614
  if (customer.email || customer.phone){
@@ -639,7 +761,7 @@
639
 
640
  $scope.editCustomerDetails = function(customer) {
641
  var $dialog = jQuery('#bookly-customer-details-dialog');
642
- $dialog.find('input.bookly-custom-field:text, textarea.bookly-custom-field, select.bookly-custom-field').val('');
643
  $dialog.find('input.bookly-custom-field:checkbox, input.bookly-custom-field:radio').prop('checked', false);
644
  $dialog.find('#bookly-extras :checkbox').prop('checked', false);
645
 
@@ -675,7 +797,7 @@
675
  var max = $scope.form.service
676
  ? ($scope.form.service.id
677
  ? parseInt($scope.form.service.capacity_max) - $scope.dataSource.getTotalNumberOfNotCancelledPersons(customer)
678
- : 1)
679
  : 1;
680
  $number_of_persons.empty();
681
  for (var i = 1; i <= max; ++i) {
@@ -688,12 +810,15 @@
688
  $dialog.find('#bookly-appointment-status').val(customer.status);
689
  $dialog.find('#bookly-appointment-notes').val(customer.notes);
690
  $dialog.find('#bookly-deposit-due').val(customer.due);
 
691
  $scope.edit_customer = customer;
692
 
693
  $dialog.modal({show: true})
694
  .on('hidden.bs.modal', function () {
695
  jQuery('body').addClass('modal-open');
696
  });
 
 
697
  };
698
 
699
  $scope.prepareExtras = function () {
@@ -742,6 +867,7 @@
742
  extras = {},
743
  $fields = jQuery('#bookly-js-custom-fields > *'),
744
  $status = jQuery('#bookly-appointment-status'),
 
745
  $number_of_persons = jQuery('#bookly-number-of-persons'),
746
  $notes = jQuery('#bookly-appointment-notes'),
747
  $extras = jQuery('#bookly-extras')
@@ -778,6 +904,7 @@
778
  }
779
 
780
  $scope.edit_customer.status = $status.val();
 
781
  $scope.edit_customer.number_of_persons = $number_of_persons.val();
782
  $scope.edit_customer.notes = $notes.val();
783
  $scope.edit_customer.custom_fields = result;
@@ -794,15 +921,59 @@
794
  * Payment Details *
795
  **************************************************************************************************************/
796
 
797
- $scope.completePayment = function(payment_id, payment_title) {
798
- jQuery.each($scope.dataSource.data.customers, function(key, item) {
799
- if (item.payment_id == payment_id) {
800
- item.payment_type = 'full';
801
- item.payment_title = payment_title;
802
- }
 
 
 
 
 
803
  });
804
  };
805
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
806
  /**************************************************************************************************************
807
  * Package Schedule *
808
  **************************************************************************************************************/
@@ -817,21 +988,22 @@
817
  callback('refresh');
818
  }
819
  }, true]);
820
- }
821
 
822
  /**************************************************************************************************************
823
  * Repeat Times in Recurring Appointments *
824
  **************************************************************************************************************/
825
  $scope.isDateMatchesSelections = function (current_date) {
 
826
  switch ($scope.form.repeat.repeat) {
827
  case 'daily':
828
- if (($scope.form.repeat.daily.every > 6 || jQuery.inArray(current_date.format('ddd').toLowerCase(), $scope.dataSource.data.week_days) != -1) && (current_date.diff(moment($scope.dataSource.form.date.getTime()), 'days') % $scope.form.repeat.daily.every == 0)) {
829
  return true;
830
  }
831
  break;
832
  case 'weekly':
833
  case 'biweekly':
834
- if (($scope.form.repeat.repeat == 'weekly' || current_date.diff(moment($scope.dataSource.form.date.getTime()).startOf('isoWeek'), 'weeks') % 2 == 0) && (jQuery.inArray(current_date.format('ddd').toLowerCase(), $scope.form.repeat.weekly.on) != -1)) {
835
  return true;
836
  }
837
  break;
@@ -843,7 +1015,7 @@
843
  }
844
  break;
845
  case 'last':
846
- if (current_date.format('ddd').toLowerCase() == $scope.form.repeat.monthly.weekday && current_date.clone().endOf('month').diff(current_date, 'days') < 7) {
847
  return true;
848
  }
849
  break;
@@ -852,7 +1024,7 @@
852
  weeks = ['first', 'second', 'third', 'fourth'],
853
  week_number = weeks.indexOf($scope.form.repeat.monthly.on);
854
 
855
- if (current_date.format('ddd').toLowerCase() == $scope.form.repeat.monthly.weekday && month_diff >= week_number * 7 && month_diff < (week_number + 1) * 7) {
856
  return true;
857
  }
858
  }
@@ -862,13 +1034,13 @@
862
  return false;
863
  };
864
  $scope.onRepeatChange = function () {
865
- if (jQuery('#bookly-repeat-enabled').length) {
866
  var number_of_times = 0,
867
  date_until = moment($scope.form.repeat.until).add(1, 'days'),
868
  current_date = moment($scope.dataSource.form.date.getTime());
869
  do {
870
  if ($scope.isDateMatchesSelections(current_date)) {
871
- number_of_times++
872
  }
873
  current_date.add(1, 'days');
874
  } while (current_date.isBefore(date_until));
@@ -920,7 +1092,8 @@
920
  until : $scope.form.repeat.until,
921
  repeat : $scope.form.repeat.repeat,
922
  params : $scope.form.repeat[$scope.form.repeat.repeat],
923
- extras : extras
 
924
  },
925
  function (response) {
926
  $scope.$apply(function($scope) {
@@ -1011,13 +1184,15 @@
1011
  csrf_token : BooklyL10nAppDialog.csrf_token,
1012
  staff_id : $scope.form.staff.id,
1013
  service_id : $scope.form.service.id,
 
1014
  datetime : item.date + ' 00:00',
1015
  until : item.date,
1016
  repeat : 'daily',
1017
  params : {every: 1},
1018
  with_options : 1,
1019
  exclude : exclude,
1020
- extras : extras
 
1021
  },
1022
  function (response) {
1023
  $scope.$apply(function($scope) {
@@ -1025,13 +1200,18 @@
1025
  item.options = response.data[0].options;
1026
  var found = false;
1027
  jQuery.each(item.options, function (key, option) {
1028
- if ( option.value == item.slots ) {
1029
  found = true;
1030
  return false;
1031
  }
1032
  });
1033
  if (!found) {
1034
- item.slots = item.options[0].value;
 
 
 
 
 
1035
  }
1036
  } else {
1037
  item.options = [];
5
  /**
6
  * DataSource service.
7
  */
8
+ module.factory('dataSource', function($q) {
9
  var ds = {
10
  loaded : false,
11
  data : {
12
+ staff : [],
13
+ customers : [],
14
+ start_time : [],
15
+ end_time : [],
16
+ app_start_time : null,
17
+ app_end_time : null,
18
+ time_interval : 900,
19
+ status : {
20
+ items: []
21
  }
22
  },
23
  form : {
24
+ screen : null,
25
+ id : null,
26
+ staff : null,
27
+ staff_any : null,
28
+ service : null,
29
+ custom_service_name : null,
30
+ custom_service_price: null,
31
+ location : null,
32
+ skip_date : null,
33
+ date : null,
34
+ start_time : null,
35
+ end_time : null,
36
+ repeat : {
37
  enabled : null,
38
  repeat : null,
39
  daily : { every : null },
69
  if (data.staff.length) {
70
  ds.form.staff = data.staff[0];
71
  }
72
+
73
  ds.form.start_time = data.start_time[0];
74
  ds.form.end_time = data.end_time[1];
75
  deferred.resolve();
120
  }
121
  return result;
122
  },
123
+ findTime : function(source, value) {
124
  var result = null,
125
+ time = source == 'start' ? ds.getDataForStartTime() : ds.form.end_time_data,
126
+ search_value = parseInt(value);
 
127
  jQuery.each(time, function(key, item) {
128
+ if (parseInt(item.value) >= search_value) {
129
  result = item;
130
  return false;
131
  }
146
  ds.data.customers.forEach(function(customer) {
147
  customer.custom_fields = [];
148
  customer.extras = [];
 
149
  customer.number_of_persons = 1;
150
  customer.notes = null;
151
  customer.compound_token = null;
152
  customer.payment_id = null;
153
  customer.payment_type = null;
154
  customer.payment_title = null;
155
+ customer.payment_create = false;
156
+ customer.payment_price = null;
157
+ customer.payment_tax = null;
158
  customer.package_id = null;
159
+ customer.ca_id = null;
160
  });
161
  },
162
+ getDataForStartTime : function() {
163
+ var result = ds.data.start_time.slice();
164
+ if (
165
+ ds.data.app_start_time &&
166
+ result.every(function (item) {return item.value !== ds.data.app_start_time.value;})
167
+ ) {
168
+ result.push(ds.data.app_start_time);
169
+ result.sort(function (a, b) {
170
+ return a.value < b.value ? -1 : (a.value > b.value ? 1 : 0);
171
+ });
172
+ }
173
+ return result;
174
+ },
175
  getDataForEndTime : function() {
176
  var result = [];
177
  if (ds.form.start_time) {
178
+ if (ds.form.service && parseInt(ds.form.service.units_max) > 1) {
179
+ var units_min = parseInt(ds.form.service.units_min),
180
+ units_max = parseInt(ds.form.service.units_max),
181
+ start_time = moment(ds.form.start_time.value, 'HH:mm');
182
+ for (var units = units_min; units <= units_max; units++) {
183
+ var end_time = moment(start_time).add(units * ds.form.service.duration, 'seconds'),
184
+ end_hour = parseInt(moment(end_time).format('HH')) + Math.floor((end_time.diff(start_time)) / 3600 / 24000) * 24;
185
+ jQuery.each(ds.data.end_time, function (key, item) {
186
+ if (item.value == (end_hour < 10 ? '0' + end_hour : end_hour) + ':' + moment(end_time).format('mm')) {
187
+ unit_item = jQuery.extend({}, item);
188
+ unit_item.title = item.title + ' (' + units +')';
189
+ result.push(unit_item);
190
+ }
191
+ });
192
  }
193
+ } else {
194
+ var start_time = ds.form.start_time.value.split(':'),
195
+ end = (24 + parseInt(start_time[0])) + ':' + start_time[1];
196
+ jQuery.each(ds.data.end_time, function (key, item) {
197
+ if (item.value > end) {
198
+ return false;
199
+ }
200
+ if (item.value > ds.form.start_time.value) {
201
+ result.push(item);
202
+ }
203
+ });
204
+ if (
205
+ ds.data.app_end_time &&
206
+ ds.data.app_end_time.value > ds.form.start_time.value &&
207
+ result.every(function (item) {
208
+ return item.value !== ds.data.app_end_time.value;
209
+ })
210
+ ) {
211
+ result.push(ds.data.app_end_time);
212
+ result.sort(function (a, b) {
213
+ return a.value < b.value ? -1 : (a.value > b.value ? 1 : 0);
214
+ });
215
  }
216
+ }
217
  }
218
  return result;
219
  },
220
  setEndTimeBasedOnService : function() {
221
+ ds.form.end_time_data = ds.getDataForEndTime();
222
+ var d = ds.form.service ? ds.form.service.duration * ds.form.service.units_min : ds.data.time_interval;
223
  if (d < 86400) {
224
+ ds.form.end_time = ds.findTime('end', moment(ds.form.start_time.value, 'HH:mm').add(d, 'seconds').format('HH:mm'));
 
 
 
 
 
 
 
 
225
  }
226
  },
227
  getStartAndEndDates : function() {
228
+ if (ds.form.skip_date) {
229
+ return {
230
+ start_date: null,
231
+ end_date : null
232
+ }
233
+ }
234
  var start_date = moment(ds.form.date.getTime()),
235
  end_date = moment(ds.form.date.getTime()),
236
  start_time = [0,0],
237
  end_time = [0,0]
238
  ;
239
  if (ds.form.service && ds.form.service.duration >= 86400) {
240
+ if (ds.form.end_time) {
241
+ var _start_time = ds.form.start_time.value.split(':');
242
+ var _end_time = ds.form.end_time.value.split(':');
243
+ var duration = Math.max(ds.form.service.duration, 60 * (_end_time[0] * 60 + parseInt(_end_time[1]) - _start_time[0] * 60 - parseInt(_start_time[1])));
244
+ end_date.add(duration, 'seconds');
245
+ } else if (ds.form.service && ds.form.service.units_max > 1) {
246
+ end_date.add(ds.form.service.duration * ds.form.service.units_min, 'seconds');
247
+ } else {
248
+ end_date.add(ds.form.service.duration, 'seconds');
249
+ }
250
  } else {
251
  start_time = ds.form.start_time.value.split(':');
252
  end_time = ds.form.end_time.value.split(':');
255
  start_date.minutes(start_time[1]);
256
  end_date.hours(end_time[0]);
257
  end_date.minutes(end_time[1]);
 
258
  return {
259
  start_date : start_date.format('YYYY-MM-DD HH:mm:00'),
260
  end_date : end_date.format('YYYY-MM-DD HH:mm:00')
287
  });
288
 
289
  return result;
290
+ },
291
+ getServiceDuration: function () {
292
+ var dates = ds.getStartAndEndDates(),
293
+ start_date = moment(dates.start_date),
294
+ end_date = moment(dates.end_date)
295
+ ;
296
+
297
+ return end_date.diff(start_date, 'seconds');
298
  }
299
  };
300
 
304
  /**
305
  * Controller for 'create/edit appointment' dialog form.
306
  */
307
+ module.controller('appointmentDialogCtrl', function($scope, $element, dataSource) {
308
  // Set up initial data.
309
  $scope.$calendar = null;
310
  // Set up data source.
313
  // Error messages.
314
  $scope.errors = {};
315
  // Callback to be called after editing appointment.
316
+ var callback = null;
317
 
318
  /**
319
  * Prepare the form for new event.
328
  service = staff && staff.services.length == 2 ? staff.services[1] : null,
329
  location = staff && staff.locations.length == 1 ? staff.locations[0] : null
330
  ;
331
+ $scope.dataSource.data.app_start_time = null;
332
+ $scope.dataSource.data.app_end_time = null;
333
  jQuery.extend($scope.form, {
334
  screen : 'main',
335
  id : null,
340
  custom_service_price : 0,
341
  location : location,
342
  date : start_date.clone().local().toDate(),
343
+ skip_date : null,
344
  start_time : dataSource.findTime('start', start_date.format('HH:mm')),
345
  end_time : null,
346
+ end_time_data : [],
347
  series_id : null,
348
  repeat : {
349
  enabled : 0,
372
  $scope.prepareCustomFields();
373
  $scope.dataSource.resetCustomers();
374
  $scope.onRepeatChange();
375
+ if (staff) {
376
+ $scope.onStaffChange();
377
+ }
378
  };
379
 
380
  /**
388
  function(response) {
389
  $scope.$apply(function($scope) {
390
  if (response.success) {
391
+ var start_date = response.data.start_date === null ? null : moment(response.data.start_date),
392
+ end_date = response.data.start_date === null ? null : moment(response.data.end_date),
393
  staff = $scope.dataSource.findStaff(response.data.staff_id);
394
+ $scope.dataSource.data.app_start_time = response.data.start_time;
395
+ $scope.dataSource.data.app_end_time = response.data.end_time;
396
  jQuery.extend($scope.form, {
397
+ screen : 'main',
398
+ id : appointment_id,
399
+ staff : staff,
400
+ staff_any : response.data.staff_any ? staff : null,
401
+ service : $scope.dataSource.findService(response.data.staff_id, response.data.service_id),
402
+ custom_service_name : response.data.custom_service_name,
403
+ custom_service_price : response.data.custom_service_price,
404
+ location : $scope.dataSource.findLocation(response.data.staff_id, response.data.location_id),
405
+ skip_date : start_date === null ? 1 : 0,
406
+ end_time : null,
407
+ end_time_data : [],
408
+ repeat : {
409
+ enabled : 0,
410
+ repeat : 'daily',
411
+ daily : {every: 1},
412
+ weekly : {on: []},
413
+ biweekly: {on: []},
414
+ monthly : {on: 'day', day: '1', weekday: 'mon'},
415
+ until : start_date === null ? moment().add(1, 'month').format('YYYY-MM-DD') : start_date.clone().add(1, 'month').format('YYYY-MM-DD')
 
 
416
  },
417
+ schedule : {
418
+ items : [],
419
+ edit : 0,
420
+ page : 0,
421
+ another_time: []
422
  },
423
+ customers : [],
424
+ internal_note : response.data.internal_note,
425
+ series_id : response.data.series_id,
426
+ expand_customers_list: false
427
  });
428
+ $scope.form.end_time_data = $scope.dataSource.getDataForEndTime();
429
+ if (start_date !== null) {
430
+ $scope.form.date = start_date.clone().local().toDate();
431
+ $scope.form.start_time = $scope.dataSource.findTime('start', start_date.format('HH:mm'));
432
+ $scope.dataSource.setEndTimeBasedOnService();
433
+ $scope.form.end_time = start_date.format('YYYY-MM-DD') == end_date.format('YYYY-MM-DD')
434
+ ? $scope.dataSource.findTime('end', end_date.format('HH:mm'))
435
+ : $scope.dataSource.findTime('end', (Math.floor((end_date - start_date) / 3600000) + start_date.hour()) + end_date.format(':mm'));
436
+ } else {
437
+ $scope.form.date = moment().local().toDate();
438
+ $scope.form.start_time = $scope.dataSource.findTime('start', moment().format('HH:mm'));
439
+ $scope.dataSource.setEndTimeBasedOnService();
440
+ }
441
 
442
  $scope.prepareExtras();
443
  $scope.prepareCustomFields();
444
  $scope.dataSource.resetCustomers();
445
+ $scope.onLocationChange();
446
  $scope.onRepeatChange();
447
 
448
  var customers_ids = [];
461
  clone.extras = item.extras;
462
  clone.status = item.status;
463
  clone.custom_fields = item.custom_fields;
464
+ clone.files = item.files;
465
  clone.number_of_persons = item.number_of_persons;
466
+ clone.timezone = item.timezone;
467
  clone.notes = item.notes;
468
  clone.payment_id = item.payment_id;
469
  clone.payment_type = item.payment_type;
470
  clone.payment_title = item.payment_title;
471
+ clone.payment_create = item.payment_create;
472
+ clone.payment_price = item.payment_price;
473
+ clone.payment_tax = item.payment_tax;
474
  clone.compound_token = item.compound_token;
475
  clone.compound_service = item.compound_service;
476
  $scope.form.customers.push(clone);
501
  });
502
  }
503
  customers.push({
504
+ id : item.id,
505
+ ca_id : item.ca_id,
506
+ custom_fields : item.custom_fields,
507
+ extras : customer_extras,
508
+ number_of_persons : item.number_of_persons,
509
+ timezone : item.timezone,
510
+ status : item.status
511
  });
512
  });
513
 
539
  $scope.dataSource.setEndTimeBasedOnService();
540
  $scope.prepareExtras();
541
  $scope.prepareCustomFields();
542
+ $scope.onLocationChange();
543
  checkAppointmentErrors();
544
  };
545
 
546
+ $scope.onLocationChange = function () {
547
+ if ($scope.form.staff && $scope.form.service) {
548
+ var current_service = $scope.dataSource.findService($scope.form.staff.id, $scope.form.service.id),
549
+ location_id = $scope.form.location ? $scope.form.location.id : 0;
550
+ if (current_service.locations.hasOwnProperty(location_id)) {
551
+ $scope.form.service.capacity_min = current_service.locations[location_id].capacity_min;
552
+ $scope.form.service.capacity_max = current_service.locations[location_id].capacity_max;
553
+ } else if (current_service.locations.hasOwnProperty(0)) {
554
+ $scope.form.service.capacity_min = current_service.locations[0].capacity_min;
555
+ $scope.form.service.capacity_max = current_service.locations[0].capacity_max;
556
+ } else {
557
+ $scope.form.service.capacity_min = 1;
558
+ $scope.form.service.capacity_max = 1;
559
+ }
560
+ checkAppointmentErrors();
561
+ }
562
+ };
563
+
564
  $scope.onStaffChange = function() {
565
  if ($scope.form.staff.services.length == 2) {
566
  $scope.form.service = $scope.form.staff.services[1];
569
  $scope.form.service = null;
570
  }
571
  $scope.form.location = $scope.form.staff.locations.length == 1 ? $scope.form.staff.locations[0] : null;
572
+ $scope.onLocationChange();
573
  };
574
 
575
  $scope.onStartTimeChange = function() {
603
  checkAppointmentErrors();
604
  };
605
 
606
+ $scope.onSkipDateChange = function() {
607
+ checkAppointmentErrors();
608
+ };
609
+
610
  $scope.processForm = function() {
611
  $scope.loading = true;
612
 
639
  custom_fields : item.custom_fields,
640
  extras : customer_extras,
641
  number_of_persons : item.number_of_persons,
642
+ timezone : item.timezone,
643
  notes : item.notes,
644
+ status : item.status,
645
+ payment_id : item.payment_id,
646
+ payment_create : item.payment_create,
647
+ payment_price : item.payment_price,
648
+ payment_tax : item.payment_tax
649
  });
650
  });
651
  jQuery.post(
659
  custom_service_name : $scope.form.custom_service_name,
660
  custom_service_price : $scope.form.custom_service_price,
661
  location_id : $scope.form.location ? $scope.form.location.id : undefined,
662
+ skip_date : $scope.form.skip_date,
663
  start_date : dates.start_date,
664
  end_date : dates.end_date,
665
  repeat : JSON.stringify($scope.form.repeat),
721
  custom_fields : customer.custom_fields,
722
  extras : customer.extras,
723
  status : customer.status,
724
+ timezone : customer.timezone,
725
  number_of_persons : nop,
726
  notes : null,
727
  compound_token : null,
728
  payment_id : null,
729
  payment_type : null,
730
+ payment_title : null,
731
+ payment_create : false,
732
+ payment_price : null,
733
+ payment_tax : null
734
  };
735
 
736
  if (customer.email || customer.phone){
761
 
762
  $scope.editCustomerDetails = function(customer) {
763
  var $dialog = jQuery('#bookly-customer-details-dialog');
764
+ $dialog.find('input.bookly-custom-field:text, textarea.bookly-custom-field, select.bookly-custom-field, input.bookly-js-file').val('');
765
  $dialog.find('input.bookly-custom-field:checkbox, input.bookly-custom-field:radio').prop('checked', false);
766
  $dialog.find('#bookly-extras :checkbox').prop('checked', false);
767
 
797
  var max = $scope.form.service
798
  ? ($scope.form.service.id
799
  ? parseInt($scope.form.service.capacity_max) - $scope.dataSource.getTotalNumberOfNotCancelledPersons(customer)
800
+ : 999)
801
  : 1;
802
  $number_of_persons.empty();
803
  for (var i = 1; i <= max; ++i) {
810
  $dialog.find('#bookly-appointment-status').val(customer.status);
811
  $dialog.find('#bookly-appointment-notes').val(customer.notes);
812
  $dialog.find('#bookly-deposit-due').val(customer.due);
813
+ $dialog.find('#bookly-customer-time-zone').val(customer.timezone ? customer.timezone : '');
814
  $scope.edit_customer = customer;
815
 
816
  $dialog.modal({show: true})
817
  .on('hidden.bs.modal', function () {
818
  jQuery('body').addClass('modal-open');
819
  });
820
+
821
+ jQuery(document.body).trigger('bookly.edit.customer_details', [$dialog, $scope.edit_customer]);
822
  };
823
 
824
  $scope.prepareExtras = function () {
867
  extras = {},
868
  $fields = jQuery('#bookly-js-custom-fields > *'),
869
  $status = jQuery('#bookly-appointment-status'),
870
+ $timezone = jQuery('#bookly-customer-time-zone'),
871
  $number_of_persons = jQuery('#bookly-number-of-persons'),
872
  $notes = jQuery('#bookly-appointment-notes'),
873
  $extras = jQuery('#bookly-extras')
904
  }
905
 
906
  $scope.edit_customer.status = $status.val();
907
+ $scope.edit_customer.timezone = $timezone.val();
908
  $scope.edit_customer.number_of_persons = $number_of_persons.val();
909
  $scope.edit_customer.notes = $notes.val();
910
  $scope.edit_customer.custom_fields = result;
921
  * Payment Details *
922
  **************************************************************************************************************/
923
 
924
+ $scope.attachPaymentModal = function (customer) {
925
+ var $dialog = jQuery('#bookly-payment-attach-modal');
926
+ $scope.form.attach = {
927
+ customer_id : customer.id,
928
+ payment_method: 'create',
929
+ payment_price : null,
930
+ payment_tax : null,
931
+ payment_id : null
932
+ };
933
+ $dialog.modal({show: true}).on('hidden.bs.modal', function () {
934
+ jQuery('body').addClass('modal-open');
935
  });
936
  };
937
 
938
+ $scope.attachPayment = function (attach_method, price, tax, payment_id, customer_id) {
939
+ var $dialog = jQuery('#bookly-payment-details-modal');
940
+ if (attach_method == 'search') {
941
+ $dialog.data('payment_id', payment_id).data('payment_bind', true).data('customer_id', customer_id).modal({show: true}).on('hidden.bs.modal', function () {
942
+ jQuery('body').addClass('modal-open');
943
+ });
944
+ } else {
945
+ jQuery.each($scope.dataSource.data.customers, function (key, item) {
946
+ if (item.id == customer_id) {
947
+ item.payment_create = true;
948
+ item.payment_price = price;
949
+ item.payment_tax = tax;
950
+ item.payment_type = 'partial';
951
+ }
952
+ });
953
+ }
954
+ };
955
+
956
+ $scope.callbackPayment = function (payment_action, payment_id, payment_title, customer_id, payment_type) {
957
+ if (payment_action == 'bind') {
958
+ // Bind payment
959
+ jQuery.each($scope.dataSource.data.customers, function (key, item) {
960
+ if (item.id == customer_id) {
961
+ item.payment_id = payment_id;
962
+ item.payment_type = payment_type;
963
+ item.payment_title = payment_title;
964
+ }
965
+ });
966
+ } else {
967
+ // Complete payment
968
+ jQuery.each($scope.dataSource.data.customers, function (key, item) {
969
+ if (item.payment_id == payment_id) {
970
+ item.payment_type = 'full';
971
+ item.payment_title = payment_title;
972
+ }
973
+ });
974
+ }
975
+ };
976
+
977
  /**************************************************************************************************************
978
  * Package Schedule *
979
  **************************************************************************************************************/
988
  callback('refresh');
989
  }
990
  }, true]);
991
+ };
992
 
993
  /**************************************************************************************************************
994
  * Repeat Times in Recurring Appointments *
995
  **************************************************************************************************************/
996
  $scope.isDateMatchesSelections = function (current_date) {
997
+ var current_day = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][current_date.format('d')];
998
  switch ($scope.form.repeat.repeat) {
999
  case 'daily':
1000
+ if (($scope.form.repeat.daily.every > 6 || jQuery.inArray(current_day, $scope.dataSource.data.week_days) != -1) && (current_date.diff(moment($scope.dataSource.form.date.getTime()), 'days') % $scope.form.repeat.daily.every == 0)) {
1001
  return true;
1002
  }
1003
  break;
1004
  case 'weekly':
1005
  case 'biweekly':
1006
+ if (($scope.form.repeat.repeat == 'weekly' || current_date.diff(moment($scope.dataSource.form.date.getTime()).startOf('isoWeek'), 'weeks') % 2 == 0) && (jQuery.inArray(current_day, $scope.form.repeat.weekly.on) != -1)) {
1007
  return true;
1008
  }
1009
  break;
1015
  }
1016
  break;
1017
  case 'last':
1018
+ if (current_day == $scope.form.repeat.monthly.weekday && current_date.clone().endOf('month').diff(current_date, 'days') < 7) {
1019
  return true;
1020
  }
1021
  break;
1024
  weeks = ['first', 'second', 'third', 'fourth'],
1025
  week_number = weeks.indexOf($scope.form.repeat.monthly.on);
1026
 
1027
+ if (current_day == $scope.form.repeat.monthly.weekday && month_diff >= week_number * 7 && month_diff < (week_number + 1) * 7) {
1028
  return true;
1029
  }
1030
  }
1034
  return false;
1035
  };
1036
  $scope.onRepeatChange = function () {
1037
+ if (jQuery('#bookly-repeat-enabled').length && !$scope.form.skip_date) {
1038
  var number_of_times = 0,
1039
  date_until = moment($scope.form.repeat.until).add(1, 'days'),
1040
  current_date = moment($scope.dataSource.form.date.getTime());
1041
  do {
1042
  if ($scope.isDateMatchesSelections(current_date)) {
1043
+ number_of_times++;
1044
  }
1045
  current_date.add(1, 'days');
1046
  } while (current_date.isBefore(date_until));
1092
  until : $scope.form.repeat.until,
1093
  repeat : $scope.form.repeat.repeat,
1094
  params : $scope.form.repeat[$scope.form.repeat.repeat],
1095
+ extras : extras,
1096
+ duration : $scope.form.service.id ? undefined : $scope.dataSource.getServiceDuration()
1097
  },
1098
  function (response) {
1099
  $scope.$apply(function($scope) {
1184
  csrf_token : BooklyL10nAppDialog.csrf_token,
1185
  staff_id : $scope.form.staff.id,
1186
  service_id : $scope.form.service.id,
1187
+ location_id : $scope.form.location ? $scope.form.location.id : null,
1188
  datetime : item.date + ' 00:00',
1189
  until : item.date,
1190
  repeat : 'daily',
1191
  params : {every: 1},
1192
  with_options : 1,
1193
  exclude : exclude,
1194
+ extras : extras,
1195
+ duration : $scope.form.service.id ? undefined : $scope.dataSource.getServiceDuration()
1196
  },
1197
  function (response) {
1198
  $scope.$apply(function($scope) {
1200
  item.options = response.data[0].options;
1201
  var found = false;
1202
  jQuery.each(item.options, function (key, option) {
1203
+ if (option.value == item.slots) {
1204
  found = true;
1205
  return false;
1206
  }
1207
  });
1208
  if (!found) {
1209
+ jQuery.each(item.options, function (key, option) {
1210
+ if (!option.disabled) {
1211
+ item.slots = option.value;
1212
+ return false;
1213
+ }
1214
+ });
1215
  }
1216
  } else {
1217
  item.options = [];
backend/{modules/calendar/templates/_appointment_dialog.php → components/dialogs/appointment/edit/templates/edit.php} RENAMED
@@ -1,8 +1,10 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Entities\CustomerAppointment;
3
- use BooklyLite\Lib\Config;
4
- use BooklyLite\Lib\Proxy;
5
- use BooklyLite\Lib\Utils\Common;
 
 
6
  ?>
7
  <div ng-app="appointmentDialog" ng-controller="appointmentDialogCtrl">
8
  <div id=bookly-appointment-dialog class="modal fade" tabindex=-1 role="dialog">
@@ -33,68 +35,67 @@ use BooklyLite\Lib\Utils\Common;
33
  </p>
34
  </div>
35
 
36
- <div class=form-group ng-show="form.service && !form.service.id">
37
- <label for="bookly-custom-service-name"><?php _e( 'Custom service name', 'bookly' ) ?></label>
38
- <input type="text" id="bookly-custom-service-name" class="form-control" ng-model="form.custom_service_name" />
39
- <p class="text-danger" my-slide-up="errors.custom_service_name_required">
40
- <?php _e( 'Please enter a service name', 'bookly' ) ?>
41
- </p>
42
- </div>
43
-
44
- <div class=form-group ng-show="form.service && !form.service.id">
45
- <label for="bookly-custom-service-price"><?php _e( 'Custom service price', 'bookly' ) ?></label>
46
- <input type="number" id="bookly-custom-service-price" class="form-control" ng-model="form.custom_service_price" min="0" step="1" />
47
- </div>
48
 
49
  <?php if ( Config::locationsActive() ): ?>
50
  <div class="form-group">
51
  <label for="bookly-appointment-location"><?php _e( 'Location', 'bookly' ) ?></label>
52
  <select id="bookly-appointment-location" class="form-control" ng-model="form.location"
53
- ng-options="l.name for l in form.staff.locations">
54
  <option value=""></option>
55
  </select>
56
  </div>
57
  <?php endif ?>
58
 
59
- <div class=form-group>
60
- <div class="row">
61
- <div class="col-sm-4">
62
- <label for="bookly-date"><?php _e( 'Date', 'bookly' ) ?></label>
63
- <input id="bookly-date" class="form-control" type=text
64
- ng-model=form.date ui-date="dateOptions" autocomplete="off"
65
- ng-change=onDateChange()>
66
- </div>
67
- <div class="col-sm-8">
68
- <div ng-hide="form.service.duration >= 86400">
69
- <label for="bookly-period"><?php _e( 'Period', 'bookly' ) ?></label>
70
- <div class="bookly-flexbox">
71
- <div class="bookly-flex-cell">
72
- <select id="bookly-period" class="form-control" ng-model=form.start_time
73
- ng-options="t.title for t in dataSource.data.start_time"
74
- ng-change=onStartTimeChange()></select>
75
- </div>
76
- <div class="bookly-flex-cell" style="width: 4%">
77
- <div class="bookly-margin-horizontal-md"><?php _e( 'to', 'bookly' ) ?></div>
78
- </div>
79
- <div class="bookly-flex-cell" style="width: 48%">
80
- <select class="form-control" ng-model=form.end_time
81
- ng-options="t.title for t in dataSource.getDataForEndTime()"
82
- ng-change=onEndTimeChange()></select>
 
 
 
 
83
  </div>
 
 
 
 
84
  </div>
85
- <p class="text-success" my-slide-up=errors.date_interval_warning id=date_interval_warning_msg>
86
- <?php _e( 'Selected period doesn\'t match service duration', 'bookly' ) ?>
87
- </p>
88
- <p class="text-success" my-slide-up="errors.time_interval" ng-bind="errors.time_interval"></p>
89
  </div>
90
  </div>
91
- <div class="text-success col-sm-12" my-slide-up=errors.date_interval_not_available id=date_interval_not_available_msg>
92
- <?php _e( 'The selected period is occupied by another appointment', 'bookly' ) ?>
93
- </div>
 
 
 
94
  </div>
95
- </div>
96
 
97
- <?php Proxy\RecurringAppointments::renderRecurringSubForm() ?>
 
98
 
99
  <div class=form-group>
100
  <label for="bookly-select2"><?php _e( 'Customers', 'bookly' ) ?></label>
@@ -102,7 +103,7 @@ use BooklyLite\Lib\Utils\Common;
102
  ({{dataSource.getTotalNumberOfPersons()}}/{{form.service.capacity_max}})
103
  </span>
104
  <span ng-show="form.customers.length > 5" ng-click="form.expand_customers_list = !form.expand_customers_list" role="button">
105
- <i class="dashicons" ng-class="{'dashicons-arrow-down-alt2':!form.expand_customers_list, 'dashicons-arrow-up-alt2':form.expand_customers_list}"></i>
106
  </span>
107
  <p class="text-success" ng-show=form.service my-slide-up="form.service.capacity_min > 1 && form.service.capacity_min > dataSource.getTotalNumberOfPersons()">
108
  <?php _e( 'Minimum capacity', 'bookly' ) ?>: {{form.service.capacity_min}}
@@ -111,55 +112,68 @@ use BooklyLite\Lib\Utils\Common;
111
  <li ng-repeat="customer in form.customers" class="bookly-flex-row" ng-hide="$index > 4 && !form.expand_customers_list">
112
  <a ng-click="editCustomerDetails(customer)" title="<?php esc_attr_e( 'Edit booking details', 'bookly' ) ?>" class="bookly-flex-cell bookly-padding-bottom-sm" href>{{customer.name}}</a>
113
  <span class="bookly-flex-cell text-right text-nowrap bookly-padding-bottom-sm">
114
- <?php Proxy\Shared::renderAppointmentDialogCustomerList() ?>
115
  <span class="dropdown">
116
  <button type="button" class="btn btn-sm btn-default bookly-margin-left-xs" data-toggle="dropdown" popover="<?php esc_attr_e( 'Status', 'bookly' ) ?>: {{statusToString(customer.status)}}">
117
- <span ng-class="{'dashicons': true, 'dashicons-clock': customer.status == 'pending', 'dashicons-yes': customer.status == 'approved', 'dashicons-no': customer.status == 'cancelled', 'dashicons-dismiss': customer.status == 'rejected', 'dashicons-list-view': customer.status == 'waitlisted'}"></span>
118
  <span class="caret"></span>
119
  </button>
120
  <ul class="dropdown-menu">
121
  <li>
122
  <a href ng-click="customer.status = 'pending'">
123
- <span class="dashicons dashicons-clock"></span>
124
  <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_PENDING ) ) ?>
125
  </a>
126
  </li>
127
  <li>
128
  <a href ng-click="customer.status = 'approved'">
129
- <span class="dashicons dashicons-yes"></span>
130
  <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_APPROVED ) ) ?>
131
  </a>
132
  </li>
133
  <li>
134
  <a href ng-click="customer.status = 'cancelled'">
135
- <span class="dashicons dashicons-no"></span>
136
  <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_CANCELLED ) ) ?>
137
  </a>
138
  </li>
139
  <li>
140
  <a href ng-click="customer.status = 'rejected'">
141
- <span class="dashicons dashicons-dismiss"></span>
142
  <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_REJECTED ) ) ?>
143
  </a>
144
  </li>
145
  <?php if ( Config::waitingListActive() ): ?>
146
  <li>
147
  <a href ng-click="customer.status = 'waitlisted'">
148
- <span class="dashicons dashicons-list-view"></span>
149
  <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_WAITLISTED ) ) ?>
150
  </a>
151
  </li>
152
  <?php endif ?>
 
 
 
 
 
 
 
 
153
  </ul>
154
  </span>
155
- <button type="button" class="btn btn-sm btn-default bookly-margin-left-xs" data-toggle="modal" href="#bookly-payment-details-modal" data-payment_id="{{customer.payment_id}}" ng-show="customer.payment_id" popover="<?php esc_attr_e( 'Payment', 'bookly' ) ?>: {{customer.payment_title}}">
156
- <span ng-class="{'bookly-js-toggle-popover dashicons': true, 'dashicons-thumbs-up': customer.payment_type == 'full', 'dashicons-warning': customer.payment_type == 'partial'}"></span>
157
  </button>
158
- <span class="btn btn-sm btn-default disabled bookly-margin-left-xs" style="opacity:1;cursor:default;"><i class="glyphicon glyphicon-user"></i>&times;{{customer.number_of_persons}}</span>
 
 
 
 
159
  <button type="button" class="btn btn-sm btn-default bookly-margin-left-xs" ng-click="editPackageSchedule(customer)" ng-show="customer.package_id" popover="<?php esc_attr_e( 'Package schedule', 'bookly' ) ?>">
160
- <span class="dashicons dashicons-calendar"></span>
161
  </button>
162
- <a ng-click="removeCustomer(customer)" class="dashicons dashicons-trash text-danger bookly-vertical-middle" href="#"
 
163
  popover="<?php esc_attr_e( 'Remove customer', 'bookly' ) ?>"></a>
164
  </span>
165
  </li>
@@ -175,7 +189,7 @@ use BooklyLite\Lib\Utils\Common;
175
  </select>
176
  <span class="input-group-btn">
177
  <a class="btn btn-success" ng-click="openNewCustomerDialog()">
178
- <i class="glyphicon glyphicon-plus"></i>
179
  <?php _e( 'New customer', 'bookly' ) ?>
180
  </a>
181
  </span>
@@ -209,19 +223,19 @@ use BooklyLite\Lib\Utils\Common;
209
  <div class="modal-footer">
210
  <div ng-hide=loading>
211
  <?php Proxy\Shared::renderAppointmentDialogFooter() ?>
212
- <?php Common::customButton( 'bookly-save', 'btn-lg btn-success', null, array( 'ng-hide' => 'form.repeat.enabled && form.screen == \'main\'', 'ng-disabled' => 'form.repeat.enabled && schIsScheduleEmpty()', 'formnovalidate' => '' ), 'submit' ) ?>
213
- <?php Common::customButton( null, 'btn-lg btn-default', __( 'Cancel', 'bookly' ), array( 'ng-click' => 'closeDialog()', 'data-dismiss' => 'modal' ) ) ?>
214
  </div>
215
  </div>
216
  </form>
217
- </div><!-- /.modal-content -->
218
- </div><!-- /.modal-dialog -->
219
- </div><!-- /.modal -->
220
  <div customer-dialog=createCustomer(customer)></div>
221
- <div payment-details-dialog="completePayment(payment_id, payment_title)"></div>
222
 
223
- <?php $this->render( '_customer_details_dialog' ) ?>
224
- <?php \BooklyLite\Backend\Modules\Customers\Components::getInstance()->renderCustomerDialog() ?>
225
- <?php \BooklyLite\Backend\Modules\Payments\Components::getInstance()->renderPaymentDetailsDialog() ?>
 
226
  </div>
227
- <?php Proxy\Packages::renderPackageScheduleDialog() ?>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ use Bookly\Backend\Components\Dialogs;
4
+ use Bookly\Backend\Components\Dialogs\Appointment\Edit\Proxy;
5
+ use Bookly\Backend\Components\Dialogs\Appointment\AttachPayment\Proxy as AttachPaymentProxy;
6
+ use Bookly\Lib\Config;
7
+ use Bookly\Lib\Entities\CustomerAppointment;
8
  ?>
9
  <div ng-app="appointmentDialog" ng-controller="appointmentDialogCtrl">
10
  <div id=bookly-appointment-dialog class="modal fade" tabindex=-1 role="dialog">
35
  </p>
36
  </div>
37
 
38
+ <?php Proxy\Pro::renderCustomServiceFields() ?>
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  <?php if ( Config::locationsActive() ): ?>
41
  <div class="form-group">
42
  <label for="bookly-appointment-location"><?php _e( 'Location', 'bookly' ) ?></label>
43
  <select id="bookly-appointment-location" class="form-control" ng-model="form.location"
44
+ ng-options="l.name for l in form.staff.locations" ng-change="onLocationChange()">
45
  <option value=""></option>
46
  </select>
47
  </div>
48
  <?php endif ?>
49
 
50
+ <?php Proxy\Tasks::renderSkipDate() ?>
51
+
52
+ <div ng-hide="form.skip_date">
53
+ <div class=form-group>
54
+ <div class="row">
55
+ <div class="col-sm-4">
56
+ <label for="bookly-date"><?php _e( 'Date', 'bookly' ) ?></label>
57
+ <input id="bookly-date" class="form-control" type=text
58
+ ng-model=form.date ui-date="dateOptions" autocomplete="off"
59
+ ng-change=onDateChange()>
60
+ </div>
61
+ <div class="col-sm-8">
62
+ <div ng-hide="form.service.duration >= 86400">
63
+ <label for="bookly-period"><?php _e( 'Period', 'bookly' ) ?></label>
64
+ <div class="bookly-flexbox">
65
+ <div class="bookly-flex-cell">
66
+ <select id="bookly-period" class="form-control" ng-model=form.start_time
67
+ ng-options="t.title for t in dataSource.getDataForStartTime()"
68
+ ng-change=onStartTimeChange()></select>
69
+ </div>
70
+ <div class="bookly-flex-cell" style="width: 4%">
71
+ <div class="bookly-margin-horizontal-md"><?php _e( 'to', 'bookly' ) ?></div>
72
+ </div>
73
+ <div class="bookly-flex-cell" style="width: 48%">
74
+ <select class="form-control" ng-model=form.end_time
75
+ ng-options="t.title for t in form.end_time_data"
76
+ ng-change=onEndTimeChange()></select>
77
+ </div>
78
  </div>
79
+ <p class="text-success" my-slide-up=errors.date_interval_warning id=date_interval_warning_msg>
80
+ <?php _e( 'Selected period doesn\'t match service duration', 'bookly' ) ?>
81
+ </p>
82
+ <p class="text-success" my-slide-up="errors.time_interval" ng-bind="errors.time_interval"></p>
83
  </div>
84
+ </div>
85
+ <div class="text-success col-sm-12" my-slide-up=errors.date_interval_not_available id=date_interval_not_available_msg>
86
+ <?php _e( 'The selected period is occupied by another appointment', 'bookly' ) ?>
 
87
  </div>
88
  </div>
89
+ <p class="text-success" my-slide-up=errors.interval_not_in_staff_schedule id=interval_not_in_staff_schedule_msg>
90
+ <?php _e( 'Selected period doesn\'t match provider\'s schedule', 'bookly' ) ?>
91
+ </p>
92
+ <p class="text-success" my-slide-up=errors.interval_not_in_service_schedule id=interval_not_in_service_schedule_msg>
93
+ <?php _e( 'Selected period doesn\'t match service schedule', 'bookly' ) ?>
94
+ </p>
95
  </div>
 
96
 
97
+ <?php Proxy\RecurringAppointments::renderSubForm() ?>
98
+ </div>
99
 
100
  <div class=form-group>
101
  <label for="bookly-select2"><?php _e( 'Customers', 'bookly' ) ?></label>
103
  ({{dataSource.getTotalNumberOfPersons()}}/{{form.service.capacity_max}})
104
  </span>
105
  <span ng-show="form.customers.length > 5" ng-click="form.expand_customers_list = !form.expand_customers_list" role="button">
106
+ <i class="fa fa-fw" ng-class="{'fa-angle-down':!form.expand_customers_list, 'fa-angle-up':form.expand_customers_list}"></i>
107
  </span>
108
  <p class="text-success" ng-show=form.service my-slide-up="form.service.capacity_min > 1 && form.service.capacity_min > dataSource.getTotalNumberOfPersons()">
109
  <?php _e( 'Minimum capacity', 'bookly' ) ?>: {{form.service.capacity_min}}
112
  <li ng-repeat="customer in form.customers" class="bookly-flex-row" ng-hide="$index > 4 && !form.expand_customers_list">
113
  <a ng-click="editCustomerDetails(customer)" title="<?php esc_attr_e( 'Edit booking details', 'bookly' ) ?>" class="bookly-flex-cell bookly-padding-bottom-sm" href>{{customer.name}}</a>
114
  <span class="bookly-flex-cell text-right text-nowrap bookly-padding-bottom-sm">
115
+ <?php Proxy\Shared::renderAppointmentDialogCustomersList() ?>
116
  <span class="dropdown">
117
  <button type="button" class="btn btn-sm btn-default bookly-margin-left-xs" data-toggle="dropdown" popover="<?php esc_attr_e( 'Status', 'bookly' ) ?>: {{statusToString(customer.status)}}">
118
+ <span ng-class="{'fa fa-fw': true, 'fa-clock': customer.status == 'pending', 'fa-check': customer.status == 'approved', 'fa-times': customer.status == 'cancelled', 'fa-times-circle': customer.status == 'rejected', 'fa-list-alt': customer.status == 'waitlisted', 'fa-check-circle': customer.status == 'done'}"></span>
119
  <span class="caret"></span>
120
  </button>
121
  <ul class="dropdown-menu">
122
  <li>
123
  <a href ng-click="customer.status = 'pending'">
124
+ <span class="fa fa-fw fa-clock"></span>
125
  <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_PENDING ) ) ?>
126
  </a>
127
  </li>
128
  <li>
129
  <a href ng-click="customer.status = 'approved'">
130
+ <span class="fa fa-fw fa-check"></span>
131
  <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_APPROVED ) ) ?>
132
  </a>
133
  </li>
134
  <li>
135
  <a href ng-click="customer.status = 'cancelled'">
136
+ <span class="fa fa-fw fa-times"></span>
137
  <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_CANCELLED ) ) ?>
138
  </a>
139
  </li>
140
  <li>
141
  <a href ng-click="customer.status = 'rejected'">
142
+ <span class="fa fa-fw fa-times-circle"></span>
143
  <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_REJECTED ) ) ?>
144
  </a>
145
  </li>
146
  <?php if ( Config::waitingListActive() ): ?>
147
  <li>
148
  <a href ng-click="customer.status = 'waitlisted'">
149
+ <span class="fa fa-fw fa-list-alt"></span>
150
  <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_WAITLISTED ) ) ?>
151
  </a>
152
  </li>
153
  <?php endif ?>
154
+ <?php if ( Config::tasksActive() ): ?>
155
+ <li>
156
+ <a href ng-click="customer.status = 'done'">
157
+ <span class="fa fa-fw fa-check-circle"></span>
158
+ <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_DONE ) ) ?>
159
+ </a>
160
+ </li>
161
+ <?php endif ?>
162
  </ul>
163
  </span>
164
+ <button type="button" class="btn btn-sm btn-default bookly-margin-left-xs" data-toggle="modal" href="#bookly-payment-details-modal" data-payment_id="{{customer.payment_id}}" ng-show="customer.payment_id || customer.payment_create" popover="<?php esc_attr_e( 'Payment', 'bookly' ) ?>: {{customer.payment_title}}" ng-disabled="customer.payment_create">
165
+ <span ng-class="{'bookly-js-toggle-popover fa fa-fw': true, 'fa-clipboard-check': customer.payment_type == 'full', 'fa-hourglass': customer.payment_type == 'partial'}"></span>
166
  </button>
167
+
168
+ <?php Proxy\Pro::renderAttachPaymentButton() ?>
169
+
170
+ <span class="btn btn-sm btn-default disabled bookly-margin-left-xs" style="opacity:1;cursor:default;"><i class="fa fa-fw fa-user"></i>&times;{{customer.number_of_persons}}</span>
171
+ <?php if ( Config::packagesActive() ) : ?>
172
  <button type="button" class="btn btn-sm btn-default bookly-margin-left-xs" ng-click="editPackageSchedule(customer)" ng-show="customer.package_id" popover="<?php esc_attr_e( 'Package schedule', 'bookly' ) ?>">
173
+ <span class="fa fa-fw fa-calendar-alt"></span>
174
  </button>
175
+ <?php endif ?>
176
+ <a ng-click="removeCustomer(customer)" class="fa fa-fw fa-trash-alt text-danger bookly-vertical-middle" href="#"
177
  popover="<?php esc_attr_e( 'Remove customer', 'bookly' ) ?>"></a>
178
  </span>
179
  </li>
189
  </select>
190
  <span class="input-group-btn">
191
  <a class="btn btn-success" ng-click="openNewCustomerDialog()">
192
+ <i class="fa fa-fw fa-plus"></i>
193
  <?php _e( 'New customer', 'bookly' ) ?>
194
  </a>
195
  </span>
223
  <div class="modal-footer">
224
  <div ng-hide=loading>
225
  <?php Proxy\Shared::renderAppointmentDialogFooter() ?>
226
+ <?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' => '' ) ) ?>
227
+ <?php Buttons::renderCustom( null, 'btn-lg btn-default', __( 'Cancel', 'bookly' ), array( 'ng-click' => 'closeDialog()', 'data-dismiss' => 'modal' ) ) ?>
228
  </div>
229
  </div>
230
  </form>
231
+ </div>
232
+ </div>
233
+ </div>
234
  <div customer-dialog=createCustomer(customer)></div>
235
+ <div payment-details-dialog="callbackPayment(payment_action, payment_id, payment_title, customer_id, payment_type)"></div>
236
 
237
+ <?php Dialogs\Appointment\CustomerDetails\Dialog::render() ?>
238
+ <?php AttachPaymentProxy\Pro::renderAttachPaymentDialog() ?>
239
+ <?php Dialogs\Customer\Edit::render() ?>
240
+ <?php Dialogs\Payment\Dialog::render() ?>
241
  </div>
 
backend/components/dialogs/common/CascadeDelete.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Common;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CascadeDelete
8
+ * @package Bookly\Backend\Components\Dialogs\Common
9
+ */
10
+ class CascadeDelete extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render cascade delete dialog (used in services and staff lists).
14
+ */
15
+ public static function render()
16
+ {
17
+ self::enqueueStyles( array(
18
+ 'frontend' => array( 'css/ladda.min.css', ),
19
+ ) );
20
+
21
+ self::enqueueScripts( array(
22
+ 'frontend' => array(
23
+ 'js/spin.min.js' => array( 'jquery' ),
24
+ 'js/ladda.min.js' => array( 'bookly-spin.min.js', 'jquery' ),
25
+ )
26
+ ) );
27
+
28
+ self::renderTemplate( 'delete_cascade' );
29
+ }
30
+ }
backend/components/dialogs/common/UnsavedChanges.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Common;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class UnsavedChanges
8
+ * @package Bookly\Backend\Components\Dialogs\Common
9
+ */
10
+ class UnsavedChanges extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render unsaved data confirm dialog.
14
+ */
15
+ public static function render()
16
+ {
17
+ self::enqueueStyles( array(
18
+ 'frontend' => array( 'css/ladda.min.css', ),
19
+ ) );
20
+
21
+ self::enqueueScripts( array(
22
+ 'frontend' => array(
23
+ 'js/spin.min.js' => array( 'jquery' ),
24
+ 'js/ladda.min.js' => array( 'bookly-spin.min.js', 'jquery' ),
25
+ )
26
+ ) );
27
+
28
+ self::renderTemplate( 'unsaved_changes' );
29
+ }
30
+ }
backend/components/dialogs/common/templates/delete_cascade.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ ?>
4
+ <div class="modal fade bookly-js-delete-cascade-confirm" tabindex="-1" role="dialog">
5
+ <div class="modal-dialog modal-lg" role="document">
6
+ <div class="modal-content">
7
+ <div class="modal-header">
8
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
9
+ <div class="modal-title h4"><?php _e( 'Are you sure?', 'bookly' ) ?></div>
10
+ </div>
11
+ <div class="modal-body">
12
+ <p><?php _e( 'You are going to delete item which is involved in upcoming appointments. All related appointments will be deleted. Please double check and edit appointments before this item deletion if needed.', 'bookly' ) ?></p>
13
+ </div>
14
+ <div class="modal-footer">
15
+ <?php Buttons::renderCustom( null, 'btn-lg btn-danger bookly-js-delete', __( 'Delete', 'bookly' ) ) ?>
16
+ <?php Buttons::renderCustom( null, 'btn-lg btn-success bookly-js-edit', __( 'Edit appointments', 'bookly' ) ) ?>
17
+ <?php Buttons::renderCustom( null, 'btn-lg btn-default', __( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </div>
backend/components/dialogs/common/templates/unsaved_changes.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ ?>
4
+ <div class="modal fade bookly-js-unsaved-changes" tabindex="-1" role="dialog">
5
+ <div class="modal-dialog modal-lg" role="document">
6
+ <div class="modal-content">
7
+ <div class="modal-header">
8
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
9
+ <div class="modal-title h4"><?php _e( 'Are you sure?', 'bookly' ) ?></div>
10
+ </div>
11
+ <div class="modal-body">
12
+ <p><?php _e( 'All unsaved changes will be lost.', 'bookly' ) ?></p>
13
+ </div>
14
+ <div class="modal-footer">
15
+ <?php Buttons::renderCustom( null, 'btn-lg btn-success bookly-js-save-changes', __( 'Save', 'bookly' ) ) ?>
16
+ <?php Buttons::renderCustom( null, 'btn-lg btn-danger bookly-js-ignore-changes', __( 'Don\'t save', 'bookly' ) ) ?>
17
+ <?php Buttons::renderCustom( null, 'btn-lg btn-default', __( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </div>
backend/{modules/customers/Components.php → components/dialogs/customer/Edit.php} RENAMED
@@ -1,30 +1,29 @@
1
  <?php
2
- namespace BooklyLite\Backend\Modules\Customers;
3
 
4
- use BooklyLite\Lib;
5
 
6
  /**
7
- * Class Components
8
- * @package BooklyLite\Backend\Modules\Customers
9
  */
10
- class Components extends Lib\Base\Components
11
  {
12
  /**
13
  * Render customer dialog.
14
- * @throws \Exception
15
  */
16
- public function renderCustomerDialog()
17
  {
18
  global $wp_locale;
19
 
20
- $this->enqueueStyles( array(
21
  'backend' => array( 'css/jquery-ui-theme/jquery-ui.min.css', ),
22
  'frontend' => get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
23
  ? array()
24
  : array( 'css/intlTelInput.css' ),
25
  ) );
26
 
27
- $this->enqueueScripts( array(
28
  'backend' => array(
29
  'js/angular.min.js' => array( 'jquery' ),
30
  'js/angular-ui-date-0.0.8.js' => array( 'bookly-angular.min.js', 'jquery-ui-datepicker' ),
@@ -32,28 +31,31 @@ class Components extends Lib\Base\Components
32
  'frontend' => get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
33
  ? array()
34
  : array( 'js/intlTelInput.min.js' => array( 'jquery' ) ),
35
- 'module' => array( 'js/ng-customer_dialog.js' => array( 'bookly-angular.min.js' ), )
36
  ) );
37
 
38
- wp_localize_script( 'bookly-ng-customer_dialog.js', 'BooklyL10nCustDialog', array(
39
- 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
40
- 'default_status' => get_option( 'bookly_gen_default_appointment_status' ),
41
- 'intlTelInput' => array(
 
42
  'enabled' => get_option( 'bookly_cst_phone_default_country' ) != 'disabled',
43
- 'utils' => plugins_url( 'intlTelInput.utils.js', Lib\Plugin::getDirectory() . '/frontend/resources/js/intlTelInput.utils.js' ),
44
  'country' => get_option( 'bookly_cst_phone_default_country' ),
45
  ),
46
- 'dateOptions' => array(
47
  'dateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_JQUERY_DATEPICKER ),
48
  'monthNamesShort' => array_values( $wp_locale->month_abbrev ),
49
  'monthNames' => array_values( $wp_locale->month ),
50
  'dayNamesMin' => array_values( $wp_locale->weekday_abbrev ),
51
  'longDays' => array_values( $wp_locale->weekday ),
52
  'firstDay' => (int) get_option( 'start_of_week' ),
 
 
53
  ),
 
54
  ) );
55
 
56
- $this->render( '_customer_dialog' );
57
  }
58
-
59
  }
1
  <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer;
3
 
4
+ use Bookly\Lib;
5
 
6
  /**
7
+ * Class Edit
8
+ * @package Bookly\Backend\Components\Dialogs\Customer
9
  */
10
+ class Edit extends Lib\Base\Component
11
  {
12
  /**
13
  * Render customer dialog.
 
14
  */
15
+ public static function render()
16
  {
17
  global $wp_locale;
18
 
19
+ static::enqueueStyles( array(
20
  'backend' => array( 'css/jquery-ui-theme/jquery-ui.min.css', ),
21
  'frontend' => get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
22
  ? array()
23
  : array( 'css/intlTelInput.css' ),
24
  ) );
25
 
26
+ static::enqueueScripts( array(
27
  'backend' => array(
28
  'js/angular.min.js' => array( 'jquery' ),
29
  'js/angular-ui-date-0.0.8.js' => array( 'bookly-angular.min.js', 'jquery-ui-datepicker' ),
31
  'frontend' => get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
32
  ? array()
33
  : array( 'js/intlTelInput.min.js' => array( 'jquery' ) ),
34
+ 'module' => array( 'js/ng-customer.js' => array( 'bookly-angular.min.js' ), )
35
  ) );
36
 
37
+ wp_localize_script( 'bookly-ng-customer.js', 'BooklyL10nCustDialog', array(
38
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
39
+ 'first_last_name' => (int) Lib\Config::showFirstLastName(),
40
+ 'default_status' => get_option( 'bookly_gen_default_appointment_status' ),
41
+ 'intlTelInput' => array(
42
  'enabled' => get_option( 'bookly_cst_phone_default_country' ) != 'disabled',
43
+ 'utils' => is_rtl() ? '' : plugins_url( 'intlTelInput.utils.js', Lib\Plugin::getDirectory() . '/frontend/resources/js/intlTelInput.utils.js' ),
44
  'country' => get_option( 'bookly_cst_phone_default_country' ),
45
  ),
46
+ 'dateOptions' => array(
47
  'dateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_JQUERY_DATEPICKER ),
48
  'monthNamesShort' => array_values( $wp_locale->month_abbrev ),
49
  'monthNames' => array_values( $wp_locale->month ),
50
  'dayNamesMin' => array_values( $wp_locale->weekday_abbrev ),
51
  'longDays' => array_values( $wp_locale->weekday ),
52
  'firstDay' => (int) get_option( 'start_of_week' ),
53
+ 'yearRange' => sprintf( "%s:%s", date_create()->modify( '-100 years' )->format( 'Y' ), date( 'Y' ) ),
54
+ 'changeYear' => true,
55
  ),
56
+ 'infoFields' => (array) Lib\Proxy\CustomerInformation::getFieldsWhichMayHaveData(),
57
  ) );
58
 
59
+ static::renderTemplate( 'edit' );
60
  }
 
61
  }
backend/components/dialogs/customer/EditAjax.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Edit
8
+ * @package Bookly\Backend\Components\Dialogs\Customer
9
+ */
10
+ class EditAjax extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * @inheritdoc
14
+ */
15
+ protected static function permissions()
16
+ {
17
+ return array( '_default' => 'user' );
18
+ }
19
+
20
+ /**
21
+ * Create or edit a customer.
22
+ */
23
+ public static function saveCustomer()
24
+ {
25
+ $response = array();
26
+
27
+ $params = self::postParameters();
28
+ $errors = array();
29
+
30
+ // Check for errors.
31
+ if ( get_option( 'bookly_cst_first_last_name' ) ) {
32
+ if ( $params['first_name'] == '' ) {
33
+ $errors['first_name'] = array( 'required' );
34
+ }
35
+ if ( $params['last_name'] == '' ) {
36
+ $errors['last_name'] = array( 'required' );
37
+ }
38
+ } else if ( $params['full_name'] == '' ) {
39
+ $errors['full_name'] = array( 'required' );
40
+ }
41
+
42
+ if ( empty ( $errors ) ) {
43
+ if ( ! $params['wp_user_id'] ) {
44
+ $params['wp_user_id'] = null;
45
+ }
46
+ if ( ! $params['birthday'] ) {
47
+ $params['birthday'] = null;
48
+ }
49
+ if ( ! $params['group_id'] ) {
50
+ $params['group_id'] = null;
51
+ }
52
+ $params = Proxy\CustomerInformation::prepareCustomerFormData( $params );
53
+ $params['info_fields'] = json_encode( $params['info_fields'] );
54
+ $form = new Forms\Customer();
55
+ $form->bind( $params );
56
+ /** @var Lib\Entities\Customer $customer */
57
+ $customer = $form->save();
58
+ $response['success'] = true;
59
+ $response['customer'] = array(
60
+ 'id' => $customer->getId(),
61
+ 'wp_user_id' => $customer->getWpUserId(),
62
+ 'group_id' => $customer->getGroupId(),
63
+ 'full_name' => $customer->getFullName(),
64
+ 'first_name' => $customer->getFirstName(),
65
+ 'last_name' => $customer->getLastName(),
66
+ 'phone' => $customer->getPhone(),
67
+ 'email' => $customer->getEmail(),
68
+ 'notes' => $customer->getNotes(),
69
+ 'birthday' => $customer->getBirthday(),
70
+ 'info_fields' => json_decode( $customer->getInfoFields() ),
71
+ );
72
+ } else {
73
+ $response['success'] = false;
74
+ $response['errors'] = $errors;
75
+ }
76
+
77
+ wp_send_json( $response );
78
+ }
79
+
80
+ /**
81
+ * Check if the current user has access to the action.
82
+ *
83
+ * @param string $action
84
+ * @return bool
85
+ */
86
+ protected static function hasAccess( $action )
87
+ {
88
+ if ( parent::hasAccess( $action ) ) {
89
+ if ( ! Lib\Utils\Common::isCurrentUserAdmin() ) {
90
+ switch ( $action ) {
91
+ case 'saveCustomer':
92
+ return Lib\Entities\Staff::query()
93
+ ->where( 'wp_user_id', get_current_user_id() )
94
+ ->count() > 0;
95
+ }
96
+ } else {
97
+ return true;
98
+ }
99
+ }
100
+
101
+ return false;
102
+ }
103
+ }
backend/{modules/customers → components/dialogs/customer}/forms/Customer.php RENAMED
@@ -1,11 +1,11 @@
1
  <?php
2
- namespace BooklyLite\Backend\Modules\Customers\Forms;
3
 
4
- use BooklyLite\Lib;
5
 
6
  /**
7
  * Class Customer
8
- * @package BooklyLite\Backend\Modules\Customers\Forms
9
  */
10
  class Customer extends Lib\Base\Form
11
  {
@@ -15,14 +15,22 @@ class Customer extends Lib\Base\Form
15
  {
16
  $this->setFields( array(
17
  'wp_user_id',
 
18
  'full_name',
19
  'first_name',
20
  'last_name',
21
  'phone',
22
  'email',
 
 
 
 
 
 
 
23
  'notes',
24
  'birthday',
 
25
  ) );
26
  }
27
-
28
  }
1
  <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Forms;
3
 
4
+ use Bookly\Lib;
5
 
6
  /**
7
  * Class Customer
8
+ * @package Bookly\Backend\Components\Dialogs\Customer\Forms
9
  */
10
  class Customer extends Lib\Base\Form
11
  {
15
  {
16
  $this->setFields( array(
17
  'wp_user_id',
18
+ 'group_id',
19
  'full_name',
20
  'first_name',
21
  'last_name',
22
  'phone',
23
  'email',
24
+ 'country',
25
+ 'state',
26
+ 'postcode',
27
+ 'city',
28
+ 'street',
29
+ 'street_number',
30
+ 'additional_address',
31
  'notes',
32
  'birthday',
33
+ 'info_fields',
34
  ) );
35
  }
 
36
  }
backend/components/dialogs/customer/proxy/CustomerGroups.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CustomerGroups
8
+ * @package Bookly\Backend\Components\Dialogs\Customer\Proxy
9
+ *
10
+ * @method static void renderCustomerDialog() Render 'Customer Group' row in edit customer dialog.
11
+ * @see \BooklyCustomerGroups\Backend\Components\Dialogs\Customer\ProxyProviders\Local::renderCustomerDialog()
12
+ */
13
+ abstract class CustomerGroups extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/components/dialogs/customer/proxy/CustomerInformation.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CustomerInformation
8
+ * @package Bookly\Backend\Components\Dialogs\Customer\Proxy
9
+ *
10
+ * @method static void renderCustomerDialog() Render 'Customer Information' row in edit customer dialog.
11
+ * @method static array prepareCustomerFormData( array $params ) Prepare customer info fields before saving customer form.
12
+ */
13
+ abstract class CustomerInformation extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/components/dialogs/customer/proxy/Pro.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Components\Dialogs\Customer\Proxy
9
+ *
10
+ * @method static void renderCustomerDialogAddress() Render address fields in edit customer dialog.
11
+ * @method static void renderCustomerDialogBirthday() Render birthday fields in edit customer dialog.
12
+ */
13
+ abstract class Pro extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/{modules/customers/resources/js/ng-customer_dialog.js → components/dialogs/customer/resources/js/ng-customer.js} RENAMED
@@ -15,16 +15,28 @@
15
  // Form fields.
16
  if (!scope.form) {
17
  scope.form = {
18
- id : '',
19
- wp_user_id : '',
20
- full_name : '',
21
- first_name : '',
22
- last_name : '',
23
- phone : '',
24
- email : '',
25
- notes : '',
26
- birthday : ''
 
 
 
 
 
 
 
 
 
27
  };
 
 
 
28
  }
29
  // Form errors.
30
  scope.errors = {
@@ -60,7 +72,40 @@
60
  jQuery('body').addClass('modal-open');
61
  }
62
  });
63
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  /**
65
  * Send form to server.
66
  */
@@ -84,15 +129,24 @@
84
  // Send new customer to the parent scope.
85
  scope.callback({customer : response.customer});
86
  scope.form = {
87
- id : '',
88
- wp_user_id : '',
89
- full_name : '',
90
- first_name : '',
91
- last_name : '',
92
- phone : '',
93
- email : '',
94
- notes : '',
95
- birthday : ''
 
 
 
 
 
 
 
 
 
96
  };
97
  // Close the dialog.
98
  element.modal('hide');
@@ -120,6 +174,22 @@
120
  * Datepicker options.
121
  */
122
  scope.dateOptions = BooklyL10nCustDialog.dateOptions;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  }
124
  };
125
  });
15
  // Form fields.
16
  if (!scope.form) {
17
  scope.form = {
18
+ id : '',
19
+ wp_user_id : '',
20
+ group_id : '',
21
+ full_name : '',
22
+ first_name : '',
23
+ last_name : '',
24
+ phone : '',
25
+ email : '',
26
+ country : '',
27
+ state : '',
28
+ postcode : '',
29
+ city : '',
30
+ street : '',
31
+ street_number : '',
32
+ additional_address : '',
33
+ info_fields : [],
34
+ notes : '',
35
+ birthday : ''
36
  };
37
+ BooklyL10nCustDialog.infoFields.forEach(function (field) {
38
+ scope.form.info_fields.push({id: field.id, value: field.type === 'checkboxes' ? [] : ''});
39
+ });
40
  }
41
  // Form errors.
42
  scope.errors = {
72
  jQuery('body').addClass('modal-open');
73
  }
74
  });
75
+ scope.changeWpUser = function () {
76
+ var $user = jQuery('#wp_user option:selected'),
77
+ email = $user.attr('data-email'),
78
+ first_name = $user.attr('data-first-name'),
79
+ last_name = $user.attr('data-last-name'),
80
+ phone = $user.attr('data-phone'),
81
+ display_name = $user.text().trim();
82
+ if (BooklyL10nCustDialog.first_last_name == 1) {
83
+ if (!first_name.length && !last_name.length) {
84
+ var name_parts = display_name.split(' ');
85
+ first_name = name_parts[0];
86
+ name_parts.splice(0, 1);
87
+ last_name = name_parts.join(' ');
88
+ }
89
+ if (first_name.length) {
90
+ scope.form.first_name = first_name;
91
+ }
92
+ if (last_name.length) {
93
+ scope.form.last_name = last_name;
94
+ }
95
+ } else {
96
+ if (first_name.length || last_name.length) {
97
+ scope.form.full_name = (first_name + ' ' + last_name).trim();
98
+ } else {
99
+ scope.form.full_name = display_name;
100
+ }
101
+ }
102
+ if (email.length) {
103
+ scope.form.email = email;
104
+ }
105
+ if (phone.length) {
106
+ scope.form.phone = phone;
107
+ }
108
+ };
109
  /**
110
  * Send form to server.
111
  */
129
  // Send new customer to the parent scope.
130
  scope.callback({customer : response.customer});
131
  scope.form = {
132
+ id : '',
133
+ wp_user_id : '',
134
+ group_id : '',
135
+ full_name : '',
136
+ first_name : '',
137
+ last_name : '',
138
+ phone : '',
139
+ email : '',
140
+ country : '',
141
+ state : '',
142
+ postcode : '',
143
+ city : '',
144
+ street : '',
145
+ street_number : '',
146
+ additional_address : '',
147
+ info_fields : [],
148
+ notes : '',
149
+ birthday : ''
150
  };
151
  // Close the dialog.
152
  element.modal('hide');
174
  * Datepicker options.
175
  */
176
  scope.dateOptions = BooklyL10nCustDialog.dateOptions;
177
+
178
+ /**
179
+ * Toggle checkbox info field.
180
+ */
181
+ scope.toggleCheckbox = function (i, value) {
182
+ var idx = scope.form.info_fields[i].value.indexOf(value);
183
+
184
+ // Is currently selected.
185
+ if (idx > -1) {
186
+ scope.form.info_fields[i].value.splice(idx, 1);
187
+ }
188
+ // Is newly selected.
189
+ else {
190
+ scope.form.info_fields[i].value.push(value);
191
+ }
192
+ };
193
  }
194
  };
195
  });
backend/{modules/customers/templates/_customer_dialog.php → components/dialogs/customer/templates/edit.php} RENAMED
@@ -1,6 +1,7 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Utils\Common;
3
- use BooklyLite\Lib\Config;
 
4
  ?>
5
  <script type="text/ng-template" id="bookly-customer-dialog.tpl">
6
  <div id="bookly-customer-dialog" class="modal fade" tabindex=-1 role="dialog">
@@ -8,18 +9,19 @@ use BooklyLite\Lib\Config;
8
  <div class="modal-content">
9
  <div class="modal-header">
10
  <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
11
- <div class="modal-title h2"><?php _e( 'New Customer', 'bookly' ) ?></div>
12
  </div>
13
  <div ng-show=loading class="modal-body">
14
  <div class="bookly-loading"></div>
15
  </div>
16
  <div class="modal-body" ng-hide="loading">
17
  <div class="form-group">
18
- <label for="wp_user"><?php _e( 'User', 'bookly' ) ?></label>
19
- <select ng-model="form.wp_user_id" class="form-control" id="wp_user">
20
  <option value=""></option>
21
- <?php foreach ( get_users( array( 'fields' => array( 'ID', 'display_name' ), 'orderby' => 'display_name' ) ) as $wp_user ) : ?>
22
- <option value="<?php echo $wp_user->ID ?>">
 
23
  <?php echo $wp_user->display_name ?>
24
  </option>
25
  <?php endforeach ?>
@@ -30,50 +32,50 @@ use BooklyLite\Lib\Config;
30
  <div class="form-group">
31
  <div class="row">
32
  <div class="col-sm-6">
33
- <label for="first_name"><?php _e( 'First name', 'bookly' ) ?></label>
34
  <input class="form-control" type="text" ng-model="form.first_name" id="first_name" />
35
- <span style="font-size: 11px;color: red" ng-show="errors.first_name.required"><?php _e( 'Required', 'bookly' ) ?></span>
36
  </div>
37
  <div class="col-sm-6">
38
- <label for="last_name"><?php _e( 'Last name', 'bookly' ) ?></label>
39
  <input class="form-control" type="text" ng-model="form.last_name" id="last_name" />
40
- <span style="font-size: 11px;color: red" ng-show="errors.last_name.required"><?php _e( 'Required', 'bookly' ) ?></span>
41
  </div>
42
  </div>
43
  </div>
44
  <?php else : ?>
45
- <div class="form-group">
46
- <label for="full_name"><?php _e( 'Name', 'bookly' ) ?></label>
47
- <input class="form-control" type="text" ng-model="form.full_name" id="full_name" />
48
- <span style="font-size: 11px;color: red" ng-show="errors.full_name.required"><?php _e( 'Required', 'bookly' ) ?></span>
49
- </div>
50
  <?php endif ?>
51
 
52
  <div class="form-group">
53
- <label for="phone"><?php _e( 'Phone', 'bookly' ) ?></label>
54
  <input class="form-control" type="text" ng-model=form.phone id="phone" />
55
  </div>
56
 
57
  <div class="form-group">
58
- <label for="email"><?php _e( 'Email', 'bookly' ) ?></label>
59
  <input class="form-control" type="text" ng-model=form.email id="email" />
60
  </div>
61
 
 
 
 
 
 
62
  <div class="form-group">
63
- <label for="notes"><?php _e( 'Notes', 'bookly' ) ?></label>
64
  <textarea class="form-control" ng-model=form.notes id="notes"></textarea>
65
  </div>
66
 
67
- <div class="form-group">
68
- <label for="birthday"><?php _e( 'Date of birth', 'bookly' ) ?></label>
69
- <input class="form-control" type="text" ng-model=form.birthday id="birthday"
70
- ui-date="dateOptions" ui-date-format="yy-mm-dd" autocomplete="off" />
71
- </div>
72
  </div>
73
  <div class="modal-footer">
74
  <div ng-hide=loading>
75
- <?php Common::customButton( null, 'btn-success btn-lg', '', array( 'ng-click' => 'processForm()' ) ) ?>
76
- <?php Common::customButton( null, 'btn-default btn-lg', __( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
77
  </div>
78
  </div>
79
  </div>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ use Bookly\Backend\Components\Dialogs\Customer\Proxy;
4
+ use Bookly\Lib\Config;
5
  ?>
6
  <script type="text/ng-template" id="bookly-customer-dialog.tpl">
7
  <div id="bookly-customer-dialog" class="modal fade" tabindex=-1 role="dialog">
9
  <div class="modal-content">
10
  <div class="modal-header">
11
  <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
12
+ <div class="modal-title h2"><?php esc_html_e( 'New Customer', 'bookly' ) ?></div>
13
  </div>
14
  <div ng-show=loading class="modal-body">
15
  <div class="bookly-loading"></div>
16
  </div>
17
  <div class="modal-body" ng-hide="loading">
18
  <div class="form-group">
19
+ <label for="wp_user"><?php esc_html_e( 'User', 'bookly' ) ?></label>
20
+ <select ng-model="form.wp_user_id" class="form-control" id="wp_user" ng-change="changeWpUser()">
21
  <option value=""></option>
22
+ <?php foreach ( get_users( array( 'fields' => array( 'ID', 'display_name', 'user_email' ), 'orderby' => 'display_name' ) ) as $wp_user ) : ?>
23
+ <?php $user_data = get_userdata( $wp_user->ID ) ?>
24
+ <option value="<?php echo $wp_user->ID ?>" data-email="<?php echo esc_html( $wp_user->user_email ) ?>" data-first-name="<?php echo esc_html( $user_data->first_name ) ?>" data-last-name="<?php echo esc_html( $user_data->last_name ) ?>" data-phone="<?php echo esc_html( get_user_meta( $wp_user->ID, 'billing_phone', true ) ) ?>">
25
  <?php echo $wp_user->display_name ?>
26
  </option>
27
  <?php endforeach ?>
32
  <div class="form-group">
33
  <div class="row">
34
  <div class="col-sm-6">
35
+ <label for="first_name"><?php esc_html_e( 'First name', 'bookly' ) ?></label>
36
  <input class="form-control" type="text" ng-model="form.first_name" id="first_name" />
37
+ <span style="font-size: 11px;color: red" ng-show="errors.first_name.required"><?php esc_html_e( 'Required', 'bookly' ) ?></span>
38
  </div>
39
  <div class="col-sm-6">
40
+ <label for="last_name"><?php esc_html_e( 'Last name', 'bookly' ) ?></label>
41
  <input class="form-control" type="text" ng-model="form.last_name" id="last_name" />
42
+ <span style="font-size: 11px;color: red" ng-show="errors.last_name.required"><?php esc_html_e( 'Required', 'bookly' ) ?></span>
43
  </div>
44
  </div>
45
  </div>
46
  <?php else : ?>
47
+ <div class="form-group">
48
+ <label for="full_name"><?php esc_html_e( 'Name', 'bookly' ) ?></label>
49
+ <input class="form-control" type="text" ng-model="form.full_name" id="full_name" />
50
+ <span style="font-size: 11px;color: red" ng-show="errors.full_name.required"><?php esc_html_e( 'Required', 'bookly' ) ?></span>
51
+ </div>
52
  <?php endif ?>
53
 
54
  <div class="form-group">
55
+ <label for="phone"><?php esc_html_e( 'Phone', 'bookly' ) ?></label>
56
  <input class="form-control" type="text" ng-model=form.phone id="phone" />
57
  </div>
58
 
59
  <div class="form-group">
60
+ <label for="email"><?php esc_html_e( 'Email', 'bookly' ) ?></label>
61
  <input class="form-control" type="text" ng-model=form.email id="email" />
62
  </div>
63
 
64
+ <?php Proxy\Pro::renderCustomerDialogBirthday() ?>
65
+ <?php Proxy\Pro::renderCustomerDialogAddress() ?>
66
+ <?php Proxy\CustomerInformation::renderCustomerDialog() ?>
67
+ <?php Proxy\CustomerGroups::renderCustomerDialog() ?>
68
+
69
  <div class="form-group">
70
+ <label for="notes"><?php esc_html_e( 'Notes', 'bookly' ) ?></label>
71
  <textarea class="form-control" ng-model=form.notes id="notes"></textarea>
72
  </div>
73
 
 
 
 
 
 
74
  </div>
75
  <div class="modal-footer">
76
  <div ng-hide=loading>
77
+ <?php Buttons::renderCustom( null, 'btn-success btn-lg', null, array( 'ng-click' => 'processForm()' ) ) ?>
78
+ <?php Buttons::renderCustom( null, 'btn-default btn-lg', __( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
79
  </div>
80
  </div>
81
  </div>
backend/components/dialogs/payment/Ajax.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Payment;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Ajax
8
+ * @package Bookly\Backend\Components\Dialogs\Payment
9
+ */
10
+ class Ajax extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * @inheritdoc
14
+ */
15
+ protected static function permissions()
16
+ {
17
+ return array(
18
+ 'completePayment' => 'user',
19
+ 'getPaymentDetails' => 'user',
20
+ 'getPaymentInfo' => 'user',
21
+ );
22
+ }
23
+
24
+ /**
25
+ * Get payment details.
26
+ */
27
+ public static function getPaymentDetails()
28
+ {
29
+ $payment = Lib\Entities\Payment::find( self::parameter( 'payment_id' ) );
30
+ if ( $payment ) {
31
+ $data = $payment->getPaymentData();
32
+ $show_deposit = Lib\Config::depositPaymentsActive();
33
+ if ( ! $show_deposit ) {
34
+ foreach ( $data['payment']['items'] as $item ) {
35
+ if ( isset( $item['deposit_format'] ) ) {
36
+ $show_deposit = true;
37
+ break;
38
+ }
39
+ }
40
+ }
41
+
42
+ $data['show'] = array(
43
+ 'coupons' => Lib\Config::couponsActive(),
44
+ 'customer_groups' => Lib\Config::customerGroupsActive(),
45
+ 'deposit' => (int) $show_deposit,
46
+ 'gateway' => \Bookly\Backend\Modules\Payments\Proxy\Shared::paymentSpecificPriceExists( $data['payment']['type'] ) === true,
47
+ 'taxes' => (int) ( Lib\Config::taxesActive() || $data['payment']['tax_total'] > 0 ),
48
+ );
49
+ wp_send_json_success( array( 'html' => self::renderTemplate( 'details', $data, false ) ) );
50
+ }
51
+
52
+ wp_send_json_error( array( 'html' => __( 'Payment is not found.', 'bookly' ) ) );
53
+ }
54
+
55
+ /**
56
+ * Complete payment.
57
+ */
58
+ public static function completePayment()
59
+ {
60
+ $payment = Lib\Entities\Payment::find( self::parameter( 'payment_id' ) );
61
+ $details = json_decode( $payment->getDetails(), true );
62
+ $details['tax_paid'] = $payment->getTax();
63
+ $payment
64
+ ->setPaid( $payment->getTotal() )
65
+ ->setStatus( Lib\Entities\Payment::STATUS_COMPLETED )
66
+ ->setDetails( json_encode( $details ) )
67
+ ->save();
68
+
69
+ $payment_title = Lib\Utils\Price::format( $payment->getPaid() );
70
+ if ( $payment->getPaid() != $payment->getTotal() ) {
71
+ $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Price::format( $payment->getTotal() ) );
72
+ }
73
+ $payment_title .= sprintf(
74
+ ' %s <span%s>%s</span>',
75
+ Lib\Entities\Payment::typeToString( $payment->getType() ),
76
+ $payment->getStatus() == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
77
+ Lib\Entities\Payment::statusToString( $payment->getStatus() )
78
+ );
79
+
80
+ wp_send_json_success( array( 'payment_title' => $payment_title ) );
81
+ }
82
+
83
+ /**
84
+ * Get payment info
85
+ */
86
+ public static function getPaymentInfo()
87
+ {
88
+ $payment = Lib\Entities\Payment::find( self::parameter( 'payment_id' ) );
89
+
90
+ if ( $payment ) {
91
+ $payment_title = Lib\Utils\Price::format( $payment->getPaid() );
92
+ if ( $payment->getPaid() != $payment->getTotal() ) {
93
+ $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Price::format( $payment->getTotal() ) );
94
+ }
95
+ $payment_title .= sprintf(
96
+ ' %s <span%s>%s</span>',
97
+ Lib\Entities\Payment::typeToString( $payment->getType() ),
98
+ $payment->getStatus() == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
99
+ Lib\Entities\Payment::statusToString( $payment->getStatus() )
100
+ );
101
+
102
+ wp_send_json_success( array( 'payment_title' => $payment_title, 'payment_type' => $payment->getPaid() == $payment->getTotal() ? 'full' : 'partial' ) );
103
+ }
104
+ }
105
+ }
backend/components/dialogs/payment/Dialog.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Payment;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Details
8
+ * @package Bookly\Backend\Components\Dialogs\Payment
9
+ */
10
+ class Dialog extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render payment details dialog.
14
+ */
15
+ public static function render()
16
+ {
17
+ self::enqueueStyles( array(
18
+ 'frontend' => array( 'css/ladda.min.css', ),
19
+ ) );
20
+
21
+ self::enqueueScripts( array(
22
+ 'backend' => array( 'js/angular.min.js' => array( 'jquery' ), ),
23
+ 'frontend' => array(
24
+ 'js/spin.min.js' => array( 'jquery' ),
25
+ 'js/ladda.min.js' => array( 'jquery' ),
26
+ ),
27
+ 'module' => array( 'js/ng-payment_details.js' => array( 'bookly-angular.min.js' ), ),
28
+ ) );
29
+
30
+ self::renderTemplate( 'dialog' );
31
+ }
32
+ }
backend/components/dialogs/payment/resources/js/ng-payment_details.js ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ;(function() {
2
+
3
+ angular.module('paymentDetailsDialog', []).directive('paymentDetailsDialog', function() {
4
+ return {
5
+ restrict: 'A',
6
+ replace: true,
7
+ scope: {
8
+ callback: '&paymentDetailsDialog'
9
+ },
10
+ templateUrl: 'bookly-payment-details-dialog.tpl',
11
+ // The linking function will add behavior to the template.
12
+ link: function (scope, element, attrs) {
13
+ var $body = element.find('.modal-body'),
14
+ spinner = $body.html();
15
+
16
+ element
17
+ .on('show.bs.modal refresh', function (e, payment_id) {
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({
30
+ url: ajaxurl,
31
+ data: {action: 'bookly_get_payment_details', payment_id: payment_id, csrf_token : BooklyL10n.csrf_token},
32
+ dataType: 'json',
33
+ success: function (response) {
34
+ if (response.success) {
35
+ $body.html(response.data.html);
36
+ if (payment_bind) {
37
+ jQuery('.bookly-js-details-main-controls').hide();
38
+ jQuery('.bookly-js-details-bind-controls').show();
39
+ }
40
+ $body.find('#bookly-complete-payment').on('click',function () {
41
+ var ladda = Ladda.create(this);
42
+ ladda.start();
43
+ jQuery.ajax({
44
+ url: ajaxurl,
45
+ data: {action: 'bookly_complete_payment', payment_id: payment_id, csrf_token : BooklyL10n.csrf_token},
46
+ dataType: 'json',
47
+ type: 'POST',
48
+ success: function (response) {
49
+ if (response.success) {
50
+ element.trigger('refresh', [payment_id]);
51
+ if (scope.callback) {
52
+ scope.$apply(function ($scope) {
53
+ $scope.callback({
54
+ payment_action: 'complete',
55
+ payment_id : payment_id,
56
+ payment_title : response.data.payment_title
57
+ });
58
+ });
59
+ }
60
+ // Reload DataTable.
61
+ var $table = jQuery('table#bookly-payments-list.dataTable');
62
+ if ($table.length) {
63
+ $table.DataTable().ajax.reload();
64
+ }
65
+ }
66
+ }
67
+ });
68
+ });
69
+ jQuery('#bookly-js-attach-payment', $body).on('click', function () {
70
+ var ladda = Ladda.create(this);
71
+ ladda.start();
72
+
73
+ jQuery.ajax({
74
+ url : ajaxurl,
75
+ data : {action: 'bookly_get_payment_info', payment_id: payment_id, csrf_token: BooklyL10n.csrf_token},
76
+ dataType: 'json',
77
+ type : 'POST',
78
+ success : function (response) {
79
+ if (response.success) {
80
+ if (scope.callback) {
81
+ scope.$apply(function ($scope) {
82
+ $scope.callback({
83
+ payment_action: 'bind',
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
+ }
91
+ }
92
+ }
93
+ });
94
+ jQuery(element).modal('hide');
95
+ });
96
+ var $adjust_button = jQuery('#bookly-js-adjustment-button', $body),
97
+ $adjust_field = jQuery('#bookly-js-adjustment-field', $body),
98
+ $adjust_reason = jQuery('#bookly-js-adjustment-reason', $body),
99
+ $adjust_amount = jQuery('#bookly-js-adjustment-amount', $body),
100
+ $adjust_tax = jQuery('#bookly-js-adjustment-tax', $body),
101
+ $adjust_apply = jQuery('#bookly-js-adjustment-apply', $body),
102
+ $adjust_cancel = jQuery('#bookly-js-adjustment-cancel', $body);
103
+ $adjust_button.on('click', function () {
104
+ $adjust_field.show();
105
+ $adjust_reason.focus();
106
+ });
107
+ $adjust_cancel.on('click', function () {
108
+ $adjust_field.hide();
109
+ });
110
+ $adjust_apply.on('click', function () {
111
+ $body.html('<div class="bookly-loading"></div>');
112
+ jQuery.ajax({
113
+ url : ajaxurl,
114
+ data : {
115
+ action : 'bookly_pro_add_payment_adjustment',
116
+ payment_id: payment_id,
117
+ reason: $adjust_reason.val(),
118
+ amount: $adjust_amount.val(),
119
+ tax : $adjust_tax.val() || 0,
120
+ csrf_token: BooklyL10n.csrf_token
121
+ },
122
+ dataType: 'json',
123
+ type : 'POST',
124
+ success : function (response) {
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
+ }
132
+ }
133
+ }
134
+ });
135
+ });
136
+ } else {
137
+ $body.html(response.data.html);
138
+ }
139
+ }
140
+ });
141
+ })
142
+ .on('hidden.bs.modal', function () {
143
+ $body.html(spinner);
144
+ if ((jQuery("#bookly-appointment-dialog").data('bs.modal') || {isShown: false}).isShown) {
145
+ jQuery('body').addClass('modal-open');
146
+ }
147
+ });
148
+ }
149
+ }
150
+ });
151
+ })();
backend/components/dialogs/payment/templates/details.php ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>
29
+ </table>
30
+ </div>
31
+
32
+ <div class="table-responsive">
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>
48
+ <tbody>
49
+ <?php foreach ( $payment['items'] as $item ) : ?>
50
+ <tr>
51
+ <td>
52
+ <?php if ( $item['number_of_persons'] > 1 ) echo $item['number_of_persons'] . '&nbsp;&times;&nbsp;' ?><?php echo esc_html( $item['service_name'] ) ?><?php if ( isset( $item['units'], $item['duration'] ) && $item['units'] > 1 ) echo '&nbsp;(' . DateTime::secondsToInterval( $item['units'] * $item['duration'] ) . ')' ?>
53
+ <?php if ( ! empty ( $item['extras'] ) ) : ?>
54
+ <ul class="bookly-list list-dots">
55
+ <?php foreach ( $item['extras'] as $extra ) : ?>
56
+ <li><?php if ( $extra['quantity'] > 1 ) echo $extra['quantity'] . '&nbsp;&times;&nbsp;' ?><?php echo esc_html( $extra['title'] ) ?></li>
57
+ <?php endforeach ?>
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>
65
+ <?php endif ?>
66
+ <td class="text-right">
67
+ <?php $service_price = Price::format( $item['service_price'] ) ?>
68
+ <?php if ( $payment['from_backend'] ) : ?>
69
+ <?php echo $service_price ?>
70
+ <?php else : ?>
71
+ <?php if ( $item['number_of_persons'] > 1 ) $service_price = $item['number_of_persons'] . '&nbsp;&times;&nbsp' . $service_price ?>
72
+ <?php echo $service_price ?>
73
+ <ul class="bookly-list">
74
+ <?php foreach ( $item['extras'] as $extra ) : ?>
75
+ <li>
76
+ <?php printf( '%s%s%s',
77
+ ( $item['number_of_persons'] > 1 && $payment['extras_multiply_nop'] ) ? $item['number_of_persons'] . '&nbsp;&times;&nbsp;' : '',
78
+ ( $extra['quantity'] > 1 ) ? $extra['quantity'] . '&nbsp;&times;&nbsp;' : '',
79
+ Price::format( $extra['price'] )
80
+ ) ?>
81
+ </li>
82
+ <?php endforeach ?>
83
+ </ul>
84
+ <?php endif ?>
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>
92
+ <?php endforeach ?>
93
+ </tbody>
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 ?>
101
+ <th class="text-right"><?php echo Price::format( $payment['subtotal']['price'] ) ?></th>
102
+ <?php if ( $show['taxes'] ) : ?><th></th><?php endif ?>
103
+ </tr>
104
+ <?php if ( $show['coupons'] || $payment['coupon'] ) : ?>
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">
112
+ <?php if ( $payment['coupon'] ) : ?>
113
+ <?php if ( $payment['coupon']['discount'] ) : ?>
114
+ <div><?php echo $payment['coupon']['discount'] ?>%</div>
115
+ <?php endif ?>
116
+ <?php if ( $payment['coupon']['deduction'] ) : ?>
117
+ <div><?php echo Price::format( $payment['coupon']['deduction'] ) ?></div>
118
+ <?php endif ?>
119
+ <?php else : ?>
120
+ <?php echo Price::format( 0 ) ?>
121
+ <?php endif ?>
122
+ </th>
123
+ <?php if ( $show['taxes'] ) : ?>
124
+ <th></th>
125
+ <?php endif ?>
126
+ </tr>
127
+ <?php endif ?>
128
+ <?php if ( $show['customer_groups'] || $payment['group_discount'] ) : ?>
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 ) ?>
136
+ </th>
137
+ <?php if ( $show['taxes'] ) : ?><th></th><?php endif ?>
138
+ </tr>
139
+ <?php endif ?>
140
+ <?php foreach ( $adjustments as $adjustment ) : ?>
141
+ <tr>
142
+ <th style="border-left-color:#fff;border-bottom-color:#fff;"></th>
143
+ <th colspan="<?php echo 2 + $show['deposit'] ?>">
144
+ <?php echo esc_html( $adjustment['reason'] ) ?>
145
+ </th>
146
+ <th class="text-right"><?php echo Price::format( $adjustment['amount'] ) ?></th>
147
+ <?php if ( $show['taxes'] ) : ?>
148
+ <th class="text-right"><?php echo Price::format( $adjustment['tax'] ) ?></th>
149
+ <?php endif ?>
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'] ) ?>
163
+ </th>
164
+ <?php if ( $show['taxes'] ) : ?>
165
+ <td class="text-right">-</td>
166
+ <?php endif ?>
167
+ </tr>
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">
175
+ (<?php echo Price::format( $payment['tax_total'] ) ?>)
176
+ </th>
177
+ <?php endif ?>
178
+ </tr>
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>
193
+ <?php if ( $show['taxes'] ) : ?>
194
+ <th class="text-right"><i>(<?php echo Price::format( $payment['tax_total'] - $payment['tax_paid'] ) ?>)</i></th>
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>
213
+ <?php endif ?>
214
+ </tfoot>
215
+ </table>
216
+ </div>
217
+ <?php endif ?>
backend/{modules/payments/templates/_payment_details_dialog.php → components/dialogs/payment/templates/dialog.php} RENAMED
@@ -1,4 +1,6 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
2
  <script type="text/ng-template" id="bookly-payment-details-dialog.tpl">
3
  <div class="modal fade" id="bookly-payment-details-modal" tabindex="-1" role="dialog">
4
  <div class="modal-dialog" role="document">
@@ -11,7 +13,7 @@
11
  <div class="bookly-loading"></div>
12
  </div>
13
  <div class="modal-footer">
14
- <?php \BooklyLite\Lib\Utils\Common::customButton( null, 'btn-lg btn-default', __( 'Close', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
15
  </div>
16
  </div>
17
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ ?>
4
  <script type="text/ng-template" id="bookly-payment-details-dialog.tpl">
5
  <div class="modal fade" id="bookly-payment-details-modal" tabindex="-1" role="dialog">
6
  <div class="modal-dialog" role="document">
13
  <div class="bookly-loading"></div>
14
  </div>
15
  <div class="modal-footer">
16
+ <?php Buttons::renderCustom( null, 'btn-lg btn-default', __( 'Close', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
17
  </div>
18
  </div>
19
  </div>
backend/components/dialogs/special_price/proxy/SpecialHours.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\SpecialPrice\Proxy;
3
+
4
+ use Bookly\Lib as BooklyLib;
5
+
6
+ /**
7
+ * Class SpecialHours
8
+ * @package Bookly\Backend\Components\Dialogs\SpecialPrice\ProxyProviders
9
+ *
10
+ * @method static void renderSpecialPricePopup( int $staff_id ) Render popup for special price.
11
+ */
12
+ abstract class SpecialHours extends BooklyLib\Base\Proxy
13
+ {
14
+ }
backend/components/notices/CollectStats.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Notices;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CollectStats
8
+ * @package Bookly\Backend\Components\Notices
9
+ */
10
+ class CollectStats extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render collect stats notice.
14
+ */
15
+ public static function render()
16
+ {
17
+ if ( self::needShowCollectStatNotice() ) {
18
+ self::enqueueStyles( array(
19
+ 'frontend' => array( 'css/ladda.min.css', ),
20
+ ) );
21
+ self::enqueueScripts( array(
22
+ 'module' => array( 'js/collect-stats.js' => array( 'jquery' ), ),
23
+ ) );
24
+
25
+ self::renderTemplate( 'collect_stats', array( 'enabled' => get_option( 'bookly_gen_collect_stats' ) == '1' ) );
26
+ }
27
+ }
28
+
29
+ /**
30
+ * @return bool
31
+ */
32
+ public static function needShowCollectStatNotice()
33
+ {
34
+ if ( Lib\Utils\Common::isCurrentUserAdmin() ) {
35
+ $enabled = get_option( 'bookly_gen_collect_stats' ) == '1';
36
+ $user_id = get_current_user_id();
37
+ if (
38
+ $enabled && get_user_meta( $user_id, 'bookly_show_collecting_stats_notice', true ) ||
39
+ ! $enabled && ! get_user_meta( $user_id, 'bookly_dismiss_collect_stats_notice', true )
40
+ ) {
41
+ return true;
42
+ }
43
+ }
44
+
45
+ return false;
46
+ }
47
+ }
backend/components/notices/CollectStatsAjax.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Notices;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CollectStatsAjax
8
+ * @package Bookly\Backend\Components\Notices
9
+ */
10
+ class CollectStatsAjax extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * Dismiss 'Collecting stats' notice.
14
+ */
15
+ public static function dismissCollectingStatsNotice()
16
+ {
17
+ delete_user_meta( get_current_user_id(), 'bookly_show_collecting_stats_notice' );
18
+
19
+ wp_send_json_success();
20
+ }
21
+
22
+ /**
23
+ * Dismiss 'Collect stats' notice.
24
+ */
25
+ public static function dismissCollectStatsNotice()
26
+ {
27
+ update_user_meta( get_current_user_id(), 'bookly_dismiss_collect_stats_notice', 1 );
28
+
29
+ wp_send_json_success();
30
+ }
31
+
32
+ /**
33
+ * Enable collecting stats.
34
+ */
35
+ public static function enableCollectingStats()
36
+ {
37
+ update_option( 'bookly_gen_collect_stats', '1' );
38
+
39
+ wp_send_json_success();
40
+ }
41
+ }
backend/components/notices/Limitation.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Notices;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Limitation
8
+ * @package Bookly\Backend\Components\Notices
9
+ */
10
+ class Limitation extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render limitation notice.
14
+ */
15
+ public static function getHtml()
16
+ {
17
+ return self::renderTemplate( 'limitation', array(), false );
18
+ }
19
+ }
backend/components/notices/LiteRebranding.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Notices;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class LiteRebranding
8
+ * @package Bookly\Backend\Components\Notices
9
+ */
10
+ class LiteRebranding extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render subscribe notice.
14
+ */
15
+ public static function render()
16
+ {
17
+ if ( Lib\Utils\Common::isCurrentUserAdmin() &&
18
+ get_user_meta( get_current_user_id(), 'bookly_show_lite_rebranding_notice', true ) ) {
19
+
20
+ self::enqueueScripts( array(
21
+ 'module' => array( 'js/lite-rebranding.js' => array( 'jquery' ), ),
22
+ ) );
23
+
24
+ self::renderTemplate( 'lite_rebranding' );
25
+ }
26
+ }
27
+ }
backend/components/notices/LiteRebrandingAjax.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Notices;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class LiteRebrandingAjax
8
+ * @package Bookly\Backend\Components\Notices
9
+ */
10
+ class LiteRebrandingAjax extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * Dismiss 'Lite Rebranding' notice.
14
+ */
15
+ public static function dismissLiteRebrandingNotice()
16
+ {
17
+ delete_user_meta( get_current_user_id(), 'bookly_show_lite_rebranding_notice' );
18
+
19
+ wp_send_json_success();
20
+ }
21
+ }
backend/components/notices/Nps.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Notices;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Backend\Modules;
6
+
7
+ /**
8
+ * Class Nps
9
+ * @package Bookly\Backend\Components\Notices
10
+ */
11
+ class Nps extends Lib\Base\Component
12
+ {
13
+ /**
14
+ * Render Net Promoter Score notice.
15
+ */
16
+ public static function render()
17
+ {
18
+ if ( Lib\Utils\Common::isCurrentUserAdmin() ) {
19
+ $dismiss_value = get_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_nps_notice', true );
20
+ // Show notice 1 month after it was closed the last time.
21
+ if ( ! $dismiss_value || $dismiss_value > 1 && time() - $dismiss_value >= 30 * DAY_IN_SECONDS ) {
22
+ // Show notice 1 month after installation time.
23
+ if ( time() - Lib\Plugin::getInstallationTime() >= 30 * DAY_IN_SECONDS ) {
24
+ self::enqueueStyles( array(
25
+ 'frontend' => array( 'css/ladda.min.css', ),
26
+ 'module' => array( 'css/bootstrap-stars.css', ),
27
+ ) );
28
+
29
+ self::enqueueScripts( array(
30
+ 'backend' => array(
31
+ 'js/alert.js' => array( 'jquery' ),
32
+ ),
33
+ 'frontend' => array(
34
+ 'js/spin.min.js' => array( 'jquery' ),
35
+ 'js/ladda.min.js' => array( 'jquery' ),
36
+ ),
37
+ 'module' => array(
38
+ 'js/jquery.barrating.min.js' => array( 'jquery' ),
39
+ 'js/nps.js' => array( 'bookly-jquery.barrating.min.js', 'bookly-alert.js', 'bookly-ladda.min.js', ),
40
+ ),
41
+ ) );
42
+
43
+ self::renderTemplate( 'nps', array( 'current_user' => wp_get_current_user() ) );
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
backend/components/notices/NpsAjax.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Notices;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Backend\Modules;
6
+
7
+ /**
8
+ * Class NpsAjax
9
+ * @package Bookly\Backend\Components\Notices
10
+ */
11
+ class NpsAjax extends Lib\Base\Ajax
12
+ {
13
+ /**
14
+ * Send Net Promoter Score.
15
+ */
16
+ public static function npsSend()
17
+ {
18
+ $rate = self::parameter( 'rate' );
19
+ $msg = self::parameter( 'msg', '' );
20
+ $email = self::parameter( 'email', '' );
21
+
22
+ Lib\API::sendNps( $rate, $msg, $email );
23
+
24
+ update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_nps_notice', 1 );
25
+
26
+ wp_send_json_success( array( 'message' => __( 'Sent successfully.', 'bookly' ) ) );
27
+ }
28
+
29
+ /**
30
+ * Dismiss NPS notice.
31
+ */
32
+ public static function dismissNpsNotice()
33
+ {
34
+ if ( get_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_nps_notice', true ) != 1 ) {
35
+ update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_nps_notice', time() );
36
+ }
37
+
38
+ wp_send_json_success();
39
+ }
40
+ }
backend/components/notices/Subscribe.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Notices;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Subscribe
8
+ * @package Bookly\Backend\Components\Notices
9
+ */
10
+ class Subscribe extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render subscribe notice.
14
+ */
15
+ public static function render()
16
+ {
17
+ if ( Lib\Utils\Common::isCurrentUserAdmin() &&
18
+ ! get_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_subscribe_notice', true ) ) {
19
+
20
+ // Show notice 1 day after installation time.
21
+ if ( time() - Lib\Plugin::getInstallationTime() >= DAY_IN_SECONDS ) {
22
+ self::enqueueStyles( array(
23
+ 'frontend' => array( 'css/ladda.min.css', ),
24
+ ) );
25
+ self::enqueueScripts( array(
26
+ 'backend' => array( 'js/alert.js' => array( 'jquery' ), ),
27
+ 'frontend' => array(
28
+ 'js/spin.min.js' => array( 'jquery' ),
29
+ 'js/ladda.min.js' => array( 'jquery' ),
30
+ ),
31
+ 'module' => array( 'js/subscribe.js' => array( 'bookly-alert.js', 'bookly-ladda.min.js', ), ),
32
+ ) );
33
+
34
+ self::renderTemplate( 'subscribe' );
35
+ }
36
+ }
37
+ }
38
+ }
backend/components/notices/SubscribeAjax.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Notices;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class SubscribeAjax
8
+ * @package Bookly\Backend\Components\Notices
9
+ */
10
+ class SubscribeAjax extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * Subscribe to monthly emails.
14
+ */
15
+ public static function subscribe()
16
+ {
17
+ $email = self::parameter( 'email' );
18
+ if ( is_email( $email ) ) {
19
+ Lib\API::registerSubscriber( $email );
20
+
21
+ wp_send_json_success( array( 'message' => __( 'Sent successfully.', 'bookly' ) ) );
22
+ } else {
23
+ wp_send_json_error( array( 'message' => __( 'Invalid email.', 'bookly' ) ) );
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Dismiss subscribe notice.
29
+ */
30
+ public static function dismissSubscribeNotice()
31
+ {
32
+ update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_subscribe_notice', 1 );
33
+
34
+ wp_send_json_success();
35
+ }
36
+ }
backend/components/notices/proxy/Pro.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Notices\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Components\Notices\Proxy
9
+ *
10
+ * @method static void renderWelcome()
11
+ */
12
+ abstract class Pro extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/{modules/support → components/notices}/resources/css/bootstrap-stars.css RENAMED
File without changes
backend/components/notices/resources/js/collect-stats.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ jQuery(function ($) {
2
+ var $notice = $('#bookly-collect-stats-notice');
3
+ $notice.on('close.bs.alert', function () {
4
+ $.post(ajaxurl, {action: $notice.data('action'), csrf_token : SupportL10n.csrf_token});
5
+ });
6
+ $notice.find('#bookly-enable-collecting-stats-btn').on('click', function () {
7
+ $.post(ajaxurl, {action: 'bookly_enable_collecting_stats', csrf_token : SupportL10n.csrf_token});
8
+ });
9
+ });
backend/{modules/support → components/notices}/resources/js/jquery.barrating.min.js RENAMED
File without changes
backend/components/notices/resources/js/lite-rebranding.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ jQuery(function ($) {
2
+ var $notice = $('#bookly-lite-rebranding-notice');
3
+ $notice.on('close.bs.alert', function () {
4
+ $.post(ajaxurl, {action: $notice.data('action'), csrf_token : SupportL10n.csrf_token});
5
+ });
6
+ });
backend/{modules/support → components/notices}/resources/js/nps.js RENAMED
File without changes
backend/{modules/support → components/notices}/resources/js/subscribe.js RENAMED
File without changes
backend/components/notices/templates/collect_stats.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div id="bookly-tbs" class="wrap">
3
+ <div id="bookly-collect-stats-notice" class="alert alert-info bookly-tbs-body bookly-flexbox" data-action="bookly_dismiss_collect<?php if ( $enabled ): ?>ing<?php endif ?>_stats_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 if ( $enabled ): ?>
9
+ <?php esc_html_e( 'To help us improve Bookly, the plugin anonymously collects usage information. You can opt out of sharing the information in Settings > General.', 'bookly' ) ?>
10
+ <div class="bookly-margin-top-md">
11
+ <button type="button" class="btn btn-primary" data-dismiss="alert"><?php esc_html_e( 'Close', 'bookly' ) ?></button>
12
+ </div>
13
+ <?php else: ?>
14
+ <?php esc_html_e( 'Let the plugin anonymously collect usage information to help Bookly team improve the product.', 'bookly' ) ?>
15
+ <div class="bookly-margin-top-md">
16
+ <button type="button" class="btn btn-default" data-dismiss="alert"><?php esc_html_e( 'Disagree', 'bookly' ) ?></button>
17
+ <button type="button" class="btn btn-success" id="bookly-enable-collecting-stats-btn" data-dismiss="alert"><?php esc_html_e( 'Agree', 'bookly' ) ?></button>
18
+ </div>
19
+ <?php endif ?>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </div>
backend/components/notices/templates/limitation.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <b class="h4"><?php _e( 'This function is not available in the Bookly.', 'bookly' ) ?></b>
3
+ <br><br><?php _e( 'To get access to all Bookly features, lifetime free updates and 24/7 support, please upgrade to the Pro version of Bookly.<br>For more information visit', 'bookly' ) ?> <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://www.booking-wp-plugin.com</a>
backend/components/notices/templates/lite_rebranding.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div id="bookly-tbs" class="wrap">
3
+ <div id="bookly-lite-rebranding-notice" class="alert alert-info bookly-tbs-body bookly-flexbox" data-action="bookly_dismiss_lite_rebranding_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 printf( __( '<b>Bookly Lite rebrands into Bookly with more features available.</b><br/><br/>We have changed the architecture of Bookly Lite and Bookly to optimize the development of both plugin versions and add more features to the new free Bookly. To learn more about the major Bookly update, check our <a href="%s" target="_blank">blog post</a>.', 'bookly' ), 'https://www.booking-wp-plugin.com/bookly-major-update/?utm_source=bookly_admin&utm_medium=pro_not_active&utm_campaign=notification' ) ?>
9
+ </div>
10
+ </div>
11
+ </div>
12
+ </div>
backend/{modules/support/templates/_nps_notice.php → components/notices/templates/nps.php} RENAMED
@@ -1,10 +1,14 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
 
 
2
  <div id="bookly-tbs" class="wrap bookly-js-nps-notice">
3
  <div id="bookly-nps-notice" class="alert alert-info bookly-tbs-body bookly-flexbox">
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"></button>
8
  <div id="bookly-nps-quiz">
9
  <label><?php _e( 'How likely is it that you would recommend Bookly to a friend or colleague?', 'bookly' ) ?></label>
10
  <select id="bookly-nps-stars" class="hidden">
@@ -23,12 +27,12 @@
23
  <label for="bookly-nps-email" class="control-label"><?php _e( 'Please enter your email (optional)', 'bookly' ) ?></label>
24
  <input type="text" id="bookly-nps-email" class="form-control" value="<?php echo esc_attr( $current_user->user_email ) ?>" />
25
  </div>
26
- <?php \BooklyLite\Lib\Utils\Common::customButton( 'bookly-nps-btn', 'btn-success', __( 'Send', 'bookly' ) ) ?>
27
  </div>
28
  <div id="bookly-nps-thanks" style="display:none;">
29
  <?php printf(
30
  __( 'Please leave your feedback <a href="%s" target="_blank">here</a>.', 'bookly' ),
31
- $this::BOOKLY_WORDPRESS_URL
32
  ) ?>
33
  </div>
34
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ use Bookly\Backend\Components\Support\Lib\Urls;
4
+ use Bookly\Lib\Utils\Common;
5
+ ?>
6
  <div id="bookly-tbs" class="wrap bookly-js-nps-notice">
7
  <div id="bookly-nps-notice" class="alert alert-info bookly-tbs-body bookly-flexbox">
8
  <div class="bookly-flex-row">
9
  <div class="bookly-flex-cell" style="width:39px"><i class="alert-icon"></i></div>
10
  <div class="bookly-flex-cell">
11
+ <button type="button" class="close" data-dismiss="alert">&times;</button>
12
  <div id="bookly-nps-quiz">
13
  <label><?php _e( 'How likely is it that you would recommend Bookly to a friend or colleague?', 'bookly' ) ?></label>
14
  <select id="bookly-nps-stars" class="hidden">
27
  <label for="bookly-nps-email" class="control-label"><?php _e( 'Please enter your email (optional)', 'bookly' ) ?></label>
28
  <input type="text" id="bookly-nps-email" class="form-control" value="<?php echo esc_attr( $current_user->user_email ) ?>" />
29
  </div>
30
+ <?php Buttons::renderCustom( 'bookly-nps-btn', 'btn-success', __( 'Send', 'bookly' ) ) ?>
31
  </div>
32
  <div id="bookly-nps-thanks" style="display:none;">
33
  <?php printf(
34
  __( 'Please leave your feedback <a href="%s" target="_blank">here</a>.', 'bookly' ),
35
+ Common::prepareUrlReferrers( Urls::BOOKLY_CODECANYON_PAGE, 'nps' )
36
  ) ?>
37
  </div>
38
  </div>
backend/{modules/support/templates/_subscribe_notice.php → components/notices/templates/subscribe.php} RENAMED
@@ -4,7 +4,7 @@
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"></button>
8
  <label for="bookly-subscribe-email"><?php _e( 'Subscribe to monthly emails about Bookly improvements and new releases.', 'bookly' ) ?></label>
9
  <div class="input-group input-group-sm" style="max-width: 400px">
10
  <span class="input-group-addon"><i class="glyphicon glyphicon-envelope"></i></span>
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
  <label for="bookly-subscribe-email"><?php _e( 'Subscribe to monthly emails about Bookly improvements and new releases.', 'bookly' ) ?></label>
9
  <div class="input-group input-group-sm" style="max-width: 400px">
10
  <span class="input-group-addon"><i class="glyphicon glyphicon-envelope"></i></span>
backend/components/settings/Image.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Settings;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Image
8
+ * @package Bookly\Backend\Components\Settings
9
+ */
10
+ class Image extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render media image attachment.
14
+ *
15
+ * @param string $option_name
16
+ * @param string $class
17
+ */
18
+ public static function render( $option_name, $class = 'lg' )
19
+ {
20
+ $img = wp_get_attachment_image_src( get_option( $option_name ), 'full' );
21
+
22
+ self::renderTemplate( 'image', array(
23
+ 'option_name' => $option_name,
24
+ 'option_value' => get_option( $option_name ),
25
+ 'class' => $class,
26
+ 'img_style' => $img ? 'background-image: url(' . $img[0] . '); background-size: contain;' : '',
27
+ 'delete_style' => $img ? '' : 'display: none;',
28
+ ) );
29
+ }
30
+ }
backend/components/settings/Inputs.php ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Settings;
3
+
4
+ /**
5
+ * Class Inputs
6
+ * @package Bookly\Backend\Components\Settings
7
+ */
8
+ class Inputs
9
+ {
10
+ /**
11
+ * Render numeric input.
12
+ *
13
+ * @param string $option_name
14
+ * @param string $label
15
+ * @param string $help
16
+ * @param int|null $min
17
+ * @param int|null $step
18
+ * @param int|null $max
19
+ */
20
+ public static function renderNumber( $option_name, $label, $help, $min = null, $step = null, $max = null )
21
+ {
22
+ $control = strtr(
23
+ '<input type="number" id="{name}" class="form-control" name="{name}" value="{value}"{min}{max}{step} />',
24
+ array(
25
+ '{name}' => esc_attr( $option_name ),
26
+ '{value}' => esc_attr( get_option( $option_name ) ),
27
+ '{min}' => $min !== null ? ' min="' . $min . '"' : '',
28
+ '{max}' => $max !== null ? ' max="' . $max . '"' : '',
29
+ '{step}' => $step !== null ? ' step="' . $step . '"' : '',
30
+ )
31
+ );
32
+
33
+ echo self::buildControl( $option_name, $label, $help, $control );
34
+ }
35
+
36
+ /**
37
+ * Render text input.
38
+ *
39
+ * @param string $option_name
40
+ * @param string $label
41
+ * @param string|null $help
42
+ */
43
+ public static function renderText( $option_name, $label, $help = null )
44
+ {
45
+ $control = strtr(
46
+ '<input type="text" id="{name}" class="form-control" name="{name}" value="{value}" />',
47
+ array(
48
+ '{name}' => esc_attr( $option_name ),
49
+ '{value}' => esc_attr( get_option( $option_name ) ),
50
+ )
51
+ );
52
+
53
+ echo self::buildControl( $option_name, $label, $help, $control );
54
+ }
55
+
56
+ /**
57
+ * Build setting control.
58
+ *
59
+ * @param string $option_name
60
+ * @param string $label
61
+ * @param string $help
62
+ * @param string $control_html
63
+ * @return string
64
+ */
65
+ public static function buildControl( $option_name, $label, $help, $control_html )
66
+ {
67
+ return strtr(
68
+ '<div class="form-group">{label}{help}{control}</div>',
69
+ array(
70
+ '{label}' => $label != '' ? sprintf( '<label for="%s">%s</label>', $option_name, $label ) : '',
71
+ '{help}' => $help != '' ? sprintf( '<p class="help-block">%s</p>', $help ) : '',
72
+ '{control}' => $control_html,
73
+ )
74
+ );
75
+ }
76
+ }
backend/components/settings/Menu.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Settings;
3
+
4
+ /**
5
+ * Class Menu
6
+ * @package Bookly\Backend\Components\Settings
7
+ */
8
+ class Menu
9
+ {
10
+ /**
11
+ * Render menu item on settings page.
12
+ *
13
+ * @param string $title
14
+ * @param string $tab
15
+ */
16
+ public static function renderItem( $title, $tab )
17
+ {
18
+ printf( '<li class="bookly-nav-item" data-target="#bookly_settings_%s" data-toggle="tab">%s</li>',
19
+ $tab,
20
+ $title
21
+ );
22
+ }
23
+ }
backend/components/settings/Payments.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Settings;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Payments
8
+ * @package Bookly\Backend\Components\Settings
9
+ */
10
+ class Payments extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render discount and deduction for payment gateway.
14
+ *
15
+ * @param string $gateway
16
+ */
17
+ public static function renderPriceCorrection( $gateway )
18
+ {
19
+ self::renderTemplate( 'price_correction', compact( 'gateway' ) );
20
+ }
21
+
22
+ /**
23
+ * Render tax settings for payment gateway.
24
+ *
25
+ * @param string $gateway
26
+ */
27
+ public static function renderTax( $gateway )
28
+ {
29
+ if ( Lib\Config::taxesActive() ) {
30
+ Selects::renderSingle(
31
+ 'bookly_' . $gateway . '_send_tax',
32
+ __( 'Send tax information', 'bookly' ),
33
+ null,
34
+ array(
35
+ array( 0, __( 'No', 'bookly' ) ),
36
+ array( 1, __( 'Yes', 'bookly' ) ),
37
+ )
38
+ );
39
+ }
40
+ }
41
+ }
backend/components/settings/Selects.php ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Settings;
3
+
4
+ /**
5
+ * Class Selects
6
+ * @package Bookly\Backend\Components\Settings
7
+ */
8
+ class Selects
9
+ {
10
+ /**
11
+ * Render multiple select (checkbox group).
12
+ *
13
+ * @param string $option_name
14
+ * @param string $label
15
+ * @param string $help
16
+ * @param array $options
17
+ */
18
+ public static function renderMultiple( $option_name, $label = null, $help = null, array $options = array() )
19
+ {
20
+ $values = (array) get_option( $option_name );
21
+ $control = '';
22
+ foreach ( $options as $attr ) {
23
+ $control .= strtr(
24
+ '<div class="checkbox"><label><input type="checkbox" name="{name}[]" value="{value}"{checked} />{caption}</label></div>',
25
+ array(
26
+ '{name}' => $option_name,
27
+ '{value}' => esc_attr( $attr[0] ),
28
+ '{checked}' => checked( in_array( $attr[0], $values ), true, false ),
29
+ '{caption}' => esc_html( $attr[1] ),
30
+ )
31
+ );
32
+ }
33
+ $control = "<div class=\"bookly-flags\" id=\"$option_name\">$control</div>";
34
+
35
+ echo Inputs::buildControl( $option_name, $label, $help, $control );
36
+ }
37
+
38
+ /**
39
+ * Render drop-down select.
40
+ *
41
+ * @param string $option_name
42
+ * @param string $label
43
+ * @param array $options
44
+ * @param string $help
45
+ */
46
+ public static function renderSingle( $option_name, $label = null, $help = null, array $options = array() )
47
+ {
48
+ if ( empty ( $options ) ) {
49
+ $options = array(
50
+ // value title disabled
51
+ array( 0, __( 'Disabled', 'bookly' ), 0 ),
52
+ array( 1, __( 'Enabled', 'bookly' ), 0 ),
53
+ );
54
+ }
55
+
56
+ $options_str = '';
57
+ foreach ( $options as $attr ) {
58
+ $options_str .= strtr(
59
+ '<option value="{value}"{attr}>{caption}</option>',
60
+ array(
61
+ '{value}' => esc_attr( $attr[ 0 ] ),
62
+ '{attr}' => empty ( $attr[ 2 ] )
63
+ ? selected( get_option( $option_name ), $attr[0], false )
64
+ : disabled( true, true, false ),
65
+ '{caption}' => esc_html( $attr[1] ),
66
+ )
67
+ );
68
+ }
69
+
70
+ $control = strtr(
71
+ '<select id="{name}" class="form-control" name="{name}">{options}</select>',
72
+ array(
73
+ '{name}' => $option_name,
74
+ '{options}' => $options_str,
75
+ )
76
+ );
77
+
78
+ echo Inputs::buildControl( $option_name, $label, $help, $control );
79
+ }
80
+ }
backend/components/settings/proxy/Pro.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Settings\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Components\Settings\Proxy
9
+ *
10
+ * @method static void renderPurchaseCode( $blog_id = null ) Render purchase code
11
+ */
12
+ abstract class Pro extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/components/settings/proxy/Taxes.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Settings\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Taxes
8
+ * @package Bookly\Backend\Components\Settings\Proxy
9
+ *
10
+ * @method static void renderHelpMessage() Render tax help message.
11
+ */
12
+ abstract class Taxes extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/components/settings/templates/image.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div id="bookly-js-<?php echo esc_attr( $option_name ) ?>" class="bookly-thumb bookly-thumb-<?php echo $class ?> bookly-margin-right-lg">
3
+ <input type="hidden" name="<?php echo $option_name ?>" data-default="<?php echo esc_attr( $option_value ) ?>" value="<?php echo esc_attr( $option_value ) ?>">
4
+ <div class="bookly-flex-cell">
5
+ <div class="form-group">
6
+ <div class="bookly-js-image bookly-thumb bookly-thumb-<?php echo esc_attr( $class ) ?> bookly-margin-right-lg" style="<?php echo esc_attr( $img_style ) ?>" data-style="<?php echo esc_attr( $img_style ) ?>">
7
+ <a class="dashicons dashicons-trash text-danger bookly-thumb-delete" href="javascript:void(0)" style="<?php echo esc_attr( $delete_style ) ?>" title="<?php esc_attr_e( 'Delete', 'bookly' ) ?>"></a>
8
+ <div class="bookly-thumb-edit">
9
+ <div class="bookly-pretty"><label class="bookly-pretty-indicator bookly-thumb-edit-btn"><?php esc_html_e( 'Image', 'bookly' ) ?></label></div>
10
+ </div>
11
+ </div>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ <script type="text/javascript">
16
+ jQuery(function ($) {
17
+ $('#bookly-js-<?php echo $option_name ?> .bookly-pretty-indicator').on('click', function(){
18
+ var frame = wp.media({
19
+ library: {type: 'image'},
20
+ multiple: false
21
+ });
22
+ frame.on('select', function () {
23
+ var selection = frame.state().get('selection').toJSON(),
24
+ img_src
25
+ ;
26
+ if (selection.length) {
27
+ if (selection[0].sizes['full'] !== undefined) {
28
+ img_src = selection[0].sizes['full'].url;
29
+ } else {
30
+ img_src = selection[0].url;
31
+ }
32
+ $('[name=<?php echo $option_name ?>]').val(selection[0].id);
33
+ $('#bookly-js-<?php echo $option_name ?> .bookly-js-image').css({'background-image': 'url(' + img_src + ')', 'background-size': 'contain'});
34
+ $('#bookly-js-<?php echo $option_name ?> .bookly-thumb-delete').show();
35
+ $(this).hide();
36
+ }
37
+ });
38
+ frame.open();
39
+ });
40
+
41
+ $('#bookly-js-<?php echo $option_name ?>')
42
+ .on('click', '.bookly-thumb-delete', function () {
43
+ var $thumb = $(this).closest('.bookly-js-image');
44
+ $thumb.attr('style', '');
45
+ $('[name=<?php echo $option_name ?>]').val('');
46
+ });
47
+ });
48
+ </script>
backend/components/settings/templates/price_correction.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Settings;
3
+ use Bookly\Lib\Entities\Payment;
4
+ ?>
5
+ <label for="bookly_<?php echo $gateway ?>_discount"><?php _e( 'Price correction', 'bookly' ) ?></label>
6
+ <?php if ( ! in_array( $gateway, array( Payment::TYPE_MOLLIE, Payment::TYPE_PAYSON, Payment::TYPE_STRIPE, Payment::TYPE_PAYUBIZ ) ) ) :
7
+ Settings\Proxy\Taxes::renderHelpMessage();
8
+ endif ?>
9
+ <div class="form-group">
10
+ <div class="row">
11
+ <div class="col-md-6">
12
+ <?php Settings\Inputs::renderNumber( 'bookly_' . $gateway . '_increase', __( 'Increase/Discount (%)', 'bookly' ), '', -100, 'any', 100 ) ?>
13
+ </div>
14
+ <div class="col-md-6">
15
+ <?php Settings\Inputs::renderNumber( 'bookly_' . $gateway . '_addition', __( 'Addition/Deduction', 'bookly' ), '', null, 'any' ) ?>
16
+ </div>
17
+ </div>
18
+ </div>
backend/components/support/Buttons.php ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Support;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Backend\Modules;
6
+ use Bookly\Backend\Components\Notices;
7
+
8
+ /**
9
+ * Class Buttons
10
+ * @package Bookly\Backend\Components\Support
11
+ */
12
+ class Buttons extends Lib\Base\Component
13
+ {
14
+ /**
15
+ * Render support buttons.
16
+ *
17
+ * @param string $page_slug
18
+ */
19
+ public static function render( $page_slug )
20
+ {
21
+ static::enqueueStyles( array(
22
+ 'frontend' => array( 'css/ladda.min.css', ),
23
+ ) );
24
+
25
+ static::enqueueScripts( array(
26
+ 'backend' => array( 'js/alert.js' => array( 'jquery' ), ),
27
+ 'frontend' => array(
28
+ 'js/spin.min.js' => array( 'jquery' ),
29
+ 'js/ladda.min.js' => array( 'jquery' ),
30
+ ),
31
+ 'module' => array( 'js/support.js' => array( 'bookly-alert.js', 'bookly-ladda.min.js', ), ),
32
+ ) );
33
+
34
+ wp_localize_script( 'bookly-support.js', 'SupportL10n', array(
35
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken()
36
+ ) );
37
+
38
+ // Documentation link.
39
+ $doc_link = 'http://api.booking-wp-plugin.com/go/' . $page_slug;
40
+
41
+ $days_in_use = (int) ( ( time() - Lib\Plugin::getInstallationTime() ) / DAY_IN_SECONDS );
42
+
43
+ // Whether to show contact us notice or not.
44
+ $show_contact_us_notice = $days_in_use < 7 &&
45
+ ! get_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_contact_us_notice', true ) &&
46
+ ! Notices\CollectStats::needShowCollectStatNotice();
47
+
48
+ // Whether to show feedback notice.
49
+ $show_feedback_notice = $days_in_use >= 7 &&
50
+ ! get_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_feedback_notice', true ) &&
51
+ ! get_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'contact_us_btn_clicked', true );
52
+
53
+ $current_user = wp_get_current_user();
54
+
55
+ $messages = Lib\Entities\Message::query( 'm' )
56
+ ->select( 'm.created, m.subject, m.seen' )
57
+ ->sortBy( 'm.seen, m.message_id' )
58
+ ->order( 'DESC' )
59
+ ->limit( 10 )
60
+ ->fetchArray();
61
+ $messages_new = Lib\Entities\Message::query( 'm' )->where( 'm.seen', '0' )->count();
62
+ $messages_link = Lib\Utils\Common::escAdminUrl( Modules\Messages\Ajax::pageSlug() );
63
+
64
+ static::renderTemplate( 'buttons', compact(
65
+ 'doc_link',
66
+ 'show_contact_us_notice',
67
+ 'show_feedback_notice',
68
+ 'current_user',
69
+ 'messages',
70
+ 'messages_new',
71
+ 'messages_link'
72
+ ) );
73
+ }
74
+ }
backend/components/support/ButtonsAjax.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Support;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Backend\Modules;
6
+
7
+ /**
8
+ * Class ButtonsAjax
9
+ * @package Bookly\Backend\Components\Support
10
+ */
11
+ class ButtonsAjax extends Lib\Base\Ajax
12
+ {
13
+ /**
14
+ * Send support request.
15
+ */
16
+ public static function sendSupportRequest()
17
+ {
18
+ $name = trim( self::parameter( 'name' ) );
19
+ $email = trim( self::parameter( 'email' ) );
20
+ $msg = trim( self::parameter( 'msg' ) );
21
+
22
+ // Validation.
23
+ if ( $email == '' || $msg == '' ) {
24
+ wp_send_json_error( array( 'message' => __( 'All fields marked with an asterisk (*) are required.', 'bookly' ) ) );
25
+ }
26
+ if ( ! is_email( $email ) ) {
27
+ wp_send_json_error( array(
28
+ 'invalid_email' => true,
29
+ 'message' => __( 'Invalid email.', 'bookly' ),
30
+ ) );
31
+ }
32
+
33
+ $plugins = apply_filters( 'bookly_plugins', array() );
34
+ $message = self::renderTemplate( '_email_to_support', compact( 'name', 'email', 'msg', 'plugins' ), false );
35
+ $headers = array(
36
+ 'Content-Type: text/html; charset=utf-8',
37
+ 'From: ' . get_option( 'bookly_email_sender_name' ) . ' <' . get_option( 'bookly_email_sender' ) . '>',
38
+ 'Reply-To: ' . $name . ' <' . $email . '>'
39
+ );
40
+
41
+ if ( wp_mail( 'support@ladela.com', 'Support Request ' . site_url(), $message, $headers ) ) {
42
+ wp_send_json_success( array( 'message' => __( 'Sent successfully.', 'bookly' ) ) );
43
+ } else {
44
+ wp_send_json_error( array( 'message' => __( 'Error sending support request.', 'bookly' ) ) );
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Dismiss notice for 'Contact Us' button.
50
+ */
51
+ public static function dismissContactUsNotice()
52
+ {
53
+ update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_contact_us_notice', 1 );
54
+
55
+ wp_send_json_success();
56
+ }
57
+
58
+ /**
59
+ * Record click on 'Contact Us' button.
60
+ */
61
+ public static function contactUsBtnClicked()
62
+ {
63
+ update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_contact_us_notice', 1 );
64
+ update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'contact_us_btn_clicked', 1 );
65
+
66
+ wp_send_json_success();
67
+ }
68
+
69
+ /**
70
+ * Dismiss notice for 'Feedback' button.
71
+ */
72
+ public static function dismissFeedbackNotice()
73
+ {
74
+ update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_feedback_notice', 1 );
75
+
76
+ wp_send_json_success();
77
+ }
78
+ }
backend/components/support/lib/Urls.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Support\Lib;
3
+
4
+ /**
5
+ * Class Urls
6
+ * @package Bookly\Backend\Components\Support\Lib
7
+ */
8
+ abstract class Urls
9
+ {
10
+ const BOOKLY_CODECANYON_PAGE = 'https://codecanyon.net/item/bookly-booking-plugin-responsive-appointment-booking-and-scheduling/7226091?ref=ladela';
11
+ }
backend/{modules → components}/support/resources/js/support.js RENAMED
File without changes
backend/{modules → components}/support/templates/_email_to_support.php RENAMED
@@ -4,12 +4,16 @@
4
  <head>
5
  <meta name="viewport" content="width=device-width" />
6
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
7
- <title>Support Request <?php echo site_url() ?> Bookly Lite</title>
8
  </head>
9
  <body>
10
- Bookly Lite
11
  <p><?php echo esc_html( $name ) ?><br /><?php echo esc_html( $email ) ?></p>
12
  <p><?php echo nl2br( esc_html( $msg ) ) ?></p>
13
- <p><?php echo esc_html( $_SERVER["HTTP_REFERER"] ) ?></p>
 
 
 
 
 
14
  </body>
15
  </html>
4
  <head>
5
  <meta name="viewport" content="width=device-width" />
6
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
7
+ <title>Support Request <?php echo site_url() ?></title>
8
  </head>
9
  <body>
 
10
  <p><?php echo esc_html( $name ) ?><br /><?php echo esc_html( $email ) ?></p>
11
  <p><?php echo nl2br( esc_html( $msg ) ) ?></p>
12
+ <ol>
13
+ <?php foreach ( $plugins as $plugin ): ?>
14
+ <li><?php echo $plugin::getTitle() ?> v<?php echo $plugin::getVersion() ?>: <b><?php echo $plugin::getPurchaseCode() ?></b></li>
15
+ <?php endforeach ?>
16
+ </ol>
17
+ <p><?php echo esc_html( $_SERVER['HTTP_REFERER'] ) ?></p>
18
  </body>
19
  </html>
backend/{modules/support/templates/_buttons.php → components/support/templates/buttons.php} RENAMED
@@ -1,5 +1,8 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use \BooklyLite\Lib\Utils;
 
 
 
3
  ?>
4
 
5
  <style type="text/css">
@@ -42,7 +45,7 @@ use \BooklyLite\Lib\Utils;
42
  >
43
  <i class="bookly-icon bookly-icon-contact-us"></i><?php _e( 'Contact Us', 'bookly' ) ?>
44
  </a>
45
- <a href="<?php echo $this::BOOKLY_WORDPRESS_URL ?>" id="bookly-feedback-btn" target="_blank" class="btn btn-default-outline"
46
  data-toggle="modal"
47
  <?php if ( $show_feedback_notice ) : ?>
48
  data-trigger="manual" data-placement="bottom" data-html="1"
@@ -74,9 +77,9 @@ use \BooklyLite\Lib\Utils;
74
  </div>
75
  </div>
76
  <div class="modal-footer">
77
- <?php Utils\Common::csrf() ?>
78
- <?php Utils\Common::customButton( 'bookly-support-send', 'btn-success btn-lg', __( 'Send', 'bookly' ) ) ?>
79
- <?php Utils\Common::customButton( null, 'btn-default btn-lg', __( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
80
  </div>
81
  </div>
82
  </div>
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
+ use Bookly\Backend\Components\Support\Lib\Urls;
5
+ use Bookly\Lib\Utils;
6
  ?>
7
 
8
  <style type="text/css">
45
  >
46
  <i class="bookly-icon bookly-icon-contact-us"></i><?php _e( 'Contact Us', 'bookly' ) ?>
47
  </a>
48
+ <a href="<?php echo Utils\Common::prepareUrlReferrers( Urls::BOOKLY_CODECANYON_PAGE, 'feedback' ) ?>" id="bookly-feedback-btn" target="_blank" class="btn btn-default-outline"
49
  data-toggle="modal"
50
  <?php if ( $show_feedback_notice ) : ?>
51
  data-trigger="manual" data-placement="bottom" data-html="1"
77
  </div>
78
  </div>
79
  <div class="modal-footer">
80
+ <?php Inputs::renderCsrf() ?>
81
+ <?php Buttons::renderCustom( 'bookly-support-send', 'btn-success btn-lg', __( 'Send', 'bookly' ) ) ?>
82
+ <?php Buttons::renderCustom( null, 'btn-default btn-lg', __( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
83
  </div>
84
  </div>
85
  </div>
backend/components/tiny_mce/Tools.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\TinyMce;
3
+
4
+ use Bookly\Backend\Components\TinyMce\Proxy;
5
+ use Bookly\Lib;
6
+
7
+ /**
8
+ * Class Tools
9
+ * @package Bookly\Backend\Modules\TinyMce
10
+ */
11
+ class Tools extends Lib\Base\Component
12
+ {
13
+ public static function init()
14
+ {
15
+ global $PHP_SELF;
16
+ if ( // check if we are in admin area and current page is adding/editing the post
17
+ is_admin() && ( strpos( $PHP_SELF, 'post-new.php' ) !== false || strpos( $PHP_SELF, 'post.php' ) !== false || strpos( $PHP_SELF, 'admin-ajax.php' ) )
18
+ ) {
19
+ add_action( 'admin_footer', array( '\Bookly\Backend\Components\TinyMce\Tools', 'renderPopup' ), 10, 0 );
20
+ add_filter( 'media_buttons', array( '\Bookly\Backend\Components\TinyMce\Tools', 'addButton' ), 50, 1 );
21
+ }
22
+ }
23
+
24
+ public static function addButton( $editor_id )
25
+ {
26
+ // don't show on dashboard (QuickPress)
27
+ $current_screen = get_current_screen();
28
+ if ( $current_screen && 'dashboard' == $current_screen->base ) {
29
+ return;
30
+ }
31
+
32
+ // don't display button for users who don't have access
33
+ if ( ! current_user_can( 'edit_posts' ) && ! current_user_can( 'edit_pages' ) ) {
34
+ return;
35
+ }
36
+
37
+ // do a version check for the new 3.5 UI
38
+ $version = get_bloginfo( 'version' );
39
+
40
+ if ( $version < 3.5 ) {
41
+ // show button for v 3.4 and below
42
+ echo '<a href="#TB_inline?width=640&inlineId=bookly-tinymce-popup&height=650" id="add-bookly-form" title="' . esc_attr__( 'Add Bookly booking form', 'bookly' ) . '">' . __( 'Add Bookly booking form', 'bookly' ) . '</a>';
43
+ } else {
44
+ // display button matching new UI
45
+ $img = '<span class="bookly-media-icon"></span> ';
46
+ echo '<a href="#TB_inline?width=640&inlineId=bookly-tinymce-popup&height=650" id="add-bookly-form" class="thickbox button bookly-media-button" title="' . esc_attr__( 'Add Bookly booking form', 'bookly' ) . '">' . $img . __( 'Add Bookly booking form', 'bookly' ) . '</a>';
47
+ }
48
+ Proxy\Shared::renderMediaButtons( $version );
49
+ }
50
+
51
+ public static function renderPopup()
52
+ {
53
+ $casest = Lib\Config::getCaSeSt();
54
+ self::renderTemplate( 'bookly_form', compact( 'casest' ) );
55
+
56
+ Proxy\Shared::renderPopup();
57
+ }
58
+ }
backend/components/tiny_mce/proxy/DepositPayments.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\TinyMce\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class DepositPayments
8
+ * @package Bookly\Backend\Components\TinyMce\Proxy\Proxy
9
+ *
10
+ * @method static void renderStaffCabinetSettings() Render settings in Staff Cabinet shortcode.
11
+ */
12
+ abstract class DepositPayments extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/components/tiny_mce/proxy/GroupBooking.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\TinyMce\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class GroupBooking
8
+ * @package Bookly\Backend\Components\TinyMce\Proxy\Proxy
9
+ *
10
+ * @method static void renderStaffCabinetSettings() Render settings in Staff Cabinet shortcode.
11
+ */
12
+ abstract class GroupBooking extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/components/tiny_mce/proxy/Shared.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\TinyMce\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Shared
8
+ * @package Bookly\Backend\Components\TinyMce\Proxy
9
+ *
10
+ * @method static void renderMediaButtons( string $version ) Add buttons to WordPress editor.
11
+ * @method static void renderBooklyFormFields() Render controls in popup for bookly-form (build shortcode).
12
+ * @method static void renderBooklyFormHead() Render controls in header of popup for bookly-form (build shortcode).
13
+ * @method static void renderPopup() Render popup windows for WordPress editor.
14
+ */
15
+ abstract class Shared extends Lib\Base\Proxy
16
+ {
17
+
18
+ }
backend/components/tiny_mce/proxy/SpecialDays.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\TinyMce\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class SpecialDays
8
+ * @package Bookly\Backend\Components\TinyMce\Proxy\Proxy
9
+ *
10
+ * @method static void renderStaffCabinetSettings() Render settings in Staff Cabinet shortcode.
11
+ */
12
+ abstract class SpecialDays extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/components/tiny_mce/proxy/SpecialHours.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\TinyMce\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class SpecialHours
8
+ * @package Bookly\Backend\Components\TinyMce\Proxy\Proxy
9
+ *
10
+ * @method static void renderStaffCabinetSettings() Render settings in Staff Cabinet shortcode.
11
+ */
12
+ abstract class SpecialHours extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/{modules → components}/tiny_mce/resources/images/calendar.png RENAMED
File without changes
backend/{modules/tiny_mce/templates/popup.php → components/tiny_mce/templates/bookly_form.php} RENAMED
@@ -1,8 +1,11 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
 
2
  <div id="bookly-tinymce-popup" style="display: none">
3
  <form id="bookly-shortcode-form">
4
  <table>
5
- <?php \BooklyLite\Lib\Proxy\Shared::renderPopUpShortCodeBooklyFormHead() ?>
6
  <tr>
7
  <td>
8
  <label for="bookly-select-category"><?php _e( 'Default value for category select', 'bookly' ) ?></label>
@@ -37,7 +40,7 @@
37
  <div><label><input type="checkbox" id="bookly-hide-employee" /><?php _e( 'Hide this field', 'bookly' ) ?></label></div>
38
  </td>
39
  </tr>
40
- <?php \BooklyLite\Lib\Proxy\Shared::renderPopUpShortCodeBooklyForm() ?>
41
  <tr>
42
  <td>
43
  <label for="bookly-hide-date"><?php _e( 'Date', 'bookly' ) ?></label>
@@ -96,6 +99,7 @@
96
  $hide_categories = $('#bookly-hide-categories'),
97
  $hide_services = $('#bookly-hide-services'),
98
  $hide_staff = $('#bookly-hide-employee'),
 
99
  $hide_number_of_persons = $('#bookly-hide-number-of-persons'),
100
  $hide_quantity = $('#bookly-hide-quantity'),
101
  $hide_date = $('#bookly-hide-date'),
@@ -103,6 +107,7 @@
103
  $hide_time_range = $('#bookly-hide-time-range'),
104
  $add_button = $('#add-bookly-form'),
105
  $insert = $('#bookly-insert-shortcode'),
 
106
  locations = <?php echo json_encode( $casest['locations'] ) ?>,
107
  categories = <?php echo json_encode( $casest['categories'] ) ?>,
108
  services = <?php echo json_encode( $casest['services'] ) ?>,
@@ -118,7 +123,7 @@
118
  });
119
  },100);
120
  });
121
-
122
  function setSelect($select, data, value) {
123
  // reset select
124
  $('option:not([value=""])', $select).remove();
@@ -152,11 +157,12 @@
152
  }
153
 
154
  function setSelects(location_id, category_id, service_id, staff_id) {
155
- var _staff = {}, _services = {}, _categories = {}, _nop = {};
 
156
  $.each(staff, function(id, staff_member) {
157
- if (location_id == '' || locations[location_id].staff.hasOwnProperty(id)) {
158
- if (service_id == '') {
159
- if (category_id == '') {
160
  _staff[id] = staff_member;
161
  } else {
162
  $.each(staff_member.services, function(s_id) {
@@ -167,32 +173,45 @@
167
  });
168
  }
169
  } else if (staff_member.services.hasOwnProperty(service_id)) {
170
- if (staff_member.services[service_id].price != null) {
171
- _staff[id] = {
172
- id : id,
173
- name : staff_member.name + ' (' + staff_member.services[service_id].price + ')',
174
- pos : staff_member.pos
175
- };
176
- } else {
177
- _staff[id] = staff_member;
 
 
 
 
 
 
 
 
 
178
  }
179
  }
180
  }
181
  });
182
- if (location_id == '') {
183
  _categories = categories;
184
  $.each(services, function(id, service) {
185
- if (category_id == '' || service.category_id == category_id) {
186
- if (staff_id == '' || staff[staff_id].services.hasOwnProperty(id)) {
187
  _services[id] = service;
188
  }
189
  }
190
  });
191
  } else {
192
- var category_ids = [];
193
- $.each(locations[location_id].staff, function(st_id) {
194
- $.each(staff[st_id].services, function(s_id) {
195
- category_ids.push(services[s_id].category_id);
 
 
 
 
196
  });
197
  });
198
  $.each(categories, function(id, category) {
@@ -201,13 +220,16 @@
201
  }
202
  });
203
  $.each(services, function(id, service) {
204
- if ($.inArray(service.category_id, category_ids) > -1) {
205
- if (staff_id == '' || staff[staff_id].services.hasOwnProperty(id)) {
206
- _services[id] = service;
 
 
207
  }
208
  }
209
  });
210
  }
 
211
  setSelect($select_category, _categories, category_id);
212
  setSelect($select_service, _services, service_id);
213
  setSelect($select_employee, _staff, staff_id);
@@ -350,9 +372,15 @@
350
  if ($hide_services.is(':checked')) {
351
  hide.push('services');
352
  }
 
 
 
353
  if ($select_employee.val()) {
354
  insert += ' staff_member_id="' + $select_employee.val() + '"';
355
  }
 
 
 
356
  if ($hide_quantity.is(':checked')) {
357
  hide.push('quantity');
358
  }
@@ -382,6 +410,7 @@
382
  $hide_locations.prop('checked', false);
383
  $hide_categories.prop('checked', false);
384
  $hide_services.prop('checked', false);
 
385
  $hide_staff.prop('checked', false);
386
  $hide_date.prop('checked', false);
387
  $hide_week_days.prop('checked', false);
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\TinyMce\Proxy;
3
+ ?>
4
+
5
  <div id="bookly-tinymce-popup" style="display: none">
6
  <form id="bookly-shortcode-form">
7
  <table>
8
+ <?php Proxy\Shared::renderBooklyFormHead() ?>
9
  <tr>
10
  <td>
11
  <label for="bookly-select-category"><?php _e( 'Default value for category select', 'bookly' ) ?></label>
40
  <div><label><input type="checkbox" id="bookly-hide-employee" /><?php _e( 'Hide this field', 'bookly' ) ?></label></div>
41
  </td>
42
  </tr>
43
+ <?php Proxy\Shared::renderBooklyFormFields() ?>
44
  <tr>
45
  <td>
46
  <label for="bookly-hide-date"><?php _e( 'Date', 'bookly' ) ?></label>
99
  $hide_categories = $('#bookly-hide-categories'),
100
  $hide_services = $('#bookly-hide-services'),
101
  $hide_staff = $('#bookly-hide-employee'),
102
+ $hide_service_duration = $('#bookly-hide-service-duration'),
103
  $hide_number_of_persons = $('#bookly-hide-number-of-persons'),
104
  $hide_quantity = $('#bookly-hide-quantity'),
105
  $hide_date = $('#bookly-hide-date'),
107
  $hide_time_range = $('#bookly-hide-time-range'),
108
  $add_button = $('#add-bookly-form'),
109
  $insert = $('#bookly-insert-shortcode'),
110
+ location_custom = <?php echo (int) Bookly\Lib\Proxy\Locations::servicesPerLocationAllowed() ?>,
111
  locations = <?php echo json_encode( $casest['locations'] ) ?>,
112
  categories = <?php echo json_encode( $casest['categories'] ) ?>,
113
  services = <?php echo json_encode( $casest['services'] ) ?>,
123
  });
124
  },100);
125
  });
126
+ // insert data into select
127
  function setSelect($select, data, value) {
128
  // reset select
129
  $('option:not([value=""])', $select).remove();
157
  }
158
 
159
  function setSelects(location_id, category_id, service_id, staff_id) {
160
+ var _location_id = (location_custom && location_id) ? location_id : 0
161
+ var _staff = {}, _services = {}, _categories = {}, _nop = {}, _max_capacity = null, _min_capacity = null;
162
  $.each(staff, function(id, staff_member) {
163
+ if (!location_id || locations[location_id].staff.hasOwnProperty(id)) {
164
+ if (!service_id) {
165
+ if (!category_id) {
166
  _staff[id] = staff_member;
167
  } else {
168
  $.each(staff_member.services, function(s_id) {
173
  });
174
  }
175
  } else if (staff_member.services.hasOwnProperty(service_id)) {
176
+ // var _location_id = staff_member.services[service_id].locations.hasOwnProperty(location_id) ? location_id : 0;
177
+ if (staff_member.services[service_id].locations.hasOwnProperty(_location_id)) {
178
+ if ( staff_member.services[service_id].locations[_location_id].price != null) {
179
+ _min_capacity = _min_capacity ? Math.min(_min_capacity, staff_member.services[service_id].locations[_location_id].min_capacity) : staff_member.services[service_id].locations[_location_id].min_capacity;
180
+ _max_capacity = _max_capacity ? Math.max(_max_capacity, staff_member.services[service_id].locations[_location_id].max_capacity) : staff_member.services[service_id].locations[_location_id].max_capacity;
181
+ _staff[id] = {
182
+ id : id,
183
+ name : staff_member.name + ' (' + staff_member.services[service_id].locations[_location_id].price + ')',
184
+ pos : staff_member.pos
185
+ };
186
+ } else {
187
+ _staff[id] = {
188
+ id : id,
189
+ name : staff_member.name,
190
+ pos : staff_member.pos
191
+ };
192
+ }
193
  }
194
  }
195
  }
196
  });
197
+ if (!location_id) {
198
  _categories = categories;
199
  $.each(services, function(id, service) {
200
+ if (!category_id || service.category_id == category_id) {
201
+ if (!staff_id || staff[staff_id].services.hasOwnProperty(id)) {
202
  _services[id] = service;
203
  }
204
  }
205
  });
206
  } else {
207
+ var category_ids = [],
208
+ service_ids = [];
209
+ $.each(staff, function (st_id) {
210
+ $.each(staff[st_id].services, function (s_id) {
211
+ if (staff[st_id].services[s_id].locations.hasOwnProperty(_location_id)) {
212
+ category_ids.push(services[s_id].category_id);
213
+ service_ids.push(s_id);
214
+ }
215
  });
216
  });
217
  $.each(categories, function(id, category) {
220
  }
221
  });
222
  $.each(services, function(id, service) {
223
+ if ($.inArray(id, service_ids) > -1) {
224
+ if (!category_id || service.category_id == category_id) {
225
+ if (!staff_id || staff[staff_id].services.hasOwnProperty(id)) {
226
+ _services[id] = service;
227
+ }
228
  }
229
  }
230
  });
231
  }
232
+
233
  setSelect($select_category, _categories, category_id);
234
  setSelect($select_service, _services, service_id);
235
  setSelect($select_employee, _staff, staff_id);
372
  if ($hide_services.is(':checked')) {
373
  hide.push('services');
374
  }
375
+ if ($hide_service_duration.is(':checked')) {
376
+ hide.push('service_duration');
377
+ }
378
  if ($select_employee.val()) {
379
  insert += ' staff_member_id="' + $select_employee.val() + '"';
380
  }
381
+ if ($hide_number_of_persons.is(':not(:checked)')) {
382
+ insert += ' show_number_of_persons="1"';
383
+ }
384
  if ($hide_quantity.is(':checked')) {
385
  hide.push('quantity');
386
  }
410
  $hide_locations.prop('checked', false);
411
  $hide_categories.prop('checked', false);
412
  $hide_services.prop('checked', false);
413
+ $hide_service_duration.prop('checked', false);
414
  $hide_staff.prop('checked', false);
415
  $hide_date.prop('checked', false);
416
  $hide_week_days.prop('checked', false);
backend/modules/appearance/Ajax.php ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Backend\Modules\Appearance\Proxy;
6
+
7
+ /**
8
+ * Class Ajax
9
+ * @package Bookly\Backend\Modules\Appearance
10
+ */
11
+ class Ajax extends Lib\Base\Ajax
12
+ {
13
+ /**
14
+ * Update options.
15
+ */
16
+ public static function updateAppearanceOptions()
17
+ {
18
+ $options = self::parameter( 'options', array() );
19
+
20
+ // Make sure that we save only allowed options.
21
+ $options_to_save = array_intersect_key( $options, array_flip( array(
22
+ // Info text.
23
+ 'bookly_l10n_info_complete_step',
24
+ 'bookly_l10n_info_complete_step_limit_error',
25
+ 'bookly_l10n_info_complete_step_processing',
26
+ 'bookly_l10n_info_details_step',
27
+ 'bookly_l10n_info_details_step_guest',
28
+ 'bookly_l10n_info_payment_step_single_app',
29
+ 'bookly_l10n_info_payment_step_several_apps',
30
+ 'bookly_l10n_info_service_step',
31
+ 'bookly_l10n_info_time_step',
32
+ // Step, label and option texts.
33
+ 'bookly_l10n_button_apply',
34
+ 'bookly_l10n_button_back',
35
+ 'bookly_l10n_label_category',
36
+ 'bookly_l10n_label_ccard_code',
37
+ 'bookly_l10n_label_ccard_expire',
38
+ 'bookly_l10n_label_ccard_number',
39
+ 'bookly_l10n_label_email',
40
+ 'bookly_l10n_label_employee',
41
+ 'bookly_l10n_label_service_duration',
42
+ 'bookly_l10n_label_finish_by',
43
+ 'bookly_l10n_label_name',
44
+ 'bookly_l10n_label_first_name',
45
+ 'bookly_l10n_label_last_name',
46
+ 'bookly_l10n_label_notes',
47
+ 'bookly_l10n_label_number_of_persons',
48
+ 'bookly_l10n_label_pay_ccard',
49
+ 'bookly_l10n_label_pay_locally',
50
+ 'bookly_l10n_label_phone',
51
+ 'bookly_l10n_label_select_date',
52
+ 'bookly_l10n_label_service',
53
+ 'bookly_l10n_label_start_from',
54
+ 'bookly_l10n_option_category',
55
+ 'bookly_l10n_option_employee',
56
+ 'bookly_l10n_option_service',
57
+ 'bookly_l10n_option_day',
58
+ 'bookly_l10n_option_month',
59
+ 'bookly_l10n_option_year',
60
+ 'bookly_l10n_step_service',
61
+ 'bookly_l10n_step_service_mobile_button_next',
62
+ 'bookly_l10n_step_service_button_next',
63
+ 'bookly_l10n_step_time',
64
+ 'bookly_l10n_step_time_slot_not_available',
65
+ 'bookly_l10n_step_details',
66
+ 'bookly_l10n_step_details_button_next',
67
+ 'bookly_l10n_step_details_button_login',
68
+ 'bookly_l10n_step_payment',
69
+ 'bookly_l10n_step_payment_button_next',
70
+ 'bookly_l10n_step_done',
71
+ // Validator errors.
72
+ 'bookly_l10n_required_email',
73
+ 'bookly_l10n_required_employee',
74
+ 'bookly_l10n_required_name',
75
+ 'bookly_l10n_required_first_name',
76
+ 'bookly_l10n_required_last_name',
77
+ 'bookly_l10n_required_phone',
78
+ 'bookly_l10n_required_service',
79
+ // Color.
80
+ 'bookly_app_color',
81
+ // Checkboxes.
82
+ 'bookly_app_required_employee',
83
+ 'bookly_app_service_name_with_duration',
84
+ 'bookly_app_show_blocked_timeslots',
85
+ 'bookly_app_show_calendar',
86
+ 'bookly_app_show_day_one_column',
87
+ 'bookly_app_show_time_zone_switcher',
88
+ 'bookly_app_show_login_button',
89
+ 'bookly_app_show_facebook_login_button',
90
+ 'bookly_app_show_notes',
91
+ 'bookly_app_show_progress_tracker',
92
+ 'bookly_app_staff_name_with_price',
93
+ 'bookly_cst_required_details',
94
+ 'bookly_app_service_duration_with_price',
95
+ 'bookly_cst_first_last_name',
96
+ // Options.
97
+ 'bookly_multiply_appointments_quantity_max',
98
+ ) ) );
99
+
100
+ // Allow add-ons to add their options.
101
+ $options_to_save = Proxy\Shared::prepareOptions( $options_to_save, $options );
102
+
103
+ // Save options.
104
+ foreach ( $options_to_save as $option_name => $option_value ) {
105
+ update_option( $option_name, $option_value );
106
+ // Register string for translate in WPML.
107
+ if ( strpos( $option_name, 'bookly_l10n_' ) === 0 ) {
108
+ do_action( 'wpml_register_single_string', 'bookly', $option_name, $option_value );
109
+ }
110
+ }
111
+
112
+ wp_send_json_success();
113
+ }
114
+
115
+ /**
116
+ * Ajax request to dismiss appearance notice for current user.
117
+ */
118
+ public static function dismissAppearanceNotice()
119
+ {
120
+ update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_appearance_notice', 1 );
121
+ }
122
+
123
+ /**
124
+ * Process ajax request to save custom css
125
+ */
126
+ public static function saveCustomCss()
127
+ {
128
+ update_option( 'bookly_app_custom_styles', self::parameter( 'custom_css' ) );
129
+
130
+ wp_send_json_success( array( 'message' => __( 'Your custom CSS was saved. Please refresh the page to see your changes.', 'bookly') ) );
131
+ }
132
+ }
backend/modules/appearance/Components.php DELETED
@@ -1,21 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Appearance;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Components
8
- * @package BooklyLite\Backend\Modules\Appearance
9
- */
10
- class Components extends Lib\Base\Components
11
- {
12
- /**
13
- * @param array $variables
14
- * @param bool $echo
15
- * @return string|void
16
- */
17
- public function renderCodes( $variables = array(), $echo = true )
18
- {
19
- return $this->render( '_codes', $variables, $echo );
20
- }
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appearance/Controller.php DELETED
@@ -1,218 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Appearance;
3
-
4
- use BooklyLite\Lib;
5
- use BooklyLite\Backend\Modules\Appearance\Lib\Helper;
6
-
7
- /**
8
- * Class Controller
9
- * @package BooklyLite\Backend\Modules\Appearance
10
- */
11
- class Controller extends Lib\Base\Controller
12
- {
13
- const page_slug = 'bookly-appearance';
14
-
15
- /**
16
- * Default Action
17
- */
18
- public function index()
19
- {
20
- /** @var \WP_Locale $wp_locale */
21
- global $wp_locale;
22
-
23
- $this->enqueueStyles( array(
24
- 'frontend' => array_merge(
25
- ( get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
26
- ? array()
27
- : array( 'css/intlTelInput.css' ) ),
28
- array(
29
- 'css/ladda.min.css',
30
- 'css/picker.classic.css',
31
- 'css/picker.classic.date.css',
32
- 'css/bookly-main.css',
33
- )
34
- ),
35
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
36
- 'wp' => array( 'wp-color-picker', ),
37
- 'module' => array( 'css/bootstrap-editable.css', )
38
- ) );
39
-
40
- $this->enqueueScripts( array(
41
- 'backend' => array(
42
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
43
- 'js/alert.js' => array( 'jquery' ),
44
- ),
45
- 'frontend' => array_merge(
46
- array(
47
- 'js/picker.js' => array( 'jquery' ),
48
- 'js/picker.date.js' => array( 'jquery' ),
49
- 'js/spin.min.js' => array( 'jquery' ),
50
- 'js/ladda.min.js' => array( 'jquery' ),
51
- ),
52
- get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
53
- ? array()
54
- : array( 'js/intlTelInput.min.js' => array( 'jquery' ) )
55
- ),
56
- 'wp' => array( 'wp-color-picker' ),
57
- 'module' => array(
58
- 'js/bootstrap-editable.min.js' => array( 'bookly-bootstrap.min.js' ),
59
- 'js/bootstrap-editable.bookly.js' => array( 'bookly-bootstrap-editable.min.js' ),
60
- 'js/appearance.js' => array( 'bookly-bootstrap-editable.bookly.js' )
61
- )
62
- ) );
63
-
64
- wp_localize_script( 'bookly-picker.date.js', 'BooklyL10n', array(
65
- 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
66
- 'today' => __( 'Today', 'bookly' ),
67
- 'months' => array_values( $wp_locale->month ),
68
- 'days' => array_values( $wp_locale->weekday_abbrev ),
69
- 'nextMonth' => __( 'Next month', 'bookly' ),
70
- 'prevMonth' => __( 'Previous month', 'bookly' ),
71
- 'date_format' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_PICKADATE ),
72
- 'start_of_week' => (int) get_option( 'start_of_week' ),
73
- 'saved' => __( 'Settings saved.', 'bookly' ),
74
- 'intlTelInput' => array(
75
- 'enabled' => get_option( 'bookly_cst_phone_default_country' ) != 'disabled',
76
- 'utils' => plugins_url( 'intlTelInput.utils.js', Lib\Plugin::getDirectory() . '/frontend/resources/js/intlTelInput.utils.js' ),
77
- 'country' => get_option( 'bookly_cst_phone_default_country' ),
78
- )
79
- ) );
80
-
81
- // Initialize steps (tabs).
82
- $steps = array(
83
- 1 => get_option( 'bookly_l10n_step_service' ),
84
- get_option( 'bookly_l10n_step_extras' ),
85
- get_option( 'bookly_l10n_step_time' ),
86
- get_option( 'bookly_l10n_step_repeat' ),
87
- get_option( 'bookly_l10n_step_cart' ),
88
- get_option( 'bookly_l10n_step_details' ),
89
- get_option( 'bookly_l10n_step_payment' ),
90
- get_option( 'bookly_l10n_step_done' )
91
- );
92
-
93
- $custom_css = get_option( 'bookly_app_custom_styles' );
94
-
95
- // Shortcut to helper class.
96
- $editable = new Helper();
97
-
98
- // Render general layout.
99
- $this->render( 'index', compact( 'steps', 'custom_css', 'editable' ) );
100
- }
101
-
102
- /**
103
- * Update options
104
- */
105
- public function executeUpdateAppearanceOptions()
106
- {
107
- $options = $this->getParameter( 'options', array() );
108
-
109
- // Make sure that we save only allowed options.
110
- $options_to_save = array_intersect_key( $options, array_flip( array(
111
- // Info text.
112
- 'bookly_l10n_info_cart_step',
113
- 'bookly_l10n_info_complete_step',
114
- 'bookly_l10n_info_complete_step_limit_error',
115
- 'bookly_l10n_info_complete_step_processing',
116
- 'bookly_l10n_info_details_step',
117
- 'bookly_l10n_info_details_step_guest',
118
- 'bookly_l10n_info_payment_step_single_app',
119
- 'bookly_l10n_info_payment_step_several_apps',
120
- 'bookly_l10n_info_service_step',
121
- 'bookly_l10n_info_time_step',
122
- // Step, label and option texts.
123
- 'bookly_l10n_button_apply',
124
- 'bookly_l10n_button_back',
125
- 'bookly_l10n_button_book_more',
126
- 'bookly_l10n_label_category',
127
- 'bookly_l10n_label_ccard_code',
128
- 'bookly_l10n_label_ccard_expire',
129
- 'bookly_l10n_label_ccard_number',
130
- 'bookly_l10n_label_email',
131
- 'bookly_l10n_label_employee',
132
- 'bookly_l10n_label_finish_by',
133
- 'bookly_l10n_label_name',
134
- 'bookly_l10n_label_first_name',
135
- 'bookly_l10n_label_last_name',
136
- 'bookly_l10n_label_notes',
137
- 'bookly_l10n_label_number_of_persons',
138
- 'bookly_l10n_label_pay_ccard',
139
- 'bookly_l10n_label_pay_locally',
140
- 'bookly_l10n_label_pay_paypal',
141
- 'bookly_l10n_label_phone',
142
- 'bookly_l10n_label_select_date',
143
- 'bookly_l10n_label_service',
144
- 'bookly_l10n_label_start_from',
145
- 'bookly_l10n_option_category',
146
- 'bookly_l10n_option_employee',
147
- 'bookly_l10n_option_service',
148
- 'bookly_l10n_step_service',
149
- 'bookly_l10n_step_service_mobile_button_next',
150
- 'bookly_l10n_step_service_button_next',
151
- 'bookly_l10n_step_time',
152
- 'bookly_l10n_step_time_slot_not_available',
153
- 'bookly_l10n_step_cart',
154
- 'bookly_l10n_step_cart_slot_not_available',
155
- 'bookly_l10n_step_cart_button_next',
156
- 'bookly_l10n_step_details',
157
- 'bookly_l10n_step_details_button_next',
158
- 'bookly_l10n_step_details_button_login',
159
- 'bookly_l10n_step_payment',
160
- 'bookly_l10n_step_payment_button_next',
161
- 'bookly_l10n_step_done',
162
- // Validator errors.
163
- 'bookly_l10n_required_email',
164
- 'bookly_l10n_required_employee',
165
- 'bookly_l10n_required_name',
166
- 'bookly_l10n_required_first_name',
167
- 'bookly_l10n_required_last_name',
168
- 'bookly_l10n_required_phone',
169
- 'bookly_l10n_required_service',
170
- // Color.
171
- 'bookly_app_color',
172
- // Checkboxes.
173
- 'bookly_app_required_employee',
174
- 'bookly_app_service_name_with_duration',
175
- 'bookly_app_show_blocked_timeslots',
176
- 'bookly_app_show_calendar',
177
- 'bookly_app_show_day_one_column',
178
- 'bookly_app_show_login_button',
179
- 'bookly_app_show_notes',
180
- 'bookly_app_show_progress_tracker',
181
- 'bookly_app_staff_name_with_price',
182
- 'bookly_cst_required_phone',
183
- 'bookly_cst_first_last_name',
184
- ) ) );
185
-
186
- // Allow add-ons to add their options.
187
- $options_to_save = Lib\Proxy\Shared::prepareAppearanceOptions( $options_to_save, $options );
188
-
189
- // Save options.
190
- foreach ( $options_to_save as $option_name => $option_value ) {
191
- update_option( $option_name, $option_value );
192
- // Register string for translate in WPML.
193
- if ( strpos( $option_name, 'bookly_l10n_' ) === 0 ) {
194
- do_action( 'wpml_register_single_string', 'bookly', $option_name, $option_value );
195
- }
196
- }
197
-
198
- wp_send_json_success();
199
- }
200
-
201
- /**
202
- * Ajax request to dismiss appearance notice for current user.
203
- */
204
- public function executeDismissAppearanceNotice()
205
- {
206
- update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_appearance_notice', 1 );
207
- }
208
-
209
- /**
210
- * Process ajax request to save custom css
211
- */
212
- public function executeSaveCustomCss()
213
- {
214
- update_option( 'bookly_app_custom_styles', $this->getParameter( 'custom_css' ) );
215
-
216
- wp_send_json_success( array( 'message' => __( 'Your custom CSS was saved. Please refresh the page to see your changes.', 'bookly') ) );
217
- }
218
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appearance/Page.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Page
8
+ * @package Bookly\Backend\Modules\Appearance
9
+ */
10
+ class Page extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render page.
14
+ */
15
+ public static function render()
16
+ {
17
+ /** @var \WP_Locale $wp_locale */
18
+ global $wp_locale;
19
+
20
+ self::enqueueStyles( array(
21
+ 'frontend' => array_merge(
22
+ ( get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
23
+ ? array()
24
+ : array( 'css/intlTelInput.css' ) ),
25
+ array(
26
+ 'css/ladda.min.css',
27
+ 'css/picker.classic.css',
28
+ 'css/picker.classic.date.css',
29
+ 'css/bookly-main.css',
30
+ )
31
+ ),
32
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
33
+ 'wp' => array( 'wp-color-picker', ),
34
+ 'module' => array( 'css/bootstrap-editable.css', )
35
+ ) );
36
+
37
+ self::enqueueScripts( array(
38
+ 'backend' => array(
39
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
40
+ 'js/alert.js' => array( 'jquery' ),
41
+ ),
42
+ 'frontend' => array_merge(
43
+ array(
44
+ 'js/picker.js' => array( 'jquery' ),
45
+ 'js/picker.date.js' => array( 'jquery' ),
46
+ 'js/spin.min.js' => array( 'jquery' ),
47
+ 'js/ladda.min.js' => array( 'jquery' ),
48
+ ),
49
+ get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
50
+ ? array()
51
+ : array( 'js/intlTelInput.min.js' => array( 'jquery' ) )
52
+ ),
53
+ 'wp' => array( 'wp-color-picker' ),
54
+ 'module' => array(
55
+ 'js/bootstrap-editable.min.js' => array( 'bookly-bootstrap.min.js' ),
56
+ 'js/bootstrap-editable.bookly.js' => array( 'bookly-bootstrap-editable.min.js' ),
57
+ 'js/appearance.js' => array( 'bookly-bootstrap-editable.bookly.js' )
58
+ )
59
+ ) );
60
+
61
+ wp_localize_script( 'bookly-picker.date.js', 'BooklyL10n', array(
62
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
63
+ 'nop_format' => get_option( 'bookly_group_booking_nop_format' ),
64
+ 'today' => __( 'Today', 'bookly' ),
65
+ 'months' => array_values( $wp_locale->month ),
66
+ 'days' => array_values( $wp_locale->weekday_abbrev ),
67
+ 'nextMonth' => __( 'Next month', 'bookly' ),
68
+ 'prevMonth' => __( 'Previous month', 'bookly' ),
69
+ 'date_format' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_PICKADATE ),
70
+ 'start_of_week' => (int) get_option( 'start_of_week' ),
71
+ 'saved' => __( 'Settings saved.', 'bookly' ),
72
+ 'intlTelInput' => array(
73
+ 'enabled' => get_option( 'bookly_cst_phone_default_country' ) != 'disabled',
74
+ 'utils' => is_rtl() ? '' : plugins_url( 'intlTelInput.utils.js', Lib\Plugin::getDirectory() . '/frontend/resources/js/intlTelInput.utils.js' ),
75
+ 'country' => get_option( 'bookly_cst_phone_default_country' ),
76
+ ),
77
+ ) );
78
+
79
+ // Initialize steps (tabs).
80
+ $steps = array(
81
+ 1 => array( 'show' => true, 'title' => get_option( 'bookly_l10n_step_service' ) ),
82
+ 3 => array( 'show' => true, 'title' => get_option( 'bookly_l10n_step_time' ) ),
83
+ 6 => array( 'show' => true, 'title' => get_option( 'bookly_l10n_step_details' ) ),
84
+ 7 => array( 'show' => true, 'title' => get_option( 'bookly_l10n_step_payment' ) ),
85
+ 8 => array( 'show' => true, 'title' => get_option( 'bookly_l10n_step_done' ) ),
86
+ );
87
+ if ( Lib\Config::serviceExtrasActive() ) {
88
+ $steps[2] = array( 'show' => get_option( 'bookly_service_extras_enabled' ), 'title' => get_option( 'bookly_l10n_step_extras' ) );
89
+ }
90
+ if ( Lib\Config::recurringAppointmentsActive() ) {
91
+ $steps[4] = array( 'show' => get_option( 'bookly_recurring_appointments_enabled' ), 'title' => get_option( 'bookly_l10n_step_repeat' ) );
92
+ }
93
+ if ( Lib\Config::cartActive() ) {
94
+ $steps[5] = array( 'show' => get_option( 'bookly_cart_enabled' ), 'title' => get_option( 'bookly_l10n_step_cart' ) );
95
+ }
96
+ ksort( $steps );
97
+
98
+ $custom_css = get_option( 'bookly_app_custom_styles' );
99
+
100
+ // Render general layout.
101
+ self::renderTemplate( 'index', compact( 'steps', 'custom_css' ) );
102
+ }
103
+ }
backend/modules/appearance/lib/Helper.php DELETED
@@ -1,69 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Appearance\Lib;
3
-
4
- class Helper
5
- {
6
- /**
7
- * Render editable string (single line).
8
- *
9
- * @param array $options
10
- */
11
- public static function renderString(array $options )
12
- {
13
- self::_renderEditable( $options, 'span' );
14
- }
15
-
16
- /**
17
- * Render editable label.
18
- *
19
- * @param array $options
20
- */
21
- public static function renderLabel( array $options )
22
- {
23
- self::_renderEditable( $options, 'label' );
24
- }
25
-
26
- /**
27
- * Render editable text (multi-line).
28
- *
29
- * @param string $option_name
30
- * @param string $codes
31
- * @param string $placement
32
- * @param string $title
33
- */
34
- public static function renderText( $option_name, $codes = '', $placement = 'bottom', $title = '' )
35
- {
36
- $option_value = get_option( $option_name );
37
-
38
- printf( '<span class="bookly-js-editable bookly-js-option %s editable-pre-wrapped" data-type="bookly" data-fieldType="textarea" data-values="%s" data-codes="%s" data-title="%s" data-placement="%s">%s</span>',
39
- $option_name,
40
- esc_attr( json_encode( array( $option_name => $option_value ) ) ),
41
- esc_attr( $codes ),
42
- esc_attr( $title ),
43
- $placement,
44
- esc_html( $option_value )
45
- );
46
- }
47
-
48
- /**
49
- * Render editable element.
50
- *
51
- * @param array $options
52
- * @param string $tag
53
- */
54
- private static function _renderEditable( array $options, $tag )
55
- {
56
- $data = array();
57
- foreach ( $options as $option_name ) {
58
- $data[ $option_name ] = get_option( $option_name );
59
- }
60
-
61
- printf( '<%s class="bookly-js-editable bookly-js-option %s" data-type="bookly" data-values="%s">%s</%s>',
62
- $tag,
63
- $options[0],
64
- esc_attr( json_encode( $data ) ),
65
- esc_html( $data[ $options[0] ] ),
66
- $tag
67
- );
68
- }
69
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appearance/proxy/Cart.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Cart
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static void renderCartStepSettings() Render settings on Cart step.
11
+ * @method static void renderShowStep() Render "Show Cart step".
12
+ * @method static void renderStep( string $progress_tracker ) Render Cart step.
13
+ */
14
+ abstract class Cart extends Lib\Base\Proxy
15
+ {
16
+
17
+ }
backend/modules/appearance/proxy/ChainAppointments.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class ChainAppointments
8
+ * @package Bookly\Backend\Modules\Frontend\Proxy
9
+ *
10
+ * @method static void renderChain() render a 'plus' for chain appointments on service step
11
+ */
12
+ abstract class ChainAppointments extends Lib\Base\Proxy
13
+ {
14
+ }
backend/modules/appearance/proxy/Coupons.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Coupons
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static void renderCouponBlock() Render coupon block in Payments step.
11
+ * @method static void renderShowCoupons() Render coupon block in Payments step.
12
+ */
13
+ abstract class Coupons extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/modules/appearance/proxy/CustomDuration.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class GoogleMapsAddress
8
+ * @package Bookly\Backend\Modules\Frontend\Proxy
9
+ *
10
+ * @method static void renderServiceDuration() render a select with service durations
11
+ * @method static void renderShowCustomDuration() Render "Show custom duration" checkbox on Service step settings.
12
+ */
13
+ abstract class CustomDuration extends Lib\Base\Proxy
14
+ {
15
+ }
backend/modules/appearance/proxy/CustomFields.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CustomFields
8
+ * @package Bookly\Backend\Modules\Frontend\Proxy
9
+ *
10
+ * @method static void renderCustomFields() render "custom fields" input
11
+ * @method static void renderShowCustomFields() render a checkbox "Show custom fields"
12
+ */
13
+ abstract class CustomFields extends Lib\Base\Proxy
14
+ {
15
+ }
backend/modules/appearance/proxy/CustomerInformation.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CustomerInformation
8
+ * @package Bookly\Backend\Modules\Frontend\Proxy
9
+ *
10
+ * @method static void renderCustomerInformation() render "customer information" input
11
+ * @method static void renderShowCustomerInformation() render a checkbox "Show customer information"
12
+ */
13
+ abstract class CustomerInformation extends Lib\Base\Proxy
14
+ {
15
+ }
backend/modules/appearance/proxy/DepositPayments.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class DepositPayments
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static void renderAppearance() Render editable selector for deposit/full payment
11
+ */
12
+ abstract class DepositPayments extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/appearance/proxy/Files.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Files
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static void renderAppearance() Render button browse in Appearance
11
+ * @method static void renderShowFiles() Render 'Show files' on details step
12
+ */
13
+ abstract class Files extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/modules/appearance/proxy/GoogleMapsAddress.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class GoogleMapsAddress
8
+ * @package Bookly\Backend\Modules\Frontend\Proxy
9
+ *
10
+ * @method static void renderGoogleMaps() Render google maps input.
11
+ * @method static void renderShowGoogleMaps() Render show google maps fields checkbox.
12
+ */
13
+ abstract class GoogleMapsAddress extends Lib\Base\Proxy
14
+ {
15
+ }
backend/modules/appearance/proxy/GroupBooking.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class GroupBooking
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static void renderNOP() Render number of persons select in Service step.
11
+ * @method static void renderShowNOP() Render "Show number of persons" checkbox on Service step settings.
12
+ */
13
+ abstract class GroupBooking extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/modules/appearance/proxy/Locations.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Locations
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static void renderLocation() Render location select in Service step.
11
+ * @method static void renderShowLocation() Render "Show location" checkbox on Service step settings.
12
+ */
13
+ abstract class Locations extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/modules/appearance/proxy/MultiplyAppointments.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class MultiplyAppointments
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static void renderQuantity() Render Multiply (quantity) control in Service step.
11
+ * @method static void renderShowQuantity() Render "Show quantity" checkbox on Service step settings.
12
+ */
13
+ abstract class MultiplyAppointments extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/modules/appearance/proxy/Pro.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static void renderFacebookButton() Render facebook login button on Time step.
11
+ * @method static void renderMultipleBookingSelector() Render single/multiple booking selector on Payment step.
12
+ * @method static void renderMultipleBookingText() Render multiple booking text option on Payment step.
13
+ * @method static void renderPayPalPaymentOption() Render Cart step.
14
+ * @method static void renderShowFacebookButton() Render 'Show facebook login button switcher' on Time step.
15
+ * @method static void renderTimeZoneSwitcher() Render timezone switcher on Time step.
16
+ * @method static void renderTimeZoneSwitcherCheckbox() Render 'Show time zone switcher' on Time step.
17
+ * @method static void renderShowAddress() render 'Show Address Fields' on Details Step.
18
+ * @method static void renderShowBirthday() render 'Show Birthday Fields' on Details Step.
19
+ */
20
+ abstract class Pro extends Lib\Base\Proxy
21
+ {
22
+
23
+ }
backend/modules/appearance/proxy/RecurringAppointments.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class RecurringAppointments
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static void renderInfoMessage() Render editable info message in appearance.
11
+ * @method static void renderShowStep() Render "Show Repeat step".
12
+ * @method static void renderStep( string $progress_tracker ) Render Repeat step.
13
+ *
14
+ */
15
+ abstract class RecurringAppointments extends Lib\Base\Proxy
16
+ {
17
+
18
+ }
backend/modules/appearance/proxy/ServiceExtras.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class ServiceExtras
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static void renderCartExtras() Render extras on Cart step.
11
+ * @method static void renderShowCartExtras() Render "Show extras" on Cart step.
12
+ * @method static void renderShowStep() Render "Show Extras step".
13
+ * @method static void renderStep( string $progress_tracker ) Render Extras step.
14
+ */
15
+ abstract class ServiceExtras extends Lib\Base\Proxy
16
+ {
17
+
18
+ }
backend/modules/appearance/proxy/Shared.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Shared
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static array prepareOptions( array $options_to_save, array $options ) Alter array of options to be saved in Bookly Appearance.
11
+ * @method static void renderPaymentGatewaySelector() Render gateway selector.
12
+ * @method static int renderServiceStepSettings() Render checkbox settings.
13
+ * @method static int renderTimeStepSettings() Render checkbox settings.
14
+ * @method static bool showCreditCard() In case there are payment systems that request credit card information in the Details step, it will return true.
15
+ */
16
+ abstract class Shared extends Lib\Base\Proxy
17
+ {
18
+
19
+ }
backend/modules/appearance/proxy/WaitingList.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class WaitingList
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static void renderInfoText() Render WL info text in Time step.
11
+ */
12
+ abstract class WaitingList extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/appearance/resources/js/appearance.js CHANGED
@@ -4,19 +4,33 @@ jQuery(function($) {
4
  $editableElements = $('.bookly-js-editable'),
5
  $show_progress_tracker = $('#bookly-show-progress-tracker'),
6
  $step_settings = $('#bookly-step-settings'),
 
 
 
7
  // Service step.
8
  $staff_name_with_price = $('#bookly-staff-name-with-price'),
 
9
  $service_name_with_duration = $('#bookly-service-name-with-duration'),
10
  $required_employee = $('#bookly-required-employee'),
11
  $required_location = $('#bookly-required-location'),
 
 
 
 
 
 
12
  // Time step.
 
13
  $time_step_calendar = $('.bookly-js-selected-date'),
14
  $time_step_calendar_wrap = $('.bookly-js-slot-calendar'),
15
  $show_blocked_timeslots = $('#bookly-show-blocked-timeslots'),
 
16
  $show_day_one_column = $('#bookly-show-day-one-column'),
 
17
  $show_calendar = $('#bookly-show-calendar'),
18
  $day_one_column = $('#bookly-day-one-column'),
19
  $day_multi_columns = $('#bookly-day-multi-columns'),
 
20
  // Step repeat.
21
  $repeat_step_calendar = $('.bookly-js-repeat-until'),
22
  $repeat_variants = $('[class^="bookly-js-variant"]'),
@@ -25,15 +39,27 @@ jQuery(function($) {
25
  $repeat_weekly_week_day = $('.bookly-js-week-day'),
26
  $repeat_monthly_specific_day = $('.bookly-js-monthly-specific-day'),
27
  $repeat_monthly_week_day = $('.bookly-js-monthly-week-day'),
 
 
28
  // Step details.
29
- $required_phone = $('#bookly-cst-required-phone'),
30
  $show_login_button = $('#bookly-show-login-button'),
 
31
  $first_last_name = $('#bookly-cst-first-last-name'),
32
  $show_notes_field = $('#bookly-show-notes'),
 
 
 
 
 
 
 
 
33
  // Buttons.
34
  $save_button = $('#ajax-send-appearance'),
35
  $reset_button = $('button[type=reset]'),
36
- $checkboxes = $('#bookly-appearance').find('input[type="checkbox"]')
 
37
  ;
38
 
39
  $checkboxes.each(function () {
@@ -115,12 +141,41 @@ jQuery(function($) {
115
  $('.bookly-progress-tracker').toggle(this.checked);
116
  }).trigger('change');
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  // Show step specific settings.
119
  $('li.bookly-nav-item').on('shown.bs.tab', function (e) {
120
  $step_settings.children().hide();
121
  switch (e.target.getAttribute('data-target')) {
122
  case '#bookly-step-1': $step_settings.find('.bookly-js-service-settings').show(); break;
123
  case '#bookly-step-3': $step_settings.find('.bookly-js-time-settings').show(); break;
 
124
  case '#bookly-step-6': $step_settings.find('.bookly-js-details-settings').show(); break;
125
  case '#bookly-step-7': $step_settings.find('.bookly-js-payment-settings').show(); break;
126
  case '#bookly-step-8': $step_settings.find('.bookly-js-done-settings').show(); break;
@@ -165,6 +220,95 @@ jQuery(function($) {
165
  $('.employee-name').toggle(!$staff_name_with_price.prop("checked"));
166
  }).trigger('change');
167
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  // Show duration next to service name.
169
  $service_name_with_duration.on('change', function () {
170
  var service = $('.bookly-js-select-service').val();
@@ -175,6 +319,19 @@ jQuery(function($) {
175
  $('.service-name').toggle(!$service_name_with_duration.prop("checked"));
176
  }).trigger('change');
177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  // Clickable week-days.
179
  $repeat_weekly_week_day.on('change', function () {
180
  $(this).parent().toggleClass('active', this.checked);
@@ -226,11 +383,22 @@ jQuery(function($) {
226
  }).trigger('change');
227
 
228
  // Show blocked time slots.
229
- $show_blocked_timeslots.on('change', function(){
230
  if (this.checked) {
231
  $('.bookly-hour.no-booked').removeClass('no-booked').addClass('booked');
 
232
  } else {
233
  $('.bookly-hour.booked').removeClass('booked').addClass('no-booked');
 
 
 
 
 
 
 
 
 
 
234
  }
235
  });
236
 
@@ -245,6 +413,46 @@ jQuery(function($) {
245
  }
246
  });
247
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  /**
249
  * Step repeat.
250
  */
@@ -284,6 +492,20 @@ jQuery(function($) {
284
  });
285
 
286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  /**
288
  * Step Details
289
  */
@@ -303,11 +525,25 @@ jQuery(function($) {
303
  });
304
  }
305
 
306
- // Show login form.
307
  $show_login_button.change(function () {
308
- $('#bookly-js-show-login-form').toggle(this.checked);
309
  }).trigger('change');
310
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  // Show first and last name.
312
  $first_last_name.on('change', function () {
313
  $first_last_name.popover('toggle');
@@ -325,6 +561,47 @@ jQuery(function($) {
325
  $('#bookly-js-notes').toggle(this.checked);
326
  }).trigger('change');
327
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  /**
329
  * Step Payment.
330
  */
@@ -340,6 +617,10 @@ jQuery(function($) {
340
  $('form.bookly-card-form').toggle(this.id == 'bookly-card-payment');
341
  });
342
 
 
 
 
 
343
  /**
344
  * Step Done.
345
  */
@@ -355,6 +636,7 @@ jQuery(function($) {
355
  /**
356
  * Misc.
357
  */
 
358
 
359
  // Custom CSS.
360
  $('#bookly-custom-css-save').on('click', function (e) {
@@ -415,13 +697,13 @@ jQuery(function($) {
415
  });
416
 
417
  // Save options.
418
- $save_button.on('click', function(e) {
419
  e.preventDefault();
420
  // Prepare data.
421
  var data = {
422
- action: 'bookly_update_appearance_options',
423
  csrf_token: BooklyL10n.csrf_token,
424
- options: {
425
  // Color.
426
  'bookly_app_color' : $color_picker.wpColorPicker('color'),
427
  // Checkboxes.
@@ -429,14 +711,36 @@ jQuery(function($) {
429
  'bookly_app_show_blocked_timeslots' : Number($show_blocked_timeslots.prop('checked')),
430
  'bookly_app_show_calendar' : Number($show_calendar.prop('checked')),
431
  'bookly_app_show_day_one_column' : Number($show_day_one_column.prop('checked')),
 
432
  'bookly_app_show_login_button' : Number($show_login_button.prop('checked')),
 
433
  'bookly_app_show_notes' : Number($show_notes_field.prop('checked')),
 
 
434
  'bookly_app_show_progress_tracker' : Number($show_progress_tracker.prop('checked')),
435
  'bookly_app_staff_name_with_price' : Number($staff_name_with_price.prop('checked')),
 
436
  'bookly_app_required_employee' : Number($required_employee.prop('checked')),
437
  'bookly_app_required_location' : Number($required_location.prop('checked')),
438
- 'bookly_cst_required_phone' : Number($required_phone.prop('checked')),
439
- 'bookly_cst_first_last_name' : Number($first_last_name.prop('checked'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
  }
441
  };
442
  // Add data from editable elements.
@@ -468,6 +772,11 @@ jQuery(function($) {
468
  $(this).prop('checked', $(this).data('default')).trigger('change');
469
  }
470
  });
 
 
 
 
 
471
  $first_last_name.popover('hide');
472
  });
473
  });
4
  $editableElements = $('.bookly-js-editable'),
5
  $show_progress_tracker = $('#bookly-show-progress-tracker'),
6
  $step_settings = $('#bookly-step-settings'),
7
+ $bookly_show_step_extras = $('#bookly-show-step-extras'),
8
+ $bookly_show_step_repeat = $('#bookly-show-step-repeat'),
9
+ $bookly_show_step_cart = $('#bookly-show-step-cart'),
10
  // Service step.
11
  $staff_name_with_price = $('#bookly-staff-name-with-price'),
12
+ $service_duration_with_price = $('#bookly-service-duration-with-price'),
13
  $service_name_with_duration = $('#bookly-service-name-with-duration'),
14
  $required_employee = $('#bookly-required-employee'),
15
  $required_location = $('#bookly-required-location'),
16
+ $show_ratings = $('#bookly-show-ratings'),
17
+ $show_chain_appointments = $('#bookly-show-chain-appointments'),
18
+ $show_location = $('#bookly-show-location'),
19
+ $show_custom_duration = $('#bookly-show-custom-duration'),
20
+ $show_nop = $('#bookly-show-nop'),
21
+ $show_quantity = $('#bookly-show-quantity'),
22
  // Time step.
23
+ $time_step_nop = $('#bookly-show-nop-on-time-step'),
24
  $time_step_calendar = $('.bookly-js-selected-date'),
25
  $time_step_calendar_wrap = $('.bookly-js-slot-calendar'),
26
  $show_blocked_timeslots = $('#bookly-show-blocked-timeslots'),
27
+ $show_waiting_list = $('#bookly-show-waiting-list'),
28
  $show_day_one_column = $('#bookly-show-day-one-column'),
29
+ $show_time_zone_switcher = $('#bookly-show-time-zone-switcher'),
30
  $show_calendar = $('#bookly-show-calendar'),
31
  $day_one_column = $('#bookly-day-one-column'),
32
  $day_multi_columns = $('#bookly-day-multi-columns'),
33
+ $columnizer = $('.bookly-time-step .bookly-columnizer-wrap'),
34
  // Step repeat.
35
  $repeat_step_calendar = $('.bookly-js-repeat-until'),
36
  $repeat_variants = $('[class^="bookly-js-variant"]'),
39
  $repeat_weekly_week_day = $('.bookly-js-week-day'),
40
  $repeat_monthly_specific_day = $('.bookly-js-monthly-specific-day'),
41
  $repeat_monthly_week_day = $('.bookly-js-monthly-week-day'),
42
+ // Step Cart.
43
+ $show_cart_extras = $('#bookly-show-cart-extras'),
44
  // Step details.
45
+ $required_details = $('#bookly-cst-required-details'),
46
  $show_login_button = $('#bookly-show-login-button'),
47
+ $show_facebook_login_button = $('#bookly-show-facebook-login-button'),
48
  $first_last_name = $('#bookly-cst-first-last-name'),
49
  $show_notes_field = $('#bookly-show-notes'),
50
+ $show_birthday_fields = $('#bookly-show-birthday'),
51
+ $show_address_fields = $('#bookly-show-address'),
52
+ $show_google_maps = $('#bookly-show-google-maps'),
53
+ $show_custom_fields = $('#bookly-show-custom-fields'),
54
+ $show_customer_information = $('#bookly-show-customer-information'),
55
+ $show_files = $('#bookly-show-files'),
56
+ // Step payment.
57
+ $show_coupons = $('#bookly-show-coupons'),
58
  // Buttons.
59
  $save_button = $('#ajax-send-appearance'),
60
  $reset_button = $('button[type=reset]'),
61
+ $checkboxes = $('#bookly-appearance').find('input[type="checkbox"]'),
62
+ $selects = $('#bookly-appearance').find('select[data-default]')
63
  ;
64
 
65
  $checkboxes.each(function () {
141
  $('.bookly-progress-tracker').toggle(this.checked);
142
  }).trigger('change');
143
 
144
+ // Show steps.
145
+ $('.bookly-js-show-step').on('change', function () {
146
+ var target = $(this).data('target'),
147
+ $button = $('li.bookly-nav-item[data-target="#' + target + '"]'),
148
+ $step = $('div[data-step="' + target + '"]');
149
+ if ($(this).prop('checked')) {
150
+ $button.show();
151
+ $step.show();
152
+ } else {
153
+ if ($button.hasClass('active')) {
154
+ $('li.bookly-nav-item[data-target="#bookly-step-1"]').trigger('click');
155
+ }
156
+ $button.hide();
157
+ $step.hide();
158
+ }
159
+ // Hide/show cart buttons
160
+ if ( target == 'bookly-step-5') {
161
+ $('.bookly-js-go-to-cart').toggle( $(this).prop('checked') );
162
+ }
163
+
164
+ $('.bookly-progress-tracker > div:visible').each(function (num) {
165
+ $(this).find('.bookly-js-step-number').html(num + 1);
166
+ });
167
+ $('.bookly-js-appearance-steps > li:visible').each(function (num) {
168
+ $(this).find('.bookly-js-step-number').html(num + 1);
169
+ });
170
+ }).trigger('change');
171
+
172
  // Show step specific settings.
173
  $('li.bookly-nav-item').on('shown.bs.tab', function (e) {
174
  $step_settings.children().hide();
175
  switch (e.target.getAttribute('data-target')) {
176
  case '#bookly-step-1': $step_settings.find('.bookly-js-service-settings').show(); break;
177
  case '#bookly-step-3': $step_settings.find('.bookly-js-time-settings').show(); break;
178
+ case '#bookly-step-5': $step_settings.find('.bookly-js-cart-settings').show(); break;
179
  case '#bookly-step-6': $step_settings.find('.bookly-js-details-settings').show(); break;
180
  case '#bookly-step-7': $step_settings.find('.bookly-js-payment-settings').show(); break;
181
  case '#bookly-step-8': $step_settings.find('.bookly-js-done-settings').show(); break;
220
  $('.employee-name').toggle(!$staff_name_with_price.prop("checked"));
221
  }).trigger('change');
222
 
223
+ if ($service_duration_with_price.prop("checked")) {
224
+ $('.bookly-js-select-duration').val(-1);
225
+ }
226
+
227
+ // Show price next to service duration.
228
+ $service_duration_with_price.on('change', function () {
229
+ var duration = $('.bookly-js-select-duration').val();
230
+ if (duration) {
231
+ $('.bookly-js-select-duration').val(duration * -1);
232
+ }
233
+ $('.bookly-js-duration-price').toggle($service_duration_with_price.prop("checked"));
234
+ $('.bookly-js-duration').toggle(!$service_duration_with_price.prop("checked"));
235
+ }).trigger('change');
236
+
237
+ $show_ratings.on('change', function () {
238
+ var state = $(this).prop('checked');
239
+ $('.bookly-js-select-employee option').each(function () {
240
+ if ($(this).val() != '0') {
241
+ if (!state) {
242
+ if ($(this).text().charAt(0) == '★') {
243
+ $(this).text($(this).text().substring(5));
244
+ }
245
+ } else {
246
+ var rating = Math.round(10 * (Math.random() * 6 + 1)) / 10;
247
+ if (rating <= 5) {
248
+ $(this).text('★' + rating.toFixed(1) + ' ' + $(this).text());
249
+ }
250
+ }
251
+ }
252
+ });
253
+ }).trigger('change');
254
+
255
+ // Show chain appointments
256
+ $show_chain_appointments.on('change', function () {
257
+ $('.bookly-js-chain-appointments').toggle( this.checked );
258
+ });
259
+
260
+ // Show location
261
+ $show_location.on('change', function () {
262
+ $('.bookly-js-location').toggle( this.checked );
263
+ if (!this.disabled) {
264
+ if (this.checked) {
265
+ $required_location.closest('[data-toggle="popover"]').popover('destroy');
266
+ $required_location.prop('disabled', false);
267
+ } else {
268
+ $required_location.closest('[data-toggle="popover"]').popover();
269
+ $required_location.prop('checked', false).prop('disabled', true).trigger('change');
270
+ }
271
+ }
272
+ }).trigger('change');
273
+
274
+ // Show custom duration
275
+ $show_custom_duration.on('change', function () {
276
+ $('.bookly-js-custom-duration').toggle( this.checked );
277
+ if (this.checked) {
278
+ $service_duration_with_price.closest('[data-toggle="popover"]').popover('destroy');
279
+ $service_duration_with_price.prop('disabled', false);
280
+ } else {
281
+ $service_duration_with_price.closest('[data-toggle="popover"]').popover();
282
+ $service_duration_with_price.prop('checked', false).prop('disabled', true).trigger('change');
283
+ }
284
+ }).trigger('change');
285
+
286
+ // Show number of persons
287
+ $show_nop.on('change', function () {
288
+ $('.bookly-js-nop').toggle( this.checked );
289
+ if (this.checked) {
290
+ $time_step_nop.closest('[data-toggle="popover"]').popover('destroy');
291
+ $time_step_nop.prop('disabled', false);
292
+ } else {
293
+ $time_step_nop.closest('[data-toggle="popover"]').popover();
294
+ $time_step_nop.prop('checked', false).prop('disabled', true).trigger('change');
295
+ }
296
+ }).trigger('change');
297
+
298
+ // Show quantity
299
+ $show_quantity.on('change', function () {
300
+ $('.bookly-js-quantity').toggle( this.checked );
301
+ });
302
+
303
+ // Set max quantity
304
+ $('.bookly_multiply_appointments_quantity_max').on('save', function (e, params) {
305
+ var $options = '';
306
+ for (var x = 1; x <= params.newValue['bookly_multiply_appointments_quantity_max']; x++) {
307
+ $options += "<option>" + x + "</option>";
308
+ }
309
+ $('.bookly-js-select-quantity').html($options);
310
+ });
311
+
312
  // Show duration next to service name.
313
  $service_name_with_duration.on('change', function () {
314
  var service = $('.bookly-js-select-service').val();
319
  $('.service-name').toggle(!$service_name_with_duration.prop("checked"));
320
  }).trigger('change');
321
 
322
+ // Show price next to service duration.
323
+ $service_duration_with_price.on('change', function () {
324
+ if ($(this).prop('checked')) {
325
+ $('.bookly-js-select-duration option[value="1"]').each(function () {
326
+ $(this).text($(this).attr('data-text-1'));
327
+ });
328
+ } else {
329
+ $('.bookly-js-select-duration option[value="1"]').each(function () {
330
+ $(this).text($(this).attr('data-text-0'));
331
+ });
332
+ }
333
+ }).trigger('change');
334
+
335
  // Clickable week-days.
336
  $repeat_weekly_week_day.on('change', function () {
337
  $(this).parent().toggleClass('active', this.checked);
383
  }).trigger('change');
384
 
385
  // Show blocked time slots.
386
+ $show_blocked_timeslots.on('change', function () {
387
  if (this.checked) {
388
  $('.bookly-hour.no-booked').removeClass('no-booked').addClass('booked');
389
+ $('.bookly-column .bookly-hour.booked .bookly-time-additional', $columnizer).text('');
390
  } else {
391
  $('.bookly-hour.booked').removeClass('booked').addClass('no-booked');
392
+ if ($time_step_nop.prop('checked')) {
393
+ $('.bookly-column .bookly-hour:not(.booked):not(.bookly-slot-in-waiting-list) .bookly-time-additional', $columnizer).each(function () {
394
+ var nop = Math.ceil(Math.random() * 9);
395
+ if (BooklyL10n.nop_format == 'busy') {
396
+ $(this).text('[' + nop + '/10]');
397
+ } else {
398
+ $(this).text('[' + nop + ']');
399
+ }
400
+ });
401
+ }
402
  }
403
  });
404
 
413
  }
414
  });
415
 
416
+ // Show time zone switcher
417
+ $show_time_zone_switcher.on('change', function() {
418
+ $('.bookly-js-time-zone-switcher').toggle(this.checked);
419
+ }).trigger('change');
420
+
421
+ // Show nop/capacity
422
+ $time_step_nop.on('change', function () {
423
+ if (this.checked) {
424
+ $('.bookly-column', $columnizer).addClass('bookly-column-wide');
425
+ $('.bookly-column .bookly-hour:not(.booked):not(.bookly-slot-in-waiting-list) .bookly-time-additional', $columnizer).each(function () {
426
+ var nop = Math.ceil(Math.random() * 9);
427
+ if (BooklyL10n.nop_format == 'busy') {
428
+ $(this).text('[' + nop + '/10]');
429
+ } else {
430
+ $(this).text('[' + nop + ']');
431
+ }
432
+ });
433
+ $('.bookly-column.col5', $columnizer).hide();
434
+ $('.bookly-column.col6', $columnizer).hide();
435
+ $('.bookly-column.col7', $columnizer).hide();
436
+ } else {
437
+ $('.bookly-column', $columnizer).removeClass('bookly-column-wide');
438
+ $('.bookly-column .bookly-hour:not(.bookly-slot-in-waiting-list) .bookly-time-additional', $columnizer).text('');
439
+ if (!$show_calendar.prop('checked')) {
440
+ $('.bookly-column', $columnizer).removeClass('bookly-column-wide').show();
441
+ }
442
+ }
443
+ }).trigger('change');
444
+
445
+ $show_waiting_list.on('change', function () {
446
+ if (this.checked) {
447
+ $('.bookly-column .bookly-hour.no-waiting-list, .bookly-column .bookly-hour.bookly-slot-in-waiting-list').each(function () {
448
+ $(this).removeClass('no-waiting-list').addClass('bookly-slot-in-waiting-list').find('.bookly-time-additional').text('(' + Math.floor(Math.random() * 10) + ')');
449
+ })
450
+ } else {
451
+ $('.bookly-column .bookly-hour.bookly-slot-in-waiting-list').removeClass('bookly-slot-in-waiting-list').addClass('no-waiting-list').find('.bookly-time-additional').text('');
452
+ $time_step_nop.trigger('change');
453
+ }
454
+ }).trigger('change');
455
+
456
  /**
457
  * Step repeat.
458
  */
492
  });
493
 
494
 
495
+ /**
496
+ * Step Repeat.
497
+ */
498
+ $bookly_show_step_repeat.change(function () {
499
+ $('.bookly-js-repeat-enabled').toggle(this.checked);
500
+ }).trigger('change');
501
+
502
+ /**
503
+ * Step Cart
504
+ */
505
+ $show_cart_extras.change(function () {
506
+ $('.bookly-js-extras-cart').toggle(this.checked);
507
+ }).trigger('change');
508
+
509
  /**
510
  * Step Details
511
  */
525
  });
526
  }
527
 
528
+ // Show login button.
529
  $show_login_button.change(function () {
530
+ $('#bookly-login-button').toggle(this.checked);
531
  }).trigger('change');
532
 
533
+ // Show Facebook login button.
534
+ $show_facebook_login_button.change(function () {
535
+ if ($(this).data('appid') == '') {
536
+ $('#bookly-facebook-warning').modal('show');
537
+ this.checked = false;
538
+ } else {
539
+ $('#bookly-facebook-login-button').toggle(this.checked);
540
+ }
541
+ });
542
+
543
+ if ($show_facebook_login_button.prop('checked')) {
544
+ $show_facebook_login_button.trigger('change');
545
+ }
546
+
547
  // Show first and last name.
548
  $first_last_name.on('change', function () {
549
  $first_last_name.popover('toggle');
561
  $('#bookly-js-notes').toggle(this.checked);
562
  }).trigger('change');
563
 
564
+ // Show birthday fields
565
+ $show_birthday_fields.change(function () {
566
+ $('#bookly-js-birthday').toggle(this.checked);
567
+ }).trigger('change');
568
+
569
+ // Show address fields
570
+ $show_address_fields.change(function () {
571
+ $('#bookly-js-address').toggle(this.checked);
572
+ if (this.checked) {
573
+ $show_google_maps.closest('[data-toggle="popover"]').popover('destroy');
574
+ $show_google_maps.prop('disabled', false);
575
+ } else {
576
+ $show_google_maps.closest('[data-toggle="popover"]').popover();
577
+ $show_google_maps.prop('checked', false).prop('disabled', true).trigger('change');
578
+ }
579
+ }).trigger('change');
580
+
581
+ // Show address fields
582
+ $show_google_maps.change(function () {
583
+ $('.bookly-js-google-maps').toggle(this.checked);
584
+ }).trigger('change');
585
+
586
+ $show_custom_fields.change(function () {
587
+ $('.bookly-js-custom-fields').toggle(this.checked);
588
+ if (this.checked) {
589
+ $show_files.closest('[data-toggle="popover"]').popover('destroy');
590
+ $show_files.prop('disabled', false);
591
+ } else {
592
+ $show_files.closest('[data-toggle="popover"]').popover();
593
+ $show_files.prop('checked', false).prop('disabled', true).trigger('change');
594
+ }
595
+ }).trigger('change');
596
+
597
+ $show_files.change(function () {
598
+ $('.bookly-js-files').toggle(this.checked);
599
+ }).trigger('change');
600
+
601
+ $show_customer_information.change(function () {
602
+ $('.bookly-js-customer-information').toggle(this.checked);
603
+ }).trigger('change');
604
+
605
  /**
606
  * Step Payment.
607
  */
617
  $('form.bookly-card-form').toggle(this.id == 'bookly-card-payment');
618
  });
619
 
620
+ $show_coupons.on('change', function () {
621
+ $('.bookly-js-payment-coupons').toggle( this.checked );
622
+ });
623
+
624
  /**
625
  * Step Done.
626
  */
636
  /**
637
  * Misc.
638
  */
639
+ $('.bookly-js-simple-popover').popover();
640
 
641
  // Custom CSS.
642
  $('#bookly-custom-css-save').on('click', function (e) {
697
  });
698
 
699
  // Save options.
700
+ $save_button.on('click', function (e) {
701
  e.preventDefault();
702
  // Prepare data.
703
  var data = {
704
+ action : 'bookly_update_appearance_options',
705
  csrf_token: BooklyL10n.csrf_token,
706
+ options : {
707
  // Color.
708
  'bookly_app_color' : $color_picker.wpColorPicker('color'),
709
  // Checkboxes.
711
  'bookly_app_show_blocked_timeslots' : Number($show_blocked_timeslots.prop('checked')),
712
  'bookly_app_show_calendar' : Number($show_calendar.prop('checked')),
713
  'bookly_app_show_day_one_column' : Number($show_day_one_column.prop('checked')),
714
+ 'bookly_app_show_time_zone_switcher' : Number($show_time_zone_switcher.prop('checked')),
715
  'bookly_app_show_login_button' : Number($show_login_button.prop('checked')),
716
+ 'bookly_app_show_facebook_login_button' : Number($show_facebook_login_button.prop('checked')),
717
  'bookly_app_show_notes' : Number($show_notes_field.prop('checked')),
718
+ 'bookly_app_show_birthday' : Number($show_birthday_fields.prop('checked')),
719
+ 'bookly_app_show_address' : Number($show_address_fields.prop('checked')),
720
  'bookly_app_show_progress_tracker' : Number($show_progress_tracker.prop('checked')),
721
  'bookly_app_staff_name_with_price' : Number($staff_name_with_price.prop('checked')),
722
+ 'bookly_app_service_duration_with_price': Number($service_duration_with_price.prop('checked')),
723
  'bookly_app_required_employee' : Number($required_employee.prop('checked')),
724
  'bookly_app_required_location' : Number($required_location.prop('checked')),
725
+ 'bookly_group_booking_app_show_nop' : Number($time_step_nop.prop('checked')),
726
+ 'bookly_ratings_app_show_on_frontend' : Number($show_ratings.prop('checked')),
727
+ 'bookly_cst_required_details' : $required_details.val() == 'both' ? ['phone', 'email'] : [$required_details.val()],
728
+ 'bookly_cst_first_last_name' : Number($first_last_name.prop('checked')),
729
+ 'bookly_service_extras_enabled' : Number($bookly_show_step_extras.prop('checked')),
730
+ 'bookly_recurring_appointments_enabled' : Number($bookly_show_step_repeat.prop('checked')),
731
+ 'bookly_cart_enabled' : Number($bookly_show_step_cart.prop('checked')),
732
+ 'bookly_chain_appointments_enabled' : Number($show_chain_appointments.prop('checked')),
733
+ 'bookly_coupons_enabled' : Number($show_coupons.prop('checked')),
734
+ 'bookly_custom_fields_enabled' : Number($show_custom_fields.prop('checked')),
735
+ 'bookly_customer_information_enabled' : Number($show_customer_information.prop('checked')),
736
+ 'bookly_files_enabled' : Number($show_files.prop('checked')),
737
+ 'bookly_waiting_list_enabled' : Number($show_waiting_list.prop('checked')),
738
+ 'bookly_google_maps_address_enabled' : Number($show_google_maps.prop('checked')),
739
+ 'bookly_service_extras_show_in_cart' : Number($show_cart_extras.prop('checked')),
740
+ 'bookly_locations_enabled' : Number($show_location.prop('checked')),
741
+ 'bookly_custom_duration_enabled' : Number($show_custom_duration.prop('checked')),
742
+ 'bookly_group_booking_enabled' : Number($show_nop.prop('checked')),
743
+ 'bookly_multiply_appointments_enabled' : Number($show_quantity.prop('checked'))
744
  }
745
  };
746
  // Add data from editable elements.
772
  $(this).prop('checked', $(this).data('default')).trigger('change');
773
  }
774
  });
775
+ $selects.each(function () {
776
+ if ($(this).val() != $(this).data('default')) {
777
+ $(this).val($(this).data('default')).trigger('change');
778
+ }
779
+ });
780
  $first_last_name.popover('hide');
781
  });
782
  });
backend/modules/appearance/resources/js/bootstrap-editable.bookly.js CHANGED
@@ -32,44 +32,57 @@ jQuery(function($) {
32
  var $row = $('<div/>')
33
  .css({position: 'relative', 'margin-top': '6px'})
34
  .appendTo(_this.$tpl);
35
- if (_this.options.fieldType == 'input') {
36
- // Create input with "x" button.
37
- var $clear = $('<span class="editable-clear-x"></span>');
38
- var $input = $('<input/>', {
39
- type : 'text',
40
- class: 'form-control',
41
- name : option_name,
42
- value: option_value
43
- });
44
- $input.keyup(function(e) {
45
- // arrows, enter, tab, etc
46
- if (~$.inArray(e.keyCode, [40,38,9,13,27])) {
47
- return;
48
- }
49
- clearTimeout(this.t);
50
- this.t = setTimeout(function() {
51
- var len = $input.val().length,
52
- visible = $clear.is(':visible');
53
- if (len && !visible) {
54
- $clear.show();
55
  }
56
- if (!len && visible) {
57
- $clear.hide();
58
- }
59
- }, 100);
60
- });
61
- $clear.click(function () {
62
- $clear.hide();
63
- $input.val('').focus();
64
- });
65
- $row.append($input).append($clear);
66
- } else {
67
- // Create textarea.
68
- $('<textarea/>', {
69
- class: 'form-control',
70
- name : option_name,
71
- rows : 7
72
- }).val(option_value).appendTo($row);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  }
74
  });
75
  // Set codes.
32
  var $row = $('<div/>')
33
  .css({position: 'relative', 'margin-top': '6px'})
34
  .appendTo(_this.$tpl);
35
+ switch (_this.options.fieldType) {
36
+ case 'input':
37
+ // Create input with "x" button.
38
+ var $clear = $('<span class="editable-clear-x"></span>');
39
+ var $input = $('<input/>', {
40
+ type : 'text',
41
+ class: 'form-control',
42
+ name : option_name,
43
+ value: option_value
44
+ });
45
+ $input.keyup(function(e) {
46
+ // arrows, enter, tab, etc
47
+ if (~$.inArray(e.keyCode, [40,38,9,13,27])) {
48
+ return;
 
 
 
 
 
 
49
  }
50
+ clearTimeout(this.t);
51
+ this.t = setTimeout(function() {
52
+ var len = $input.val().length,
53
+ visible = $clear.is(':visible');
54
+ if (len && !visible) {
55
+ $clear.show();
56
+ }
57
+ if (!len && visible) {
58
+ $clear.hide();
59
+ }
60
+ }, 100);
61
+ });
62
+ $clear.click(function () {
63
+ $clear.hide();
64
+ $input.val('').focus();
65
+ });
66
+ $row.append($input).append($clear);
67
+ break;
68
+ case 'number':
69
+ // Create input[type="number"]
70
+ $('<input/>', {
71
+ type : 'number',
72
+ class: 'form-control',
73
+ name : option_name,
74
+ min : $(_this.options.scope).data('min'),
75
+ step : $(_this.options.scope).data('step')
76
+ }).val(option_value).appendTo($row);
77
+ break;
78
+ default :
79
+ // Create textarea.
80
+ $('<textarea/>', {
81
+ class: 'form-control',
82
+ name : option_name,
83
+ rows : 7
84
+ }).val(option_value).appendTo($row);
85
+ break;
86
  }
87
  });
88
  // Set codes.
backend/modules/appearance/templates/_1_service.php CHANGED
@@ -1,11 +1,10 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Utils\Price;
3
- use BooklyLite\Lib\Utils\DateTime;
4
-
5
- /**
6
- * @var BooklyLite\Backend\Modules\Appearance\Lib\Helper $editable
7
- * @var WP_Locale $wp_locale
8
- */
9
  global $wp_locale;
10
  ?>
11
  <div class="bookly-form">
@@ -14,18 +13,14 @@ global $wp_locale;
14
  <div class="bookly-service-step">
15
  <div class="bookly-box">
16
  <span class="bookly-bold bookly-desc">
17
- <?php $editable::renderText( 'bookly_l10n_info_service_step' ) ?>
18
  </span>
19
  </div>
20
  <div class="bookly-mobile-step-1 bookly-js-mobile-step-1 bookly-box">
21
  <div class="bookly-js-chain-item bookly-table bookly-box">
22
- <?php if ( \BooklyLite\Lib\Config::locationsEnabled() ) : ?>
23
- <div class="bookly-form-group">
24
- <?php \BooklyLite\Lib\Proxy\Locations::renderAppearance() ?>
25
- </div>
26
- <?php endif ?>
27
  <div class="bookly-form-group">
28
- <?php $editable::renderLabel( array( 'bookly_l10n_label_category', 'bookly_l10n_option_category', ) ) ?>
29
  <div>
30
  <select class="bookly-select-mobile bookly-js-select-category">
31
  <option value="" class="bookly-js-option bookly_l10n_option_category"><?php echo esc_html( get_option( 'bookly_l10n_option_category' ) ) ?></option>
@@ -37,7 +32,7 @@ global $wp_locale;
37
  </div>
38
  </div>
39
  <div class="bookly-form-group">
40
- <?php $editable::renderLabel( array(
41
  'bookly_l10n_label_service',
42
  'bookly_l10n_option_service',
43
  'bookly_l10n_required_service',
@@ -65,7 +60,7 @@ global $wp_locale;
65
  </div>
66
  </div>
67
  <div class="bookly-form-group">
68
- <?php $editable::renderLabel( array(
69
  'bookly_l10n_label_employee',
70
  'bookly_l10n_option_employee',
71
  'bookly_l10n_required_employee',
@@ -96,32 +91,14 @@ global $wp_locale;
96
  </select>
97
  </div>
98
  </div>
99
- <div class="bookly-form-group" style="display: none">
100
- <?php $editable::renderLabel( array( 'bookly_l10n_label_number_of_persons', ) ) ?>
101
- <div>
102
- <select class="bookly-select-mobile bookly-js-select-number-of-persons">
103
- <option>1</option>
104
- <option>2</option>
105
- <option>3</option>
106
- </select>
107
- </div>
108
- </div>
109
- <?php if ( \BooklyLite\Lib\Config::multiplyAppointmentsEnabled() ) : ?>
110
- <div class="bookly-form-group">
111
- <?php \BooklyLite\Lib\Proxy\MultiplyAppointments::renderAppearance() ?>
112
- </div>
113
- <?php endif ?>
114
- <?php if ( \BooklyLite\Lib\Config::chainAppointmentsEnabled() ) : ?>
115
- <div class="bookly-form-group">
116
- <label></label>
117
- <div>
118
- <button class="bookly-round"><i class="bookly-icon-sm bookly-icon-plus"></i></button>
119
- </div>
120
- </div>
121
- <?php endif ?>
122
  </div>
123
  <div class="bookly-right bookly-mobile-next-step bookly-js-mobile-next-step bookly-btn bookly-none">
124
- <?php $editable::renderString( array( 'bookly_l10n_step_service_mobile_button_next' ) ) ?>
125
  </div>
126
  </div>
127
  <div class="bookly-mobile-step-2 bookly-js-mobile-step-2">
@@ -129,7 +106,7 @@ global $wp_locale;
129
  <div class="bookly-left">
130
  <div class="bookly-available-date bookly-js-available-date bookly-left">
131
  <div class="bookly-form-group">
132
- <?php $editable::renderLabel( array( 'bookly_l10n_label_select_date', ) ) ?>
133
  <div>
134
  <input class="bookly-date-from bookly-js-date-from" style="background-color: #fff;" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
135
  </div>
@@ -148,7 +125,7 @@ global $wp_locale;
148
  </div>
149
  <div class="bookly-time-range bookly-js-time-range bookly-left">
150
  <div class="bookly-form-group bookly-time-from bookly-left">
151
- <?php $editable::renderLabel( array( 'bookly_l10n_label_start_from', ) ) ?>
152
  <div>
153
  <select class="bookly-js-select-time-from">
154
  <?php for ( $i = 28800; $i <= 64800; $i += 3600 ) : ?>
@@ -158,7 +135,7 @@ global $wp_locale;
158
  </div>
159
  </div>
160
  <div class="bookly-form-group bookly-time-to bookly-left">
161
- <?php $editable::renderLabel( array( 'bookly_l10n_label_finish_by', ) ) ?>
162
  <div>
163
  <select class="bookly-js-select-time-to">
164
  <?php for ( $i = 28800; $i <= 64800; $i += 3600 ) : ?>
@@ -171,10 +148,10 @@ global $wp_locale;
171
  </div>
172
  <div class="bookly-box bookly-nav-steps">
173
  <div class="bookly-right bookly-mobile-prev-step bookly-js-mobile-prev-step bookly-btn bookly-none">
174
- <?php $editable::renderString( array( 'bookly_l10n_button_back' ) ) ?>
175
  </div>
176
  <div class="bookly-next-step bookly-js-next-step bookly-btn">
177
- <?php $editable::renderString( array( 'bookly_l10n_step_service_button_next' ) ) ?>
178
  </div>
179
  <button class="bookly-go-to-cart bookly-js-go-to-cart bookly-round bookly-round-md ladda-button"><span><img src="<?php echo plugins_url( 'bookly-responsive-appointment-booking-tool/frontend/resources/images/cart.png' ) ?>" /></span></button>
180
  </div>
@@ -186,4 +163,4 @@ global $wp_locale;
186
  <div class="bookly-js-option <?php echo $validator ?>"><?php echo get_option( $validator ) ?></div>
187
  <?php endforeach ?>
188
  </div>
189
- <style id="bookly-pickadate-style"></style>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Lib\Utils\Price;
3
+ use Bookly\Lib\Utils\DateTime;
4
+ use Bookly\Backend\Modules\Appearance\Proxy;
5
+ use Bookly\Lib\Config;
6
+ use Bookly\Backend\Components\Appearance\Editable;
7
+ /** @var WP_Locale $wp_locale */
 
8
  global $wp_locale;
9
  ?>
10
  <div class="bookly-form">
13
  <div class="bookly-service-step">
14
  <div class="bookly-box">
15
  <span class="bookly-bold bookly-desc">
16
+ <?php Editable::renderText( 'bookly_l10n_info_service_step' ) ?>
17
  </span>
18
  </div>
19
  <div class="bookly-mobile-step-1 bookly-js-mobile-step-1 bookly-box">
20
  <div class="bookly-js-chain-item bookly-table bookly-box">
21
+ <?php Proxy\Locations::renderLocation() ?>
 
 
 
 
22
  <div class="bookly-form-group">
23
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_category', 'bookly_l10n_option_category', ) ) ?>
24
  <div>
25
  <select class="bookly-select-mobile bookly-js-select-category">
26
  <option value="" class="bookly-js-option bookly_l10n_option_category"><?php echo esc_html( get_option( 'bookly_l10n_option_category' ) ) ?></option>
32
  </div>
33
  </div>
34
  <div class="bookly-form-group">
35
+ <?php Editable::renderLabel( array(
36
  'bookly_l10n_label_service',
37
  'bookly_l10n_option_service',
38
  'bookly_l10n_required_service',
60
  </div>
61
  </div>
62
  <div class="bookly-form-group">
63
+ <?php Editable::renderLabel( array(
64
  'bookly_l10n_label_employee',
65
  'bookly_l10n_option_employee',
66
  'bookly_l10n_required_employee',
91
  </select>
92
  </div>
93
  </div>
94
+ <?php Proxy\CustomDuration::renderServiceDuration() ?>
95
+ <?php Proxy\GroupBooking::renderNOP() ?>
96
+ <?php Proxy\MultiplyAppointments::renderQuantity() ?>
97
+ <?php Proxy\ChainAppointments::renderChain() ?>
98
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  </div>
100
  <div class="bookly-right bookly-mobile-next-step bookly-js-mobile-next-step bookly-btn bookly-none">
101
+ <?php Editable::renderString( array( 'bookly_l10n_step_service_mobile_button_next' ) ) ?>
102
  </div>
103
  </div>
104
  <div class="bookly-mobile-step-2 bookly-js-mobile-step-2">
106
  <div class="bookly-left">
107
  <div class="bookly-available-date bookly-js-available-date bookly-left">
108
  <div class="bookly-form-group">
109
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_select_date', ) ) ?>
110
  <div>
111
  <input class="bookly-date-from bookly-js-date-from" style="background-color: #fff;" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
112
  </div>
125
  </div>
126
  <div class="bookly-time-range bookly-js-time-range bookly-left">
127
  <div class="bookly-form-group bookly-time-from bookly-left">
128
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_start_from', ) ) ?>
129
  <div>
130
  <select class="bookly-js-select-time-from">
131
  <?php for ( $i = 28800; $i <= 64800; $i += 3600 ) : ?>
135
  </div>
136
  </div>
137
  <div class="bookly-form-group bookly-time-to bookly-left">
138
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_finish_by', ) ) ?>
139
  <div>
140
  <select class="bookly-js-select-time-to">
141
  <?php for ( $i = 28800; $i <= 64800; $i += 3600 ) : ?>
148
  </div>
149
  <div class="bookly-box bookly-nav-steps">
150
  <div class="bookly-right bookly-mobile-prev-step bookly-js-mobile-prev-step bookly-btn bookly-none">
151
+ <?php Editable::renderString( array( 'bookly_l10n_button_back' ) ) ?>
152
  </div>
153
  <div class="bookly-next-step bookly-js-next-step bookly-btn">
154
+ <?php Editable::renderString( array( 'bookly_l10n_step_service_button_next' ) ) ?>
155
  </div>
156
  <button class="bookly-go-to-cart bookly-js-go-to-cart bookly-round bookly-round-md ladda-button"><span><img src="<?php echo plugins_url( 'bookly-responsive-appointment-booking-tool/frontend/resources/images/cart.png' ) ?>" /></span></button>
157
  </div>
163
  <div class="bookly-js-option <?php echo $validator ?>"><?php echo get_option( $validator ) ?></div>
164
  <?php endforeach ?>
165
  </div>
166
+ <style id="bookly-pickadate-style"></style>
backend/modules/appearance/templates/_3_time.php CHANGED
@@ -1,161 +1,194 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Backend\Modules\Appearance\Components;
3
- /** @var BooklyLite\Backend\Modules\Appearance\Lib\Helper $editable */
 
 
 
4
  ?>
5
  <div class="bookly-form">
6
  <?php include '_progress_tracker.php' ?>
7
 
8
  <div class="bookly-box">
9
- <?php $editable::renderText( 'bookly_l10n_info_time_step', Components::getInstance()->renderCodes( array( 'step' => 3 ), false ) ) ?>
10
- <div class="bookly-holder bookly-label-error bookly-bold">
11
- <?php $editable::renderText( 'bookly_l10n_step_time_slot_not_available', null, 'bottom', __( 'Visible when the chosen time slot has been already booked', 'bookly' ) ) ?>
12
- </div>
 
13
  </div>
 
 
14
  <!-- timeslots -->
15
  <div class="bookly-time-step">
16
  <div class="bookly-columnizer-wrap">
17
- <div class="bookly-columnizer">
18
- <div id="bookly-day-multi-columns" class="bookly-time-screen" style="display: <?php echo get_option( 'bookly_app_show_day_one_column' ) == 1 ? ' none' : 'block' ?>">
19
- <div class="bookly-input-wrap bookly-slot-calendar bookly-js-slot-calendar">
20
- <span class="bookly-date-wrap">
21
- <input style="display: none" class="bookly-js-selected-date bookly-form-element" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
22
- </span>
23
- </div>
24
- <div class="bookly-column col1">
25
- <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', current_time( 'timestamp' ) ) ?></button>
26
- <?php for ( $i = 28800; $i <= 57600; $i += 3600 ) : ?>
27
- <button class="bookly-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
28
- <span class="ladda-label">
29
- <i class="bookly-hour-icon"><span></span></i>
30
- <?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
31
- </span>
32
- </button>
33
- <?php endfor ?>
34
- </div>
35
- <div class="bookly-column col2">
36
- <button class="bookly-hour ladda-button bookly-last-child">
37
- <span class="ladda-label">
38
- <i class="bookly-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( 61200 ) ?>
39
  </span>
40
- </button>
41
- <button class="bookly-day bookly-js-first-child" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'block' ?>"><?php echo date_i18n( 'D, M d', strtotime( '+1 day', current_time( 'timestamp' ) ) ) ?></button>
42
- <?php for ( $i = 28800; $i <= 54000; $i += 3600 ) : ?>
43
- <button class="bookly-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'block' ?>">
44
- <span class="ladda-label">
45
- <i class="bookly-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
46
- </span>
47
- </button>
48
- <?php endfor ?>
49
- </div>
50
- <div class="bookly-column col3" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
51
- <?php for ( $i = 57600; $i <= 61200; $i += 3600 ) : ?>
52
- <button class="bookly-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
53
- <span class="ladda-label">
54
- <i class="bookly-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
55
- </span>
56
- </button>
57
- <?php endfor ?>
58
- <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+2 days', current_time('timestamp') ) ) ?></button>
59
- <?php for ( $i = 28800; $i <= 50400; $i += 3600 ) : ?>
60
- <button class="bookly-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
61
- <span class="ladda-label">
62
- <i class="bookly-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
63
- </span>
64
- </button>
65
- <?php endfor ?>
66
- </div>
67
- <div class="bookly-column col4" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
68
- <?php for ( $i = 54000; $i <= 61200; $i += 3600 ) : ?>
69
- <button class="bookly-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
70
- <span class="ladda-label">
71
- <i class="bookly-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
72
- </span>
73
- </button>
74
- <?php endfor ?>
75
- <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+3 days', current_time( 'timestamp' ) ) ) ?></button>
76
- <?php for ( $i = 28800; $i <= 46800; $i += 3600 ) : ?>
77
- <button class="bookly-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
78
- <span class="ladda-label">
79
- <i class="bookly-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
80
- </span>
81
- </button>
82
- <?php endfor ?>
83
- </div>
84
- <div class="bookly-column col5" style="display:<?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : ' inline-block' ?>">
85
- <?php for ( $i = 50400; $i <= 61200; $i += 3600 ) : ?>
86
- <button class="bookly-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
87
- <span class="ladda-label">
88
- <i class="bookly-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
89
- </span>
90
- </button>
91
- <?php endfor ?>
92
- <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+4 days', current_time( 'timestamp' ) ) ) ?></button>
93
- <?php for ( $i = 28800; $i <= 43200; $i += 3600 ) : ?>
94
- <button class="bookly-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
95
- <span class="ladda-label">
96
- <i class="bookly-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
97
- </span>
98
- </button>
99
- <?php endfor ?>
100
- </div>
101
- <div class="bookly-column col6" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
102
- <?php for ( $i = 46800; $i <= 61200; $i += 3600 ) : ?>
103
- <button class="bookly-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
104
- <span class="ladda-label">
105
- <i class="bookly-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
106
- </span>
107
- </button>
108
- <?php endfor ?>
109
- <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+5 days', current_time( 'timestamp' ) ) ) ?></button>
110
- <?php for ( $i = 28800; $i <= 39600; $i += 3600 ) : ?>
111
- <button class="bookly-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
112
- <span class="ladda-label">
113
- <i class="bookly-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
114
- </span>
115
- </button>
116
- <?php endfor ?>
117
- </div>
118
- <div class="bookly-column col7" style="display:<?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : ' inline-block' ?>">
119
- <?php for ( $i = 43200; $i <= 61200; $i += 3600 ) : ?>
120
- <button class="bookly-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
121
- <span class="ladda-label">
122
- <i class="bookly-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
123
- </span>
124
- </button>
125
- <?php endfor ?>
126
- <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+6 days', current_time( 'timestamp' ) ) ) ?></button>
127
- <?php for ( $i = 28800; $i <= 36000; $i += 3600 ) : ?>
128
- <button class="bookly-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
129
- <span class="ladda-label">
130
- <i class="bookly-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $i ) ?>
131
  </span>
 
132
  </button>
133
- <?php endfor ?>
134
- </div>
135
- </div>
136
-
137
- <div id="bookly-day-one-column" class="bookly-time-screen" style="display: <?php echo get_option( 'bookly_app_show_day_one_column' ) == 1 ? ' block' : 'none' ?>">
138
- <div class="bookly-input-wrap bookly-slot-calendar bookly-js-slot-calendar">
139
- <span class="bookly-date-wrap">
140
- <input style="display: none" class="bookly-js-selected-date bookly-form-element" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
141
- </span>
142
- </div>
143
- <?php for ( $i = 1; $i <= 7; ++ $i ) : ?>
144
- <div class="bookly-column col<?php echo $i ?>">
145
- <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+' . ( $i - 1 ) . ' days', current_time( 'timestamp' ) ) ) ?></button>
146
- <?php for ( $j = 28800; $j <= 61200; $j += 3600 ) : ?>
147
- <button class="bookly-hour ladda-button<?php if ( mt_rand( 0, 1 ) ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked' ?>">
148
- <span class="ladda-label">
149
- <i class="bookly-hour-icon"><span></span></i><?php echo \BooklyLite\Lib\Utils\DateTime::formatTime( $j ) ?>
150
- </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  </button>
152
  <?php endfor ?>
153
  </div>
154
- <?php endfor ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  </div>
156
  </div>
157
  </div>
158
- </div>
159
  <div class="bookly-box bookly-nav-steps">
160
  <button class="bookly-time-next bookly-btn bookly-right ladda-button">
161
  <span class="bookly-label">&gt;</span>
@@ -164,7 +197,7 @@ use BooklyLite\Backend\Modules\Appearance\Components;
164
  <span class="bookly-label">&lt;</span>
165
  </button>
166
  <div class="bookly-back-step bookly-js-back-step bookly-btn">
167
- <?php $editable::renderString( array( 'bookly_l10n_button_back' ) ) ?>
168
  </div>
169
  <button class="bookly-go-to-cart bookly-js-go-to-cart bookly-round bookly-round-md ladda-button" data-style="zoom-in" data-spinner-size="30"><span class="ladda-label"><img src="<?php echo plugins_url( 'bookly-responsive-appointment-booking-tool/frontend/resources/images/cart.png' ) ?>" /></span></button>
170
  </div>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Lib\Utils\DateTime;
3
+ use Bookly\Lib\Config;
4
+ use Bookly\Backend\Components\Appearance\Codes;
5
+ use Bookly\Backend\Components\Appearance\Editable;
6
+ use Bookly\Backend\Modules\Appearance\Proxy;
7
  ?>
8
  <div class="bookly-form">
9
  <?php include '_progress_tracker.php' ?>
10
 
11
  <div class="bookly-box">
12
+ <?php Editable::renderText( 'bookly_l10n_info_time_step', Codes::getHtml( 3 ) ) ?>
13
+ </div>
14
+ <?php Proxy\WaitingList::renderInfoText() ?>
15
+ <div class="bookly-box bookly-label-error" style="padding-bottom:2px">
16
+ <?php Editable::renderText( 'bookly_l10n_step_time_slot_not_available', null, 'bottom', __( 'Visible when the chosen time slot has been already booked', 'bookly' ) ) ?>
17
  </div>
18
+ <?php Proxy\Pro::renderTimeZoneSwitcher() ?>
19
+
20
  <!-- timeslots -->
21
  <div class="bookly-time-step">
22
  <div class="bookly-columnizer-wrap">
23
+ <div class="bookly-columnizer">
24
+ <div id="bookly-day-multi-columns" class="bookly-time-screen" style="display: <?php echo get_option( 'bookly_app_show_day_one_column' ) == 1 ? ' none' : 'block' ?>">
25
+ <div class="bookly-input-wrap bookly-slot-calendar bookly-js-slot-calendar">
26
+ <span class="bookly-date-wrap">
27
+ <input style="display: none" class="bookly-js-selected-date bookly-form-element" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  </span>
29
+ </div>
30
+ <div class="bookly-column col1">
31
+ <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', current_time( 'timestamp' ) ) ?></button>
32
+ <?php for ( $i = 28800; $i <= 57600; $i += 3600 ) : ?>
33
+ <?php $slot_type = mt_rand( 0, 2 ) ?>
34
+ <button class="bookly-hour ladda-button<?php if ( $slot_type == 1 ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked'; elseif ( $slot_type == 2 && Config::waitingListActive() ) echo ' no-waiting-list' ?>">
35
+ <span class="ladda-label bookly-time-main">
36
+ <i class="bookly-hour-icon"><span></span></i>
37
+ <?php echo DateTime::formatTime( $i ) ?>
38
+ </span>
39
+ <span class="bookly-time-additional"></span>
40
+ </button>
41
+ <?php endfor ?>
42
+ </div>
43
+ <div class="bookly-column col2">
44
+ <button class="bookly-hour ladda-button bookly-last-child">
45
+ <span class="ladda-label bookly-time-main">
46
+ <i class="bookly-hour-icon"><span></span></i><?php echo DateTime::formatTime( 61200 ) ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  </span>
48
+ <span class="bookly-time-additional"></span>
49
  </button>
50
+ <button class="bookly-day bookly-js-first-child" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'block' ?>"><?php echo date_i18n( 'D, M d', strtotime( '+1 day', current_time( 'timestamp' ) ) ) ?></button>
51
+ <?php for ( $i = 28800; $i <= 54000; $i += 3600 ) : ?>
52
+ <?php $slot_type = mt_rand( 0, 2 ) ?>
53
+ <button class="bookly-hour ladda-button<?php if ( $slot_type == 1 ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked';elseif ( $slot_type == 2 && Config::waitingListActive() ) echo ' no-waiting-list'?>" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'block' ?>">
54
+ <span class="ladda-label bookly-time-main">
55
+ <i class="bookly-hour-icon"><span></span></i><?php echo DateTime::formatTime( $i ) ?>
56
+ </span>
57
+ <span class="bookly-time-additional"></span>
58
+ </button>
59
+ <?php endfor ?>
60
+ </div>
61
+ <div class="bookly-column col3" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
62
+ <?php for ( $i = 57600; $i <= 61200; $i += 3600 ) : ?>
63
+ <?php $slot_type = mt_rand( 0, 2 ) ?>
64
+ <button class="bookly-hour ladda-button<?php if ( $slot_type == 1 ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked';elseif ( $slot_type == 2 && Config::waitingListActive() ) echo ' no-waiting-list'?>">
65
+ <span class="ladda-label bookly-time-main">
66
+ <i class="bookly-hour-icon"><span></span></i><?php echo DateTime::formatTime( $i ) ?>
67
+ </span>
68
+ <span class="bookly-time-additional"></span>
69
+ </button>
70
+ <?php endfor ?>
71
+ <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+2 days', current_time('timestamp') ) ) ?></button>
72
+ <?php for ( $i = 28800; $i <= 50400; $i += 3600 ) : ?>
73
+ <?php $slot_type = mt_rand( 0, 2 ) ?>
74
+ <button class="bookly-hour ladda-button<?php if ( $slot_type == 1 ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked';elseif ( $slot_type == 2 && Config::waitingListActive() ) echo ' no-waiting-list'?>">
75
+ <span class="ladda-label bookly-time-main">
76
+ <i class="bookly-hour-icon"><span></span></i><?php echo DateTime::formatTime( $i ) ?>
77
+ </span>
78
+ <span class="bookly-time-additional"></span>
79
+ </button>
80
+ <?php endfor ?>
81
+ </div>
82
+ <div class="bookly-column col4" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
83
+ <?php for ( $i = 54000; $i <= 61200; $i += 3600 ) : ?>
84
+ <?php $slot_type = mt_rand( 0, 2 ) ?>
85
+ <button class="bookly-hour ladda-button<?php if ( $slot_type == 1 ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked';elseif ( $slot_type == 2 && Config::waitingListActive() ) echo ' no-waiting-list'?>">
86
+ <span class="ladda-label bookly-time-main">
87
+ <i class="bookly-hour-icon"><span></span></i><?php echo DateTime::formatTime( $i ) ?>
88
+ </span>
89
+ <span class="bookly-time-additional"></span>
90
+ </button>
91
+ <?php endfor ?>
92
+ <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+3 days', current_time( 'timestamp' ) ) ) ?></button>
93
+ <?php for ( $i = 28800; $i <= 46800; $i += 3600 ) : ?>
94
+ <?php $slot_type = mt_rand( 0, 2 ) ?>
95
+ <button class="bookly-hour ladda-button<?php if ( $slot_type == 1 ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked';elseif ( $slot_type == 2 && Config::waitingListActive() ) echo ' no-waiting-list'?>">
96
+ <span class="ladda-label bookly-time-main">
97
+ <i class="bookly-hour-icon"><span></span></i><?php echo DateTime::formatTime( $i ) ?>
98
+ </span>
99
+ <span class="bookly-time-additional"></span>
100
+ </button>
101
+ <?php endfor ?>
102
+ </div>
103
+ <div class="bookly-column col5" style="display:<?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : ' inline-block' ?>">
104
+ <?php for ( $i = 50400; $i <= 61200; $i += 3600 ) : ?>
105
+ <?php $slot_type = mt_rand( 0, 2 ) ?>
106
+ <button class="bookly-hour ladda-button<?php if ( $slot_type == 1 ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked';elseif ( $slot_type == 2 && Config::waitingListActive() ) echo ' no-waiting-list'?>">
107
+ <span class="ladda-label bookly-time-main">
108
+ <i class="bookly-hour-icon"><span></span></i><?php echo DateTime::formatTime( $i ) ?>
109
+ </span>
110
+ <span class="bookly-time-additional"></span>
111
+ </button>
112
+ <?php endfor ?>
113
+ <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+4 days', current_time( 'timestamp' ) ) ) ?></button>
114
+ <?php for ( $i = 28800; $i <= 43200; $i += 3600 ) : ?>
115
+ <?php $slot_type = mt_rand( 0, 2 ) ?>
116
+ <button class="bookly-hour ladda-button<?php if ( $slot_type == 1 ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked';elseif ( $slot_type == 2 && Config::waitingListActive() ) echo ' no-waiting-list'?>">
117
+ <span class="ladda-label bookly-time-main">
118
+ <i class="bookly-hour-icon"><span></span></i><?php echo DateTime::formatTime( $i ) ?>
119
+ </span>
120
+ <span class="bookly-time-additional"></span>
121
  </button>
122
  <?php endfor ?>
123
  </div>
124
+ <div class="bookly-column col6" style="display: <?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : 'inline-block' ?>">
125
+ <?php for ( $i = 46800; $i <= 61200; $i += 3600 ) : ?>
126
+ <?php $slot_type = mt_rand( 0, 2 ) ?>
127
+ <button class="bookly-hour ladda-button<?php if ( $slot_type == 1 ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked';elseif ( $slot_type == 2 && Config::waitingListActive() ) echo ' no-waiting-list'?>">
128
+ <span class="ladda-label bookly-time-main">
129
+ <i class="bookly-hour-icon"><span></span></i><?php echo DateTime::formatTime( $i ) ?>
130
+ </span>
131
+ <span class="bookly-time-additional"></span>
132
+ </button>
133
+ <?php endfor ?>
134
+ <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+5 days', current_time( 'timestamp' ) ) ) ?></button>
135
+ <?php for ( $i = 28800; $i <= 39600; $i += 3600 ) : ?>
136
+ <?php $slot_type = mt_rand( 0, 2 ) ?>
137
+ <button class="bookly-hour ladda-button<?php if ( $slot_type == 1 ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked';elseif ( $slot_type == 2 && Config::waitingListActive() ) echo ' no-waiting-list'?>">
138
+ <span class="ladda-label bookly-time-main">
139
+ <i class="bookly-hour-icon"><span></span></i><?php echo DateTime::formatTime( $i ) ?>
140
+ </span>
141
+ <span class="bookly-time-additional"></span>
142
+ </button>
143
+ <?php endfor ?>
144
+ </div>
145
+ <div class="bookly-column col7" style="display:<?php echo get_option( 'bookly_app_show_calendar' ) == 1 ? ' none' : ' inline-block' ?>">
146
+ <?php for ( $i = 43200; $i <= 61200; $i += 3600 ) : ?>
147
+ <?php $slot_type = mt_rand( 0, 2 ) ?>
148
+ <button class="bookly-hour ladda-button<?php if ( $slot_type == 1 ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked';elseif ( $slot_type == 2 && Config::waitingListActive() ) echo ' no-waiting-list'?>">
149
+ <span class="ladda-label bookly-time-main">
150
+ <i class="bookly-hour-icon"><span></span></i><?php echo DateTime::formatTime( $i ) ?>
151
+ </span>
152
+ <span class="bookly-time-additional"></span>
153
+ </button>
154
+ <?php endfor ?>
155
+ <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+6 days', current_time( 'timestamp' ) ) ) ?></button>
156
+ <?php for ( $i = 28800; $i <= 36000; $i += 3600 ) : ?>
157
+ <?php $slot_type = mt_rand( 0, 2 ) ?>
158
+ <button class="bookly-hour ladda-button<?php if ( $slot_type == 1 ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked';elseif ( $slot_type == 2 && Config::waitingListActive() ) echo ' no-waiting-list'?>">
159
+ <span class="ladda-label bookly-time-main">
160
+ <i class="bookly-hour-icon"><span></span></i><?php echo DateTime::formatTime( $i ) ?>
161
+ </span>
162
+ <span class="bookly-time-additional"></span>
163
+ </button>
164
+ <?php endfor ?>
165
+ </div>
166
+ </div>
167
+
168
+ <div id="bookly-day-one-column" class="bookly-time-screen" style="display: <?php echo get_option( 'bookly_app_show_day_one_column' ) == 1 ? ' block' : 'none' ?>">
169
+ <div class="bookly-input-wrap bookly-slot-calendar bookly-js-slot-calendar">
170
+ <span class="bookly-date-wrap">
171
+ <input style="display: none" class="bookly-js-selected-date bookly-form-element" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
172
+ </span>
173
+ </div>
174
+ <?php for ( $i = 1; $i <= 7; ++ $i ) : ?>
175
+ <div class="bookly-column col<?php echo $i ?>">
176
+ <button class="bookly-day bookly-js-first-child"><?php echo date_i18n( 'D, M d', strtotime( '+' . ( $i - 1 ) . ' days', current_time( 'timestamp' ) ) ) ?></button>
177
+ <?php for ( $j = 28800; $j <= 61200; $j += 3600 ) : ?>
178
+ <?php $slot_type = mt_rand( 0, 2 ) ?>
179
+ <button class="bookly-hour ladda-button<?php if ( $slot_type == 1 ) echo get_option( 'bookly_app_show_blocked_timeslots' ) == 1 ? ' booked' : ' no-booked';elseif ( $slot_type == 2 && Config::waitingListActive() ) echo ' no-waiting-list'?>">
180
+ <span class="ladda-label bookly-time-main">
181
+ <i class="bookly-hour-icon"><span></span></i><?php echo DateTime::formatTime( $j ) ?>
182
+ </span>
183
+ <span class="bookly-time-additional"></span>
184
+ </button>
185
+ <?php endfor ?>
186
+ </div>
187
+ <?php endfor ?>
188
+ </div>
189
  </div>
190
  </div>
191
  </div>
 
192
  <div class="bookly-box bookly-nav-steps">
193
  <button class="bookly-time-next bookly-btn bookly-right ladda-button">
194
  <span class="bookly-label">&gt;</span>
197
  <span class="bookly-label">&lt;</span>
198
  </button>
199
  <div class="bookly-back-step bookly-js-back-step bookly-btn">
200
+ <?php Editable::renderString( array( 'bookly_l10n_button_back' ) ) ?>
201
  </div>
202
  <button class="bookly-go-to-cart bookly-js-go-to-cart bookly-round bookly-round-md ladda-button" data-style="zoom-in" data-spinner-size="30"><span class="ladda-label"><img src="<?php echo plugins_url( 'bookly-responsive-appointment-booking-tool/frontend/resources/images/cart.png' ) ?>" /></span></button>
203
  </div>
backend/modules/appearance/templates/_5_cart.php DELETED
@@ -1,144 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Utils\Price;
3
- use BooklyLite\Lib\Utils\DateTime;
4
- use BooklyLite\Backend\Modules\Appearance\Components;
5
-
6
- /** @var BooklyLite\Backend\Modules\Appearance\Lib\Helper $editable */
7
- ?>
8
- <div class="bookly-form">
9
- <?php include '_progress_tracker.php' ?>
10
-
11
- <div class="bookly-box">
12
- <?php $editable::renderText( 'bookly_l10n_info_cart_step', Components::getInstance()->renderCodes( array( 'step' => 5 ), false ) ) ?>
13
- <div class="bookly-holder bookly-label-error bookly-bold">
14
- <?php $editable::renderText( 'bookly_l10n_step_cart_slot_not_available', null, 'bottom', __( 'Visible when the chosen time slot has been already booked', 'bookly' ) ) ?>
15
- </div>
16
- </div>
17
-
18
- <div class="bookly-box">
19
- <div class="bookly-btn bookly-add-item bookly-inline-block">
20
- <?php $editable::renderString( array( 'bookly_l10n_button_book_more', ) ) ?>
21
- </div>
22
- </div>
23
-
24
- <div class="bookly-cart-step">
25
- <div class="bookly-cart bookly-box">
26
- <table>
27
- <thead class="bookly-desktop-version">
28
- <tr>
29
- <th class="bookly-js-option bookly_l10n_label_service"><?php echo esc_html( get_option( 'bookly_l10n_label_service' ) ) ?></th>
30
- <th><?php _e( 'Date', 'bookly' ) ?></th>
31
- <th><?php _e( 'Time', 'bookly' ) ?></th>
32
- <th class="bookly-js-option bookly_l10n_label_employee"><?php echo esc_html( get_option( 'bookly_l10n_label_employee' ) ) ?></th>
33
- <th><?php _e( 'Price', 'bookly' ) ?></th>
34
- <th></th>
35
- </tr>
36
- </thead>
37
- <tbody class="bookly-desktop-version">
38
- <tr class="bookly-cart-primary">
39
- <td>Crown and Bridge</td>
40
- <td><?php echo DateTime::formatDate( '+2 days' ) ?></td>
41
- <td><?php echo DateTime::formatTime( 28800 ) ?></td>
42
- <td>Nick Knight</td>
43
- <td><?php echo Price::format( 350 ) ?></td>
44
- <td>
45
- <button class="bookly-round" title="<?php esc_attr_e( 'Edit', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-edit"></i></button>
46
- <button class="bookly-round" title="<?php esc_attr_e( 'Remove', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-drop"></i></button>
47
- </td>
48
- </tr>
49
- <tr class="bookly-cart-primary">
50
- <td>Teeth Whitening</td>
51
- <td><?php echo DateTime::formatDate( '+3 days' ) ?></td>
52
- <td><?php echo DateTime::formatTime( 57600 ) ?></td>
53
- <td>Wayne Turner</td>
54
- <td><?php echo Price::format( 400 ) ?></td>
55
- <td>
56
- <button class="bookly-round" title="<?php esc_attr_e( 'Edit', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-edit"></i></button>
57
- <button class="bookly-round" title="<?php esc_attr_e( 'Remove', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-drop"></i></button>
58
- </td>
59
- </tr>
60
- </tbody>
61
- <tbody class="bookly-mobile-version">
62
- <tr class="bookly-cart-primary">
63
- <th class="bookly-js-option bookly_l10n_label_service"><?php echo esc_html( get_option( 'bookly_l10n_label_service' ) ) ?></th>
64
- <td>Crown and Bridge</td>
65
- </tr>
66
- <tr class="bookly-cart-primary">
67
- <th><?php _e( 'Date', 'bookly' ) ?></th>
68
- <td><?php echo DateTime::formatDate( '+2 days' ) ?></td>
69
- </tr>
70
- <tr class="bookly-cart-primary">
71
- <th><?php _e( 'Time', 'bookly' ) ?></th>
72
- <td><?php echo DateTime::formatTime( 28800 ) ?></td>
73
- </tr>
74
- <tr class="bookly-cart-primary">
75
- <th class="bookly-js-option bookly_l10n_label_employee"><?php echo esc_html( get_option( 'bookly_l10n_label_employee' ) ) ?></th>
76
- <td>Nick Knight</td>
77
- </tr>
78
- <tr class="bookly-cart-primary">
79
- <th><?php _e( 'Price', 'bookly' ) ?></th>
80
- <td><?php echo Price::format( 350 ) ?></td>
81
- </tr>
82
- <tr class="bookly-cart-primary">
83
- <th></th>
84
- <td>
85
- <button class="bookly-round" title="<?php esc_attr_e( 'Edit', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-edit"></i></button>
86
- <button class="bookly-round" title="<?php esc_attr_e( 'Remove', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-drop"></i></button>
87
- </td>
88
- </tr>
89
- <tr class="bookly-cart-primary">
90
- <th class="bookly-js-option bookly_l10n_label_service"><?php echo esc_html( get_option( 'bookly_l10n_label_service' ) ) ?></th>
91
- <td>Teeth Whitening</td>
92
- </tr>
93
- <tr class="bookly-cart-primary">
94
- <th><?php _e( 'Date', 'bookly' ) ?></th>
95
- <td><?php echo DateTime::formatDate( '+3 days' ) ?></td>
96
- </tr>
97
- <tr class="bookly-cart-primary">
98
- <th><?php _e( 'Time', 'bookly' ) ?></th>
99
- <td><?php echo DateTime::formatTime( 57600 ) ?></td>
100
- </tr>
101
- <tr class="bookly-cart-primary">
102
- <th class="bookly-js-option bookly_l10n_label_employee"><?php echo esc_html( get_option( 'bookly_l10n_label_employee' ) ) ?></th>
103
- <td>Wayne Turner</td>
104
- </tr>
105
- <tr class="bookly-cart-primary">
106
- <th><?php _e( 'Price', 'bookly' ) ?></th>
107
- <td><?php echo Price::format( 400 ) ?></td>
108
- </tr>
109
- <tr class="bookly-cart-primary">
110
- <th></th>
111
- <td>
112
- <button class="bookly-round" title="<?php esc_attr_e( 'Edit', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-edit"></i></button>
113
- <button class="bookly-round" title="<?php esc_attr_e( 'Remove', 'bookly' ) ?>"><i class="bookly-icon-sm bookly-icon-drop"></i></button>
114
- </td>
115
- </tr>
116
- </tbody>
117
- <tfoot class="bookly-desktop-version">
118
- <tr>
119
- <td colspan="4"><strong><?php _e( 'Total', 'bookly' ) ?>:</strong></td>
120
- <td><strong class="bookly-js-total-price"><?php echo Price::format( 750 ) ?></strong></td>
121
- <td></td>
122
- </tr>
123
- </tfoot>
124
- <tfoot class="bookly-mobile-version">
125
- <tr>
126
- <th><strong><?php _e( 'Total', 'bookly' ) ?>:</strong></th>
127
- <td><strong class="bookly-js-total-price"><?php echo Price::format( 750 ) ?></strong></td>
128
- </tr>
129
- </tfoot>
130
- </table>
131
- </div>
132
- </div>
133
-
134
- <?php \BooklyLite\Lib\Proxy\RecurringAppointments::renderAppearanceEditableInfoMessage() ?>
135
-
136
- <div class="bookly-box bookly-nav-steps">
137
- <div class="bookly-back-step bookly-js-back-step bookly-btn">
138
- <?php $editable::renderString( array( 'bookly_l10n_button_back' ) ) ?>
139
- </div>
140
- <div class="bookly-next-step bookly-js-next-step bookly-btn">
141
- <?php $editable::renderString( array( 'bookly_l10n_step_cart_button_next' ) ) ?>
142
- </div>
143
- </div>
144
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appearance/templates/_6_details.php CHANGED
@@ -1,74 +1,89 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Backend\Modules\Appearance\Components;
3
- /** @var BooklyLite\Backend\Modules\Appearance\Lib\Helper $editable */
 
 
 
4
  ?>
5
  <div class="bookly-form">
6
  <?php include '_progress_tracker.php' ?>
7
 
8
  <div class="bookly-box">
9
- <?php $editable::renderText( 'bookly_l10n_info_details_step', Components::getInstance()->renderCodes( array( 'step' => 6 ), false ) ) ?>
10
  </div>
11
  <div class="bookly-box">
12
- <?php $editable::renderText( 'bookly_l10n_info_details_step_guest', Components::getInstance()->renderCodes( array( 'step' => 6, 'extra_codes' => 1 ), false ), 'bottom', __( 'Visible to non-logged in customers only', 'bookly' ) ) ?>
13
  </div>
14
- <div class="bookly-box" id="bookly-js-show-login-form">
15
- <div class="bookly-btn bookly-inline-block">
16
- <?php $editable::renderString( array( 'bookly_l10n_step_details_button_login' ) ) ?>
17
  </div>
 
18
  </div>
19
  <div class="bookly-details-step">
 
20
  <div class="bookly-box bookly-table bookly-details-first-last-name" style="display: <?php echo get_option( 'bookly_cst_first_last_name' ) == 0 ? ' none' : 'table' ?>">
21
  <div class="bookly-form-group">
22
- <?php $editable::renderLabel( array( 'bookly_l10n_label_first_name', 'bookly_l10n_required_first_name', ) ) ?>
23
  <div>
24
  <input type="text" value="" maxlength="60" />
25
  </div>
26
  </div>
27
  <div class="bookly-form-group">
28
- <?php $editable::renderLabel( array( 'bookly_l10n_label_last_name', 'bookly_l10n_required_last_name', ) ) ?>
29
  <div>
30
  <input type="text" value="" maxlength="60" />
31
  </div>
32
  </div>
33
  </div>
 
34
  <div class="bookly-box bookly-table">
35
  <div class="bookly-form-group bookly-details-full-name" style="display: <?php echo get_option( 'bookly_cst_first_last_name' ) == 1 ? ' none' : 'block' ?>">
36
- <?php $editable::renderLabel( array( 'bookly_l10n_label_name', 'bookly_l10n_required_name', ) ) ?>
37
  <div>
38
  <input type="text" value="" maxlength="60" />
39
  </div>
40
  </div>
41
  <div class="bookly-form-group">
42
- <?php $editable::renderLabel( array( 'bookly_l10n_label_phone', 'bookly_l10n_required_phone', ) ) ?>
43
  <div>
44
  <input type="text" class="<?php if ( get_option( 'bookly_cst_phone_default_country' ) != 'disabled' ) : ?>bookly-user-phone<?php endif ?>" value="" />
45
  </div>
46
  </div>
47
  <div class="bookly-form-group">
48
- <?php $editable::renderLabel( array( 'bookly_l10n_label_email', 'bookly_l10n_required_email', ) ) ?>
49
  <div>
50
  <input maxlength="40" type="text" value="" />
51
  </div>
52
  </div>
53
  </div>
 
 
 
 
 
 
54
  <div class="bookly-box" id="bookly-js-notes">
55
  <div class="bookly-form-group">
56
- <?php $editable::renderLabel( array( 'bookly_l10n_label_notes' ) ) ?>
57
  <div>
58
  <textarea rows="3"></textarea>
59
  </div>
60
  </div>
61
  </div>
 
 
62
  </div>
63
 
64
- <?php \BooklyLite\Lib\Proxy\RecurringAppointments::renderAppearanceEditableInfoMessage() ?>
65
 
66
  <div class="bookly-box bookly-nav-steps">
67
  <div class="bookly-back-step bookly-js-back-step bookly-btn">
68
- <?php $editable::renderString( array( 'bookly_l10n_button_back' ) ) ?>
69
  </div>
70
  <div class="bookly-next-step bookly-js-next-step bookly-btn">
71
- <?php $editable::renderString( array( 'bookly_l10n_step_details_button_next' ) ) ?>
72
  </div>
73
  </div>
74
  </div>
 
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components;
3
+ use Bookly\Backend\Components\Appearance\Codes;
4
+ use Bookly\Backend\Components\Appearance\Editable;
5
+ use Bookly\Backend\Modules\Appearance\Proxy;
6
+ /** @var array $userData */
7
  ?>
8
  <div class="bookly-form">
9
  <?php include '_progress_tracker.php' ?>
10
 
11
  <div class="bookly-box">
12
+ <?php Editable::renderText( 'bookly_l10n_info_details_step', Codes::getHtml( 6 ) ) ?>
13
  </div>
14
  <div class="bookly-box">
15
+ <?php Editable::renderText( 'bookly_l10n_info_details_step_guest', Codes::getHtml( 6, true ), 'bottom', __( 'Visible to non-logged in customers only', 'bookly' ) ) ?>
16
  </div>
17
+ <div class="bookly-box bookly-guest">
18
+ <div class="bookly-btn" id="bookly-login-button">
19
+ <?php Editable::renderString( array( 'bookly_l10n_step_details_button_login' ) ) ?>
20
  </div>
21
+ <div class="fb-login-button" id="bookly-facebook-login-button" data-max-rows="1" data-size="large" data-button-type="login_with" data-show-faces="false" data-auto-logout-link="false" data-use-continue-as="false" data-scope="public_profile,email" style="display:none"></div>
22
  </div>
23
  <div class="bookly-details-step">
24
+
25
  <div class="bookly-box bookly-table bookly-details-first-last-name" style="display: <?php echo get_option( 'bookly_cst_first_last_name' ) == 0 ? ' none' : 'table' ?>">
26
  <div class="bookly-form-group">
27
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_first_name', 'bookly_l10n_required_first_name', ) ) ?>
28
  <div>
29
  <input type="text" value="" maxlength="60" />
30
  </div>
31
  </div>
32
  <div class="bookly-form-group">
33
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_last_name', 'bookly_l10n_required_last_name', ) ) ?>
34
  <div>
35
  <input type="text" value="" maxlength="60" />
36
  </div>
37
  </div>
38
  </div>
39
+
40
  <div class="bookly-box bookly-table">
41
  <div class="bookly-form-group bookly-details-full-name" style="display: <?php echo get_option( 'bookly_cst_first_last_name' ) == 1 ? ' none' : 'block' ?>">
42
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_name', 'bookly_l10n_required_name', ) ) ?>
43
  <div>
44
  <input type="text" value="" maxlength="60" />
45
  </div>
46
  </div>
47
  <div class="bookly-form-group">
48
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_phone', 'bookly_l10n_required_phone', ) ) ?>
49
  <div>
50
  <input type="text" class="<?php if ( get_option( 'bookly_cst_phone_default_country' ) != 'disabled' ) : ?>bookly-user-phone<?php endif ?>" value="" />
51
  </div>
52
  </div>
53
  <div class="bookly-form-group">
54
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_email', 'bookly_l10n_required_email' ) ) ?>
55
  <div>
56
  <input maxlength="40" type="text" value="" />
57
  </div>
58
  </div>
59
  </div>
60
+
61
+ <?php Components\Appearance\Proxy\Pro::renderAddress() ?>
62
+ <?php Components\Appearance\Proxy\Pro::renderBirthday() ?>
63
+ <?php Proxy\CustomerInformation::renderCustomerInformation() ?>
64
+ <?php Proxy\CustomFields::renderCustomFields() ?>
65
+
66
  <div class="bookly-box" id="bookly-js-notes">
67
  <div class="bookly-form-group">
68
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_notes' ) ) ?>
69
  <div>
70
  <textarea rows="3"></textarea>
71
  </div>
72
  </div>
73
  </div>
74
+
75
+ <?php Proxy\Files::renderAppearance() ?>
76
  </div>
77
 
78
+ <?php Proxy\RecurringAppointments::renderInfoMessage() ?>
79
 
80
  <div class="bookly-box bookly-nav-steps">
81
  <div class="bookly-back-step bookly-js-back-step bookly-btn">
82
+ <?php Editable::renderString( array( 'bookly_l10n_button_back' ) ) ?>
83
  </div>
84
  <div class="bookly-next-step bookly-js-next-step bookly-btn">
85
+ <?php Editable::renderString( array( 'bookly_l10n_step_details_button_next' ) ) ?>
86
  </div>
87
  </div>
88
  </div>
89
+ <?php Proxy\Pro::renderFacebookButton() ?>
backend/modules/appearance/templates/_7_payment.php CHANGED
@@ -1,60 +1,50 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Backend\Modules\Appearance\Components;
3
- /** @var BooklyLite\Backend\Modules\Appearance\Lib\Helper $editable */
 
4
  ?>
5
  <div class="bookly-form">
6
  <?php include '_progress_tracker.php' ?>
7
- <?php \BooklyLite\Lib\Proxy\Coupons::renderAppearance() ?>
 
 
8
 
9
  <div class="bookly-payment-nav">
10
  <div class="bookly-box bookly-js-payment-single-app">
11
- <?php $editable::renderText( 'bookly_l10n_info_payment_step_single_app', Components::getInstance()->renderCodes( array( 'step' => 7 ), false ), 'right' ) ?>
12
- </div>
13
- <div class="bookly-box bookly-js-payment-several-apps" style="display:none">
14
- <?php $editable::renderText( 'bookly_l10n_info_payment_step_several_apps', Components::getInstance()->renderCodes( array( 'step' => 7, 'extra_codes' => 1 ), false ), 'right' ) ?>
15
  </div>
16
-
17
  <div class="bookly-box bookly-list">
18
  <label>
19
  <input type="radio" name="payment" checked="checked" />
20
- <?php $editable::renderString( array( 'bookly_l10n_label_pay_locally', ) ) ?>
21
  </label>
22
  </div>
23
 
24
- <div class="bookly-box bookly-list">
25
- <label>
26
- <input type="radio" name="payment" />
27
- <?php $editable::renderString( array( 'bookly_l10n_label_pay_paypal', ) ) ?>
28
- <img src="<?php echo plugins_url( 'frontend/resources/images/paypal.png', \BooklyLite\Lib\Plugin::getMainFile() ) ?>" alt="paypal" />
29
- </label>
30
- </div>
31
 
32
- <div class="bookly-box bookly-list"
33
- <?php if ( BooklyLite\Lib\Proxy\Shared::appearanceRequiredInterfaceCreditCard( false ) == false ) : ?>
34
- style="display: none"
35
- <?php endif ?>
36
- >
37
  <label>
38
  <input type="radio" name="payment" id="bookly-card-payment" />
39
- <?php $editable::renderString( array( 'bookly_l10n_label_pay_ccard', ) ) ?>
40
- <img src="<?php echo plugins_url( 'frontend/resources/images/cards.png', \BooklyLite\Lib\Plugin::getMainFile() ) ?>" alt="cards" />
41
  </label>
42
  <form class="bookly-card-form bookly-clear-bottom" style="margin-top:15px;display: none;">
43
  <?php include '_card_payment.php' ?>
44
  </form>
45
  </div>
46
 
47
- <?php \BooklyLite\Lib\Proxy\Shared::renderAppearancePaymentGatewaySelector() ?>
48
  </div>
49
 
50
- <?php \BooklyLite\Lib\Proxy\RecurringAppointments::renderAppearanceEditableInfoMessage() ?>
51
 
52
  <div class="bookly-box bookly-nav-steps">
53
  <div class="bookly-back-step bookly-js-back-step bookly-btn">
54
- <?php $editable::renderString( array( 'bookly_l10n_button_back' ) ) ?>
55
  </div>
56
  <div class="bookly-next-step bookly-js-next-step bookly-btn">
57
- <?php $editable::renderString( array( 'bookly_l10n_step_payment_button_next' ) ) ?>
58
  </div>
59
  </div>
60
  </div>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Appearance\Codes;
3
+ use Bookly\Backend\Components\Appearance\Editable;
4
+ use Bookly\Backend\Modules\Appearance\Proxy;
5
  ?>
6
  <div class="bookly-form">
7
  <?php include '_progress_tracker.php' ?>
8
+
9
+ <?php Proxy\Coupons::renderCouponBlock() ?>
10
+ <?php Proxy\DepositPayments::renderAppearance() ?>
11
 
12
  <div class="bookly-payment-nav">
13
  <div class="bookly-box bookly-js-payment-single-app">
14
+ <?php Editable::renderText( 'bookly_l10n_info_payment_step_single_app', Codes::getHtml( 7 ), 'right' ) ?>
 
 
 
15
  </div>
16
+ <?php Proxy\Pro::renderMultipleBookingText() ?>
17
  <div class="bookly-box bookly-list">
18
  <label>
19
  <input type="radio" name="payment" checked="checked" />
20
+ <?php Editable::renderString( array( 'bookly_l10n_label_pay_locally', ) ) ?>
21
  </label>
22
  </div>
23
 
24
+ <?php Proxy\Pro::renderPayPalPaymentOption() ?>
 
 
 
 
 
 
25
 
26
+ <div class="bookly-box bookly-list"<?php if ( Proxy\Shared::showCreditCard() == false ): ?> style="display: none"<?php endif ?>>
 
 
 
 
27
  <label>
28
  <input type="radio" name="payment" id="bookly-card-payment" />
29
+ <?php Editable::renderString( array( 'bookly_l10n_label_pay_ccard', ) ) ?>
30
+ <img src="<?php echo plugins_url( 'frontend/resources/images/cards.png', \Bookly\Lib\Plugin::getMainFile() ) ?>" alt="cards" />
31
  </label>
32
  <form class="bookly-card-form bookly-clear-bottom" style="margin-top:15px;display: none;">
33
  <?php include '_card_payment.php' ?>
34
  </form>
35
  </div>
36
 
37
+ <?php Proxy\Shared::renderPaymentGatewaySelector() ?>
38
  </div>
39
 
40
+ <?php Proxy\RecurringAppointments::renderInfoMessage() ?>
41
 
42
  <div class="bookly-box bookly-nav-steps">
43
  <div class="bookly-back-step bookly-js-back-step bookly-btn">
44
+ <?php Editable::renderString( array( 'bookly_l10n_button_back' ) ) ?>
45
  </div>
46
  <div class="bookly-next-step bookly-js-next-step bookly-btn">
47
+ <?php Editable::renderString( array( 'bookly_l10n_step_payment_button_next' ) ) ?>
48
  </div>
49
  </div>
50
  </div>
backend/modules/appearance/templates/_8_complete.php CHANGED
@@ -1,16 +1,16 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Backend\Modules\Appearance\Components;
3
- /** @var BooklyLite\Backend\Modules\Appearance\Lib\Helper $editable */
4
  ?>
5
  <div class="bookly-form">
6
  <?php include '_progress_tracker.php' ?>
7
  <div class="bookly-box bookly-js-done-success">
8
- <?php $editable::renderText( 'bookly_l10n_info_complete_step', Components::getInstance()->renderCodes( array( 'step' => 8, 'extra_codes' => 1 ), false ) ) ?>
9
  </div>
10
  <div class="bookly-box bookly-js-done-limit-error collapse">
11
- <?php $editable::renderText( 'bookly_l10n_info_complete_step_limit_error', Components::getInstance()->renderCodes( array( 'step' => 8 ), false ) ) ?>
12
  </div>
13
  <div class="bookly-box bookly-js-done-processing collapse">
14
- <?php $editable::renderText( 'bookly_l10n_info_complete_step_processing', Components::getInstance()->renderCodes( array( 'step' => 8, 'extra_codes' => 1 ), false ) ) ?>
15
  </div>
16
  </div>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Appearance\Codes;
3
+ use Bookly\Backend\Components\Appearance\Editable;
4
  ?>
5
  <div class="bookly-form">
6
  <?php include '_progress_tracker.php' ?>
7
  <div class="bookly-box bookly-js-done-success">
8
+ <?php Editable::renderText( 'bookly_l10n_info_complete_step', Codes::getHtml( 8, true ) ) ?>
9
  </div>
10
  <div class="bookly-box bookly-js-done-limit-error collapse">
11
+ <?php Editable::renderText( 'bookly_l10n_info_complete_step_limit_error', Codes::getHtml( 8 ) ) ?>
12
  </div>
13
  <div class="bookly-box bookly-js-done-processing collapse">
14
+ <?php Editable::renderText( 'bookly_l10n_info_complete_step_processing', Codes::getHtml( 8, true ) ) ?>
15
  </div>
16
  </div>
backend/modules/appearance/templates/_card_payment.php CHANGED
@@ -1,10 +1,10 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- /** @var BooklyLite\Backend\Modules\Appearance\Lib\Helper $editable */
3
  ?>
4
  <div class="bookly-box bookly-table">
5
  <div class="bookly-form-group" style="width:200px!important">
6
  <label>
7
- <?php $editable::renderString( array( 'bookly_l10n_label_ccard_number', ) ) ?>
8
  </label>
9
  <div>
10
  <input type="text" />
@@ -12,7 +12,7 @@
12
  </div>
13
  <div class="bookly-form-group">
14
  <label>
15
- <?php $editable::renderString( array( 'bookly_l10n_label_ccard_expire', ) ) ?>
16
  </label>
17
  <div>
18
  <select class="bookly-card-exp">
@@ -31,7 +31,7 @@
31
  <div class="bookly-box bookly-clear-bottom">
32
  <div class="bookly-form-group">
33
  <label>
34
- <?php $editable::renderString( array( 'bookly_l10n_label_ccard_code', ) ) ?>
35
  </label>
36
  <div>
37
  <input class="bookly-card-cvc" type="text" />
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Appearance\Editable;
3
  ?>
4
  <div class="bookly-box bookly-table">
5
  <div class="bookly-form-group" style="width:200px!important">
6
  <label>
7
+ <?php Editable::renderString( array( 'bookly_l10n_label_ccard_number', ) ) ?>
8
  </label>
9
  <div>
10
  <input type="text" />
12
  </div>
13
  <div class="bookly-form-group">
14
  <label>
15
+ <?php Editable::renderString( array( 'bookly_l10n_label_ccard_expire', ) ) ?>
16
  </label>
17
  <div>
18
  <select class="bookly-card-exp">
31
  <div class="bookly-box bookly-clear-bottom">
32
  <div class="bookly-form-group">
33
  <label>
34
+ <?php Editable::renderString( array( 'bookly_l10n_label_ccard_code', ) ) ?>
35
  </label>
36
  <div>
37
  <input class="bookly-card-cvc" type="text" />
backend/modules/appearance/templates/_codes.php DELETED
@@ -1,20 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
-
3
- use BooklyLite\Lib;
4
-
5
- $codes = array(
6
- array( 'code' => 'appointments_count', 'description' => __( 'total quantity of appointments in cart', 'bookly' ), 'flags' => array( 'step' => 7, 'extra_codes' => true ) ),
7
- array( 'code' => 'booking_number', 'description' => __( 'booking number', 'bookly' ), 'flags' => array( 'step' => 8, 'extra_codes' => true ) ),
8
- array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) ),
9
- array( 'code' => 'login_form', 'description' => __( 'login form', 'bookly' ), 'flags' => array( 'step' => 6, 'extra_codes' => true ) ),
10
- array( 'code' => 'service_date', 'description' => __( 'date of service', 'bookly' ), 'flags' => array( 'step' => '>3' ) ),
11
- array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) ),
12
- array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) ),
13
- array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ) ),
14
- array( 'code' => 'service_time', 'description' => __( 'time of service', 'bookly' ), 'flags' => array( 'step' => '>3' ) ),
15
- array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) ),
16
- array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) ),
17
- array( 'code' => 'total_price', 'description' => __( 'total price of booking', 'bookly' ) ),
18
- );
19
-
20
- Lib\Utils\Common::codes( Lib\Proxy\Shared::prepareAppearanceCodes( $codes ), array( 'step' => $step, 'extra_codes' => isset ( $extra_codes ) ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appearance/templates/_custom_css.php CHANGED
@@ -1,12 +1,7 @@
1
- <?php
2
- /**
3
- * Template to work with custom css.
4
- * Template includes button to show custom css form + form to edit it
5
- *
6
- * @var string $custom_css custom css text
7
- */
8
  ?>
9
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
10
 
11
  <div class="form-group">
12
  <button type="button" class="btn btn-default" data-toggle="modal" data-target="#bookly-custom-css-dialog">
@@ -28,8 +23,8 @@
28
  </div>
29
  <div class="modal-footer">
30
  <div id="bookly-custom-css-error"></div>
31
- <?php \BooklyLite\Lib\Utils\Common::customButton( 'bookly-custom-css-save', 'btn-success btn-lg', __( 'Save', 'bookly' ) ) ?>
32
- <?php \BooklyLite\Lib\Utils\Common::customButton( 'bookly-custom-css-cancel', 'btn-default btn-lg', __( 'Cancel', 'bookly' ) ) ?>
33
  </div>
34
  </div>
35
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ /** @var string $custom_css custom css text */
 
 
 
 
4
  ?>
 
5
 
6
  <div class="form-group">
7
  <button type="button" class="btn btn-default" data-toggle="modal" data-target="#bookly-custom-css-dialog">
23
  </div>
24
  <div class="modal-footer">
25
  <div id="bookly-custom-css-error"></div>
26
+ <?php Buttons::renderCustom( 'bookly-custom-css-save', 'btn-success btn-lg', __( 'Save', 'bookly' ) ) ?>
27
+ <?php Buttons::renderCustom( 'bookly-custom-css-cancel', 'btn-default btn-lg', __( 'Cancel', 'bookly' ) ) ?>
28
  </div>
29
  </div>
30
  </div>
backend/modules/appearance/templates/_progress_tracker.php CHANGED
@@ -1,42 +1,46 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- /** @var BooklyLite\Backend\Modules\Appearance\Lib\Helper $editable */
3
- $i = 1;
 
 
4
  ?>
5
  <div class="bookly-progress-tracker bookly-table">
6
  <div class="active">
7
- <?php echo $i ++ ?>. <?php $editable::renderString( array( 'bookly_l10n_step_service' ) ) ?>
8
  <div class="step"></div>
9
  </div>
10
- <?php if ( \BooklyLite\Lib\Config::serviceExtrasEnabled() ) : ?>
11
- <div <?php if ( $step >= 2 ) : ?>class="active"<?php endif ?>>
12
- <?php echo $i ++ ?>. <?php $editable::renderString( array( 'bookly_l10n_step_extras' ) ) ?>
13
  <div class="step"></div>
14
  </div>
15
  <?php endif ?>
16
  <div <?php if ( $step >= 3 ) : ?>class="active"<?php endif ?>>
17
- <?php echo $i ++ ?>. <?php $editable::renderString( array( 'bookly_l10n_step_time' ) ) ?>
18
  <div class="step"></div>
19
  </div>
20
- <?php if ( \BooklyLite\Lib\Config::recurringAppointmentsEnabled() ) : ?>
21
- <div <?php if ( $step >= 4 ) : ?>class="active"<?php endif ?>>
22
- <?php echo $i ++ ?>. <?php $editable::renderString( array( 'bookly_l10n_step_repeat' ) ) ?>
23
  <div class=step></div>
24
  </div>
25
  <?php endif ?>
26
- <div <?php if ( $step >= 5 ) : ?>class="active"<?php endif ?>>
27
- <?php echo $i ++ ?>. <?php $editable::renderString( array( 'bookly_l10n_step_cart' ) ) ?>
28
- <div class="step"></div>
29
- </div>
 
 
30
  <div <?php if ( $step >= 6 ) : ?>class="active"<?php endif ?>>
31
- <?php echo $i ++ ?>. <?php $editable::renderString( array( 'bookly_l10n_step_details' ) ) ?>
32
  <div class="step"></div>
33
  </div>
34
  <div <?php if ( $step >= 7 ) : ?>class="active"<?php endif ?>>
35
- <?php echo $i ++ ?>. <?php $editable::renderString( array( 'bookly_l10n_step_payment' ) ) ?>
36
  <div class="step"></div>
37
  </div>
38
  <div <?php if ( $step >= 8 ) : ?>class="active"<?php endif ?>>
39
- <?php echo $i ++ ?>. <?php $editable::renderString( array( 'bookly_l10n_step_done' ) ) ?>
40
  <div class="step"></div>
41
  </div>
42
  </div>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Lib\Config;
3
+ use Bookly\Backend\Components\Appearance\Editable;
4
+
5
+ $i = 1;
6
  ?>
7
  <div class="bookly-progress-tracker bookly-table">
8
  <div class="active">
9
+ <span class="bookly-js-step-number"><?php echo $i ++ ?></span>. <?php Editable::renderString( array( 'bookly_l10n_step_service' ) ) ?>
10
  <div class="step"></div>
11
  </div>
12
+ <?php if ( Config::serviceExtrasActive() ) : ?>
13
+ <div <?php if ( $step >= 2 ) : ?>class="active"<?php endif ?> data-step="bookly-step-2" <?php if ( ! get_option( 'bookly_service_extras_enabled' ) ) : ?>style="display: none;"<?php endif ?>>
14
+ <span class="bookly-js-step-number"><?php echo get_option( 'bookly_service_extras_enabled' ) ? $i ++ : $i ?></span>. <?php Editable::renderString( array( 'bookly_l10n_step_extras' ) ) ?>
15
  <div class="step"></div>
16
  </div>
17
  <?php endif ?>
18
  <div <?php if ( $step >= 3 ) : ?>class="active"<?php endif ?>>
19
+ <span class="bookly-js-step-number"><?php echo $i ++ ?></span>. <?php Editable::renderString( array( 'bookly_l10n_step_time' ) ) ?>
20
  <div class="step"></div>
21
  </div>
22
+ <?php if ( Config::recurringAppointmentsActive() ) : ?>
23
+ <div <?php if ( $step >= 4 ) : ?>class="active"<?php endif ?> data-step="bookly-step-4" <?php if ( ! get_option( 'bookly_recurring_appointments_enabled' ) ) : ?>style="display: none;"<?php endif ?>>
24
+ <span class="bookly-js-step-number"><?php echo get_option( 'bookly_recurring_appointments_enabled' ) ? $i ++ : $i ?></span>. <?php Editable::renderString( array( 'bookly_l10n_step_repeat' ) ) ?>
25
  <div class=step></div>
26
  </div>
27
  <?php endif ?>
28
+ <?php if ( Config::cartActive() ) : ?>
29
+ <div <?php if ( $step >= 5 ) : ?>class="active"<?php endif ?> data-step="bookly-step-5" <?php if ( ! get_option( 'bookly_cart_enabled' ) ) : ?>style="display: none;"<?php endif ?>>
30
+ <span class="bookly-js-step-number"><?php echo get_option( 'bookly_cart_enabled' ) ? $i ++ : $i ?></span>. <?php Editable::renderString( array( 'bookly_l10n_step_cart' ) ) ?>
31
+ <div class="step"></div>
32
+ </div>
33
+ <?php endif ?>
34
  <div <?php if ( $step >= 6 ) : ?>class="active"<?php endif ?>>
35
+ <span class="bookly-js-step-number"><?php echo $i ++ ?></span>. <?php Editable::renderString( array( 'bookly_l10n_step_details' ) ) ?>
36
  <div class="step"></div>
37
  </div>
38
  <div <?php if ( $step >= 7 ) : ?>class="active"<?php endif ?>>
39
+ <span class="bookly-js-step-number"><?php echo $i ++ ?></span>. <?php Editable::renderString( array( 'bookly_l10n_step_payment' ) ) ?>
40
  <div class="step"></div>
41
  </div>
42
  <div <?php if ( $step >= 8 ) : ?>class="active"<?php endif ?>>
43
+ <span class="bookly-js-step-number"><?php echo $i ++ ?></span>. <?php Editable::renderString( array( 'bookly_l10n_step_done' ) ) ?>
44
  <div class="step"></div>
45
  </div>
46
  </div>
backend/modules/appearance/templates/index.php CHANGED
@@ -1,11 +1,20 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- /**
3
- * Template to show appearance page
4
- * @var array $steps list of steps in booking form, could be string (the name of step) or false if step disabled
5
- * @var string $custom_css custom css text
6
- */
7
  ?>
8
-
 
 
 
 
 
 
 
 
 
 
9
  <?php if ( trim( $custom_css ) ) : ?>
10
  <style type="text/css">
11
  <?php echo $custom_css ?>
@@ -18,7 +27,7 @@
18
  <div class="bookly-page-title">
19
  <?php _e( 'Appearance', 'bookly' ) ?>
20
  </div>
21
- <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
22
  </div>
23
  <div class="panel panel-default bookly-main">
24
  <div class="panel-body">
@@ -29,7 +38,7 @@
29
  value="<?php form_option( 'bookly_app_color' ) ?>"
30
  data-selected="<?php form_option( 'bookly_app_color' ) ?>" />
31
  </div>
32
- <div class="col-sm-9 col-lg-10">
33
  <div class="checkbox">
34
  <label>
35
  <input type="checkbox" id=bookly-show-progress-tracker <?php checked( get_option( 'bookly_app_show_progress_tracker' ) ) ?>>
@@ -37,30 +46,33 @@
37
  </label>
38
  </div>
39
  </div>
 
 
 
40
  </div>
41
 
42
- <ul class="bookly-nav bookly-nav-tabs bookly-margin-top-lg" role="tablist">
43
  <?php $i = 1 ?>
44
- <?php foreach ( $steps as $step => $step_name ) : ?>
45
- <?php if ( ( $step != 2 || \BooklyLite\Lib\Config::serviceExtrasEnabled() )
46
- && ( $step != 4 || \BooklyLite\Lib\Config::recurringAppointmentsEnabled() ) ) : ?>
47
- <li class="bookly-nav-item <?php if ( $step == 1 ) : ?>active<?php endif ?>" data-target="#bookly-step-<?php echo $step ?>" data-toggle="tab">
48
- <?php echo $i++ ?>. <?php echo esc_html( $step_name ) ?>
49
- </li>
50
- <?php endif ?>
51
  <?php endforeach ?>
52
  </ul>
53
 
54
- <?php if ( ! get_user_meta( get_current_user_id(), \BooklyLite\Lib\Plugin::getPrefix() . 'dismiss_appearance_notice', true ) ): ?>
55
  <div class="alert alert-info alert-dismissible fade in bookly-margin-top-lg bookly-margin-bottom-remove" id="bookly-js-hint-alert" role="alert">
56
- <button type="button" class="close" data-dismiss="alert"></button>
57
  <?php _e( 'Click on the underlined text to edit.', 'bookly' ) ?>
58
  </div>
59
  <?php endif ?>
60
 
61
  <div class="row" id="bookly-step-settings">
62
  <div class="bookly-js-service-settings bookly-margin-top-lg">
63
- <?php \BooklyLite\Lib\Proxy\Shared::renderAppearanceStepServiceSettings() ?>
 
 
 
64
  <div class="col-md-3">
65
  <div class="checkbox">
66
  <label>
@@ -85,9 +97,10 @@
85
  </label>
86
  </div>
87
  </div>
 
88
  </div>
89
  <div class="bookly-js-time-settings bookly-margin-top-lg" style="display:none">
90
- <div class="col-md-4">
91
  <div class="checkbox">
92
  <label>
93
  <input type="checkbox" id="bookly-show-calendar" <?php checked( get_option( 'bookly_app_show_calendar' ) ) ?>>
@@ -95,7 +108,7 @@
95
  </label>
96
  </div>
97
  </div>
98
- <div class="col-md-4">
99
  <div class="checkbox">
100
  <label>
101
  <input type="checkbox" id="bookly-show-blocked-timeslots" <?php checked( get_option( 'bookly_app_show_blocked_timeslots' ) ) ?>>
@@ -103,7 +116,7 @@
103
  </label>
104
  </div>
105
  </div>
106
- <div class="col-md-4">
107
  <div class="checkbox">
108
  <label>
109
  <input type="checkbox" id="bookly-show-day-one-column" <?php checked( get_option( 'bookly_app_show_day_one_column' ) ) ?>>
@@ -111,15 +124,20 @@
111
  </label>
112
  </div>
113
  </div>
 
 
114
  </div>
115
- <div class="bookly-js-details-settings bookly-margin-top-lg" style="display:none">
 
 
 
 
116
  <div class="col-md-3">
117
- <div class="checkbox">
118
- <label>
119
- <input type="checkbox" id="bookly-cst-required-phone" <?php checked( get_option( 'bookly_cst_required_phone' ) ) ?>>
120
- <?php _e( 'Make phone field required', 'bookly' ) ?>
121
- </label>
122
- </div>
123
  </div>
124
  <div class="col-md-3">
125
  <div class="checkbox">
@@ -145,27 +163,19 @@
145
  </label>
146
  </div>
147
  </div>
 
 
 
 
 
 
 
148
  </div>
149
  <div class="bookly-js-payment-settings bookly-margin-top-lg" style="display:none">
150
- <div class="col-md-12">
151
- <div class="alert alert-info bookly-margin-top-lg bookly-margin-bottom-remove bookly-flexbox">
152
- <div class="bookly-flex-row">
153
- <div class="bookly-flex-cell" style="width:39px"><i class="alert-icon"></i></div>
154
- <div class="bookly-flex-cell">
155
- <div>
156
- <?php _e( 'The booking form on this step may have different set or states of its elements. It depends on various conditions such as installed/activated add-ons, settings configuration or choices made on previous steps. Select option and click on the underlined text to edit.', 'bookly' ) ?>
157
- </div>
158
- <div class="bookly-margin-top-lg">
159
- <select id="bookly-payment-step-view" class="form-control">
160
- <option value="single-app"><?php _e( 'Form view in case of single booking', 'bookly' ) ?></option>
161
- <option value="several-apps"><?php _e( 'Form view in case of multiple booking', 'bookly' ) ?></option>
162
- </select>
163
- </div>
164
- </div>
165
- </div>
166
- </div>
167
- </div>
168
  </div>
 
169
  <div class="bookly-js-done-settings bookly-margin-top-lg" style="display:none">
170
  <div class="col-md-12">
171
  <div class="alert alert-info bookly-margin-top-lg bookly-margin-bottom-remove bookly-flexbox">
@@ -197,12 +207,13 @@
197
  <?php // Render unique data per step
198
  switch ( $step ) :
199
  case 1: include '_1_service.php'; break;
200
- case 2: \BooklyLite\Lib\Proxy\ServiceExtras::renderAppearance( $this->render( '_progress_tracker', compact( 'step', 'editable' ), false ) );
201
  break;
202
  case 3: include '_3_time.php'; break;
203
- case 4: \BooklyLite\Lib\Proxy\RecurringAppointments::renderAppearance( $this->render( '_progress_tracker', compact( 'step', 'editable' ), false ) );
 
 
204
  break;
205
- case 5: include '_5_cart.php'; break;
206
  case 6: include '_6_details.php'; break;
207
  case 7: include '_7_payment.php'; break;
208
  case 8: include '_8_complete.php'; break;
@@ -213,13 +224,13 @@
213
  </div>
214
  </div>
215
  <div>
216
- <?php $this->render( '_custom_css', array( 'custom_css' => $custom_css) ); ?>
217
  </div>
218
  </div>
219
  </div>
220
  <div class="panel-footer">
221
- <?php \BooklyLite\Lib\Utils\Common::submitButton( 'ajax-send-appearance' ) ?>
222
- <?php \BooklyLite\Lib\Utils\Common::resetButton() ?>
223
  </div>
224
  </div>
225
  </div>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Lib;
3
+ use Bookly\Backend\Components\Support;
4
+ use Bookly\Backend\Components\Controls\Buttons;
5
+ use Bookly\Backend\Modules\Appearance\Proxy;
 
6
  ?>
7
+ <style type="text/css">
8
+ .bookly-columnizer .bookly-hour.bookly-slot-in-waiting-list span.bookly-time-additional {
9
+ color: #f4662f!important;
10
+ }
11
+ #bookly-step-settings > div > .col-md-3:nth-child(4n+1) {
12
+ clear: both;
13
+ }
14
+ #bookly-tbs .bookly-cart .bookly-extras-cart-title {
15
+ padding-left: 25px;
16
+ }
17
+ </style>
18
  <?php if ( trim( $custom_css ) ) : ?>
19
  <style type="text/css">
20
  <?php echo $custom_css ?>
27
  <div class="bookly-page-title">
28
  <?php _e( 'Appearance', 'bookly' ) ?>
29
  </div>
30
+ <?php Support\Buttons::render( $self::pageSlug() ) ?>
31
  </div>
32
  <div class="panel panel-default bookly-main">
33
  <div class="panel-body">
38
  value="<?php form_option( 'bookly_app_color' ) ?>"
39
  data-selected="<?php form_option( 'bookly_app_color' ) ?>" />
40
  </div>
41
+ <div class="col-sm-3 col-lg-2">
42
  <div class="checkbox">
43
  <label>
44
  <input type="checkbox" id=bookly-show-progress-tracker <?php checked( get_option( 'bookly_app_show_progress_tracker' ) ) ?>>
46
  </label>
47
  </div>
48
  </div>
49
+ <?php Proxy\ServiceExtras::renderShowStep() ?>
50
+ <?php Proxy\RecurringAppointments::renderShowStep() ?>
51
+ <?php Proxy\Cart::renderShowStep() ?>
52
  </div>
53
 
54
+ <ul class="bookly-nav bookly-nav-tabs bookly-margin-top-lg bookly-js-appearance-steps" role="tablist">
55
  <?php $i = 1 ?>
56
+ <?php foreach ( $steps as $step => $step_data ) : ?>
57
+ <li class="bookly-nav-item <?php if ( $step == 1 ) : ?>active<?php endif ?>" data-target="#bookly-step-<?php echo $step ?>" data-toggle="tab" <?php if ( ! $step_data['show'] ) : ?>style="display: none;"<?php endif ?>>
58
+ <span class="bookly-js-step-number"><?php echo $step_data['show'] ? $i++ : $i ?></span>. <?php echo esc_html( $step_data['title'] ) ?>
59
+ </li>
 
 
 
60
  <?php endforeach ?>
61
  </ul>
62
 
63
+ <?php if ( ! get_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_appearance_notice', true ) ): ?>
64
  <div class="alert alert-info alert-dismissible fade in bookly-margin-top-lg bookly-margin-bottom-remove" id="bookly-js-hint-alert" role="alert">
65
+ <button type="button" class="close" data-dismiss="alert">&times;</button>
66
  <?php _e( 'Click on the underlined text to edit.', 'bookly' ) ?>
67
  </div>
68
  <?php endif ?>
69
 
70
  <div class="row" id="bookly-step-settings">
71
  <div class="bookly-js-service-settings bookly-margin-top-lg">
72
+ <?php Proxy\Locations::renderShowLocation() ?>
73
+ <?php Proxy\CustomDuration::renderShowCustomDuration() ?>
74
+ <?php Proxy\GroupBooking::renderShowNOP() ?>
75
+ <?php Proxy\MultiplyAppointments::renderShowQuantity() ?>
76
  <div class="col-md-3">
77
  <div class="checkbox">
78
  <label>
97
  </label>
98
  </div>
99
  </div>
100
+ <?php Proxy\Shared::renderServiceStepSettings() ?>
101
  </div>
102
  <div class="bookly-js-time-settings bookly-margin-top-lg" style="display:none">
103
+ <div class="col-md-3">
104
  <div class="checkbox">
105
  <label>
106
  <input type="checkbox" id="bookly-show-calendar" <?php checked( get_option( 'bookly_app_show_calendar' ) ) ?>>
108
  </label>
109
  </div>
110
  </div>
111
+ <div class="col-md-3">
112
  <div class="checkbox">
113
  <label>
114
  <input type="checkbox" id="bookly-show-blocked-timeslots" <?php checked( get_option( 'bookly_app_show_blocked_timeslots' ) ) ?>>
116
  </label>
117
  </div>
118
  </div>
119
+ <div class="col-md-3">
120
  <div class="checkbox">
121
  <label>
122
  <input type="checkbox" id="bookly-show-day-one-column" <?php checked( get_option( 'bookly_app_show_day_one_column' ) ) ?>>
124
  </label>
125
  </div>
126
  </div>
127
+ <?php Proxy\Pro::renderTimeZoneSwitcherCheckbox() ?>
128
+ <?php Proxy\Shared::renderTimeStepSettings() ?>
129
  </div>
130
+
131
+ <?php Proxy\Cart::renderCartStepSettings() ?>
132
+
133
+ <div class="bookly-js-details-settings bookly-margin-top-lg container-fluid" style="display:none">
134
+
135
  <div class="col-md-3">
136
+ <select id="bookly-cst-required-details" class="form-control" data-default="<?php echo ! array_diff( array( 'phone', 'email' ), get_option( 'bookly_cst_required_details', array() ) ) ? 'both' : current( get_option( 'bookly_cst_required_details', array() ) ) ?>">
137
+ <option value="phone"<?php selected( in_array( 'phone', get_option( 'bookly_cst_required_details', array() ) ) && ! in_array( 'email', get_option( 'bookly_cst_required_details', array() ) ) ) ?><?php disabled( get_option( 'bookly_cst_create_account' ) ) ?>><?php _e( 'Phone field required', 'bookly' ) ?></option>
138
+ <option value="email"<?php selected( in_array( 'email', get_option( 'bookly_cst_required_details', array() ) ) && ! in_array( 'phone', get_option( 'bookly_cst_required_details', array() ) ) ) ?>><?php _e( 'Email field required', 'bookly' ) ?></option>
139
+ <option value="both"<?php selected( ! array_diff( array( 'phone', 'email' ), get_option( 'bookly_cst_required_details', array() ) ) ) ?>><?php _e( 'Both email and phone fields required', 'bookly' ) ?></option>
140
+ </select>
 
141
  </div>
142
  <div class="col-md-3">
143
  <div class="checkbox">
163
  </label>
164
  </div>
165
  </div>
166
+ <?php Proxy\Pro::renderShowBirthday() ?>
167
+ <?php Proxy\Pro::renderShowAddress() ?>
168
+ <?php Proxy\GoogleMapsAddress::renderShowGoogleMaps() ?>
169
+ <?php Proxy\CustomFields::renderShowCustomFields() ?>
170
+ <?php Proxy\Files::renderShowFiles() ?>
171
+ <?php Proxy\CustomerInformation::renderShowCustomerInformation() ?>
172
+ <?php Proxy\Pro::renderShowFacebookButton() ?>
173
  </div>
174
  <div class="bookly-js-payment-settings bookly-margin-top-lg" style="display:none">
175
+ <?php Proxy\Coupons::renderShowCoupons() ?>
176
+ <?php Proxy\Pro::renderMultipleBookingSelector() ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  </div>
178
+
179
  <div class="bookly-js-done-settings bookly-margin-top-lg" style="display:none">
180
  <div class="col-md-12">
181
  <div class="alert alert-info bookly-margin-top-lg bookly-margin-bottom-remove bookly-flexbox">
207
  <?php // Render unique data per step
208
  switch ( $step ) :
209
  case 1: include '_1_service.php'; break;
210
+ case 2: Proxy\ServiceExtras::renderStep( $self::renderTemplate( '_progress_tracker', compact( 'step', 'editable' ), false ) );
211
  break;
212
  case 3: include '_3_time.php'; break;
213
+ case 4: Proxy\RecurringAppointments::renderStep( $self::renderTemplate( '_progress_tracker', compact( 'step', 'editable' ), false ) );
214
+ break;
215
+ case 5: Proxy\Cart::renderStep( $self::renderTemplate( '_progress_tracker', compact( 'step', 'editable' ), false ) );
216
  break;
 
217
  case 6: include '_6_details.php'; break;
218
  case 7: include '_7_payment.php'; break;
219
  case 8: include '_8_complete.php'; break;
224
  </div>
225
  </div>
226
  <div>
227
+ <?php $self::renderTemplate( '_custom_css', array( 'custom_css' => $custom_css ) ) ?>
228
  </div>
229
  </div>
230
  </div>
231
  <div class="panel-footer">
232
+ <?php Buttons::renderSubmit( 'ajax-send-appearance' ) ?>
233
+ <?php Buttons::renderReset() ?>
234
  </div>
235
  </div>
236
  </div>
backend/modules/appointments/Ajax.php ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appointments;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Lib\DataHolders\Booking as DataHolders;
6
+
7
+ /**
8
+ * Class Ajax
9
+ * @package Bookly\Backend\Modules\Appointments
10
+ */
11
+ class Ajax extends Lib\Base\Ajax
12
+ {
13
+ /**
14
+ * Get list of appointments.
15
+ */
16
+ public static function getAppointments()
17
+ {
18
+ $columns = self::parameter( 'columns' );
19
+ $order = self::parameter( 'order' );
20
+ $filter = self::parameter( 'filter' );
21
+ $postfix_any = sprintf( ' (%s)', get_option( 'bookly_l10n_option_employee' ) );
22
+
23
+ $query = Lib\Entities\CustomerAppointment::query( 'ca' )
24
+ ->select( 'a.id,
25
+ ca.payment_id,
26
+ ca.status,
27
+ ca.id AS ca_id,
28
+ ca.notes,
29
+ ca.number_of_persons,
30
+ ca.extras,
31
+ ca.rating,
32
+ ca.rating_comment,
33
+ a.start_date,
34
+ a.staff_any,
35
+ c.full_name AS customer_full_name,
36
+ c.phone AS customer_phone,
37
+ c.email AS customer_email,
38
+ st.full_name AS staff_name,
39
+ p.paid AS payment,
40
+ p.total AS payment_total,
41
+ p.type AS payment_type,
42
+ p.status AS payment_status,
43
+ COALESCE(s.title, a.custom_service_name) AS service_title,
44
+ TIME_TO_SEC(TIMEDIFF(a.end_date, a.start_date)) + a.extras_duration AS service_duration' )
45
+ ->leftJoin( 'Appointment', 'a', 'a.id = ca.appointment_id' )
46
+ ->leftJoin( 'Service', 's', 's.id = a.service_id' )
47
+ ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
48
+ ->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
49
+ ->leftJoin( 'Staff', 'st', 'st.id = a.staff_id' )
50
+ ->leftJoin( 'StaffService', 'ss', 'ss.staff_id = st.id AND ss.service_id = s.id AND ss.location_id = a.location_id' );
51
+
52
+ $total = $query->count();
53
+
54
+ $sub_query = Lib\Proxy\Files::getSubQueryAttachmentExists();
55
+ if ( ! $sub_query ) {
56
+ $sub_query = '0';
57
+ }
58
+ $query->addSelect( '(' . $sub_query . ') AS attachment' );
59
+
60
+ if ( $filter['id'] != '' ) {
61
+ $query->where( 'a.id', $filter['id'] );
62
+ }
63
+
64
+ if ( $filter['date'] ) {
65
+ list ( $start, $end ) = explode( ' - ', $filter['date'], 2 );
66
+ $end = date( 'Y-m-d', strtotime( $end ) + DAY_IN_SECONDS );
67
+ $query->whereBetween( 'a.start_date', $start, $end );
68
+ } else {
69
+ $query->where( 'a.start_date', null );
70
+ }
71
+
72
+ if ( $filter['staff'] != '' ) {
73
+ $query->where( 'a.staff_id', $filter['staff'] );
74
+ }
75
+
76
+ if ( $filter['customer'] != '' ) {
77
+ $query->where( 'ca.customer_id', $filter['customer'] );
78
+ }
79
+
80
+ if ( $filter['service'] != '' ) {
81
+ $query->where( 'a.service_id', $filter['service'] ?: null );
82
+ }
83
+
84
+ if ( $filter['status'] != '' ) {
85
+ $query->where( 'ca.status', $filter['status'] );
86
+ }
87
+
88
+ foreach ( $order as $sort_by ) {
89
+ $query->sortBy( str_replace( '.', '_', $columns[ $sort_by['column'] ]['data'] ) )
90
+ ->order( $sort_by['dir'] == 'desc' ? Lib\Query::ORDER_DESCENDING : Lib\Query::ORDER_ASCENDING );
91
+ }
92
+
93
+ $custom_fields = array();
94
+ $fields_data = (array) Lib\Proxy\CustomFields::getWhichHaveData();
95
+ foreach ( $fields_data as $field_data ) {
96
+ $custom_fields[ $field_data->id ] = '';
97
+ }
98
+
99
+ $data = array();
100
+ foreach ( $query->fetchArray() as $row ) {
101
+ // Service duration.
102
+ $service_duration = Lib\Utils\DateTime::secondsToInterval( $row['service_duration'] );
103
+ // Appointment status.
104
+ $row['status'] = Lib\Entities\CustomerAppointment::statusToString( $row['status'] );
105
+ // Payment title.
106
+ $payment_title = '';
107
+ if ( $row['payment'] !== null ) {
108
+ $payment_title = Lib\Utils\Price::format( $row['payment'] );
109
+ if ( $row['payment'] != $row['payment_total'] ) {
110
+ $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Price::format( $row['payment_total'] ) );
111
+ }
112
+ $payment_title .= sprintf(
113
+ ' %s <span%s>%s</span>',
114
+ Lib\Entities\Payment::typeToString( $row['payment_type'] ),
115
+ $row['payment_status'] == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
116
+ Lib\Entities\Payment::statusToString( $row['payment_status'] )
117
+ );
118
+ }
119
+ // Custom fields
120
+ $customer_appointment = new Lib\Entities\CustomerAppointment();
121
+ $customer_appointment->load( $row['ca_id'] );
122
+ foreach ( (array) Lib\Proxy\CustomFields::getForCustomerAppointment( $customer_appointment ) as $custom_field ) {
123
+ $custom_fields[ $custom_field['id'] ] = $custom_field['value'];
124
+ }
125
+
126
+ $data[] = array(
127
+ 'id' => $row['id'],
128
+ 'start_date' => $row['start_date'] === null ? __( 'N/A', 'bookly' ) : Lib\Utils\DateTime::formatDateTime( $row['start_date'] ),
129
+ 'staff' => array(
130
+ 'name' => $row['staff_name'] . ( $row['staff_any'] ? $postfix_any : '' ),
131
+ ),
132
+ 'customer' => array(
133
+ 'full_name' => $row['customer_full_name'],
134
+ 'phone' => $row['customer_phone'],
135
+ 'email' => $row['customer_email'],
136
+ ),
137
+ 'service' => array(
138
+ 'title' => $row['service_title'],
139
+ 'duration' => $service_duration,
140
+ 'extras' => (array) Lib\Proxy\ServiceExtras::getInfo( json_decode( $row['extras'], true ), false ),
141
+ ),
142
+ 'status' => $row['status'],
143
+ 'payment' => $payment_title,
144
+ 'notes' => $row['notes'],
145
+ 'number_of_persons' => $row['number_of_persons'],
146
+ 'rating' => $row['rating'],
147
+ 'rating_comment' => $row['rating_comment'],
148
+ 'custom_fields' => $custom_fields,
149
+ 'ca_id' => $row['ca_id'],
150
+ 'attachment' => $row['attachment'],
151
+ 'payment_id' => $row['payment_id'],
152
+ );
153
+
154
+ $custom_fields = array_map( function () { return ''; }, $custom_fields );
155
+ }
156
+
157
+ unset( $filter['date'] );
158
+ update_user_meta( get_current_user_id(), 'bookly_filter_appointments_list', $filter );
159
+
160
+ wp_send_json( array(
161
+ 'draw' => (int) self::parameter( 'draw' ),
162
+ 'recordsTotal' => $total,
163
+ 'recordsFiltered' => count( $data ),
164
+ 'data' => $data,
165
+ ) );
166
+ }
167
+
168
+ /**
169
+ * Delete customer appointments.
170
+ */
171
+ public static function deleteCustomerAppointments()
172
+ {
173
+ /** @var Lib\Entities\CustomerAppointment $ca */
174
+ foreach ( Lib\Entities\CustomerAppointment::query()->whereIn( 'id', self::parameter( 'data', array() ) )->find() as $ca ) {
175
+ if ( self::parameter( 'notify' ) ) {
176
+ switch ( $ca->getStatus() ) {
177
+ case Lib\Entities\CustomerAppointment::STATUS_PENDING:
178
+ case Lib\Entities\CustomerAppointment::STATUS_WAITLISTED:
179
+ $ca->setStatus( Lib\Entities\CustomerAppointment::STATUS_REJECTED );
180
+ break;
181
+ case Lib\Entities\CustomerAppointment::STATUS_APPROVED:
182
+ $ca->setStatus( Lib\Entities\CustomerAppointment::STATUS_CANCELLED );
183
+ break;
184
+ }
185
+ Lib\Notifications\Sender::sendSingle(
186
+ DataHolders\Simple::create( $ca ),
187
+ null,
188
+ array( 'cancellation_reason' => self::parameter( 'reason' ) )
189
+ );
190
+ }
191
+ $ca->deleteCascade();
192
+ }
193
+ wp_send_json_success();
194
+ }
195
+ }
backend/modules/appointments/Controller.php DELETED
@@ -1,253 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Appointments;
3
-
4
- use BooklyLite\Lib;
5
- use BooklyLite\Lib\DataHolders\Booking as DataHolders;
6
-
7
- /**
8
- * Class Controller
9
- * @package BooklyLite\Backend\Modules\Appointments
10
- */
11
- class Controller extends Lib\Base\Controller
12
- {
13
- const page_slug = 'bookly-appointments';
14
-
15
- public function index()
16
- {
17
- /** @var \WP_Locale $wp_locale */
18
- global $wp_locale;
19
-
20
- $this->enqueueStyles( array(
21
- 'frontend' => array( 'css/ladda.min.css', ),
22
- 'backend' => array(
23
- 'css/select2.min.css',
24
- 'bootstrap/css/bootstrap-theme.min.css',
25
- 'css/daterangepicker.css',
26
- ),
27
- ) );
28
-
29
- $this->enqueueScripts( array(
30
- 'backend' => array(
31
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
32
- 'js/datatables.min.js' => array( 'jquery' ),
33
- 'js/moment.min.js',
34
- 'js/daterangepicker.js' => array( 'jquery' ),
35
- 'js/select2.full.min.js' => array( 'jquery' ),
36
- ),
37
- 'frontend' => array(
38
- 'js/spin.min.js' => array( 'jquery' ),
39
- 'js/ladda.min.js' => array( 'jquery' ),
40
- ),
41
- 'module' => array( 'js/appointments.js' => array( 'bookly-datatables.min.js' ), ),
42
- ) );
43
-
44
- // Custom fields without captcha & text content field.
45
- $custom_fields = (array) Lib\Proxy\CustomFields::getWhichHaveData();
46
-
47
- wp_localize_script( 'bookly-appointments.js', 'BooklyL10n', array(
48
- 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
49
- 'tomorrow' => __( 'Tomorrow', 'bookly' ),
50
- 'today' => __( 'Today', 'bookly' ),
51
- 'yesterday' => __( 'Yesterday', 'bookly' ),
52
- 'last_7' => __( 'Last 7 Days', 'bookly' ),
53
- 'last_30' => __( 'Last 30 Days', 'bookly' ),
54
- 'this_month' => __( 'This Month', 'bookly' ),
55
- 'next_month' => __( 'Next Month', 'bookly' ),
56
- 'custom_range' => __( 'Custom Range', 'bookly' ),
57
- 'apply' => __( 'Apply', 'bookly' ),
58
- 'cancel' => __( 'Cancel', 'bookly' ),
59
- 'to' => __( 'To', 'bookly' ),
60
- 'from' => __( 'From', 'bookly' ),
61
- 'calendar' => array(
62
- 'longMonths' => array_values( $wp_locale->month ),
63
- 'shortMonths' => array_values( $wp_locale->month_abbrev ),
64
- 'longDays' => array_values( $wp_locale->weekday ),
65
- 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
66
- ),
67
- 'mjsDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
68
- 'startOfWeek' => (int) get_option( 'start_of_week' ),
69
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
70
- 'zeroRecords' => __( 'No appointments for selected period.', 'bookly' ),
71
- 'processing' => __( 'Processing...', 'bookly' ),
72
- 'edit' => __( 'Edit', 'bookly' ),
73
- 'show_notes' => Lib\Config::showNotes(),
74
- 'cf_columns' => array_map( function ( $custom_field ) { return $custom_field->id; }, $custom_fields ),
75
- 'filter' => (array) get_user_meta( get_current_user_id(), 'bookly_filter_appointments_list', true ),
76
- 'no_result_found' => __( 'No result found', 'bookly' ),
77
- 'limitations' => __( '<b class="h4">This function is not available in the Lite version of Bookly.</b><br><br>To get access to all Bookly features, lifetime free updates and 24/7 support, please upgrade to the Standard version of Bookly.<br>For more information visit', 'bookly' ) . ' <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
78
- ) );
79
-
80
- // Filters data
81
- $staff_members = Lib\Entities\Staff::query( 's' )->select( 's.id, s.full_name' )->where( 'id', 1 )->fetchArray();
82
- $customers = Lib\Entities\Customer::query( 'c' )->select( 'c.id, c.full_name, c.first_name, c.last_name' )->fetchArray();
83
- $services = Lib\Entities\Service::query( 's' )->select( 's.id, s.title' )->where( 'type', Lib\Entities\Service::TYPE_SIMPLE )->fetchArray();
84
-
85
- $this->render( 'index', compact( 'custom_fields', 'staff_members', 'customers', 'services' ) );
86
- }
87
-
88
- /**
89
- * Get list of appointments.
90
- */
91
- public function executeGetAppointments()
92
- {
93
- $columns = $this->getParameter( 'columns' );
94
- $order = $this->getParameter( 'order' );
95
- $filter = $this->getParameter( 'filter' );
96
- $postfix_any = sprintf( ' (%s)', get_option( 'bookly_l10n_option_employee' ) );
97
-
98
- $query = Lib\Entities\CustomerAppointment::query( 'ca' )
99
- ->select( 'a.id,
100
- ca.payment_id,
101
- ca.status,
102
- ca.id AS ca_id,
103
- ca.notes,
104
- ca.extras,
105
- a.start_date,
106
- a.staff_any,
107
- c.full_name AS customer_full_name,
108
- c.phone AS customer_phone,
109
- c.email AS customer_email,
110
- st.full_name AS staff_name,
111
- p.paid AS payment,
112
- p.total AS payment_total,
113
- p.type AS payment_type,
114
- p.status AS payment_status,
115
- COALESCE(s.title, a.custom_service_name) AS service_title,
116
- TIME_TO_SEC(TIMEDIFF(a.end_date, a.start_date)) + a.extras_duration AS service_duration' )
117
- ->leftJoin( 'Appointment', 'a', 'a.id = ca.appointment_id' )
118
- ->leftJoin( 'Service', 's', 's.id = a.service_id' )
119
- ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
120
- ->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
121
- ->leftJoin( 'Staff', 'st', 'st.id = a.staff_id' )
122
- ->leftJoin( 'StaffService', 'ss', 'ss.staff_id = st.id AND ss.service_id = s.id' );
123
-
124
- $total = $query->count();
125
-
126
- if ( $filter['id'] != '' ) {
127
- $query->where( 'a.id', $filter['id'] );
128
- }
129
-
130
- list ( $start, $end ) = explode( ' - ', $filter['date'], 2 );
131
- $end = date( 'Y-m-d', strtotime( '+1 day', strtotime( $end ) ) );
132
- $query->whereBetween( 'a.start_date', $start, $end );
133
-
134
- if ( $filter['staff'] != '' ) {
135
- $query->where( 'a.staff_id', $filter['staff'] );
136
- }
137
-
138
- if ( $filter['customer'] != '' ) {
139
- $query->where( 'ca.customer_id', $filter['customer'] );
140
- }
141
-
142
- if ( $filter['service'] != '' ) {
143
- $query->where( 'a.service_id', $filter['service'] ?: null );
144
- }
145
-
146
- if ( $filter['status'] != '' ) {
147
- $query->where( 'ca.status', $filter['status'] );
148
- }
149
-
150
- foreach ( $order as $sort_by ) {
151
- $query->sortBy( str_replace( '.', '_', $columns[ $sort_by['column'] ]['data'] ) )
152
- ->order( $sort_by['dir'] == 'desc' ? Lib\Query::ORDER_DESCENDING : Lib\Query::ORDER_ASCENDING );
153
- }
154
-
155
- $custom_fields = array();
156
- $fields_data = (array) Lib\Proxy\CustomFields::getWhichHaveData();
157
- foreach ( $fields_data as $field_data ) {
158
- $custom_fields[ $field_data->id ] = '';
159
- }
160
-
161
- $data = array();
162
- foreach ( $query->fetchArray() as $row ) {
163
- // Service duration.
164
- $service_duration = Lib\Utils\DateTime::secondsToInterval( $row['service_duration'] );
165
- // Appointment status.
166
- $row['status'] = Lib\Entities\CustomerAppointment::statusToString( $row['status'] );
167
- // Payment title.
168
- $payment_title = '';
169
- if ( $row['payment'] !== null ) {
170
- $payment_title = Lib\Utils\Price::format( $row['payment'] );
171
- if ( $row['payment'] != $row['payment_total'] ) {
172
- $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Price::format( $row['payment_total'] ) );
173
- }
174
- $payment_title .= sprintf(
175
- ' %s <span%s>%s</span>',
176
- Lib\Entities\Payment::typeToString( $row['payment_type'] ),
177
- $row['payment_status'] == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
178
- Lib\Entities\Payment::statusToString( $row['payment_status'] )
179
- );
180
- }
181
- // Custom fields
182
- $customer_appointment = new Lib\Entities\CustomerAppointment();
183
- $customer_appointment->load( $row['ca_id'] );
184
- foreach ( (array) Lib\Proxy\CustomFields::getForCustomerAppointment( $customer_appointment ) as $custom_field ) {
185
- $custom_fields[ $custom_field['id'] ] = $custom_field['value'];
186
- }
187
-
188
- $data[] = array(
189
- 'id' => $row['id'],
190
- 'start_date' => Lib\Utils\DateTime::formatDateTime( $row['start_date'] ),
191
- 'staff' => array(
192
- 'name' => $row['staff_name'] . ( $row['staff_any'] ? $postfix_any : '' ),
193
- ),
194
- 'customer' => array(
195
- 'full_name' => $row['customer_full_name'],
196
- 'phone' => $row['customer_phone'],
197
- 'email' => $row['customer_email'],
198
- ),
199
- 'service' => array(
200
- 'title' => $row['service_title'],
201
- 'duration' => $service_duration,
202
- 'extras' => (array) Lib\Proxy\ServiceExtras::getInfo( json_decode( $row['extras'], true ), false ),
203
- ),
204
- 'status' => $row['status'],
205
- 'payment' => $payment_title,
206
- 'notes' => $row['notes'],
207
- 'custom_fields' => $custom_fields,
208
- 'ca_id' => $row['ca_id'],
209
- 'payment_id' => $row['payment_id'],
210
- );
211
-
212
- $custom_fields = array_map( function () { return ''; }, $custom_fields );
213
- }
214
-
215
- unset( $filter['date'] );
216
- update_user_meta( get_current_user_id(), 'bookly_filter_appointments_list', $filter );
217
-
218
- wp_send_json( array(
219
- 'draw' => (int) $this->getParameter( 'draw' ),
220
- 'recordsTotal' => $total,
221
- 'recordsFiltered' => count( $data ),
222
- 'data' => $data,
223
- ) );
224
- }
225
-
226
- /**
227
- * Delete customer appointments.
228
- */
229
- public function executeDeleteCustomerAppointments()
230
- {
231
- /** @var Lib\Entities\CustomerAppointment $ca */
232
- foreach ( Lib\Entities\CustomerAppointment::query()->whereIn( 'id', $this->getParameter( 'data', array() ) )->find() as $ca ) {
233
- if ( $this->getParameter( 'notify' ) ) {
234
- switch ( $ca->getStatus() ) {
235
- case Lib\Entities\CustomerAppointment::STATUS_PENDING:
236
- case Lib\Entities\CustomerAppointment::STATUS_WAITLISTED:
237
- $ca->setStatus( Lib\Entities\CustomerAppointment::STATUS_REJECTED );
238
- break;
239
- case Lib\Entities\CustomerAppointment::STATUS_APPROVED:
240
- $ca->setStatus( Lib\Entities\CustomerAppointment::STATUS_CANCELLED );
241
- break;
242
- }
243
- Lib\NotificationSender::sendSingle(
244
- DataHolders\Simple::create( $ca ),
245
- null,
246
- array( 'cancellation_reason' => $this->getParameter( 'reason' ) )
247
- );
248
- }
249
- $ca->deleteCascade();
250
- }
251
- wp_send_json_success();
252
- }
253
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appointments/Page.php ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appointments;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Page
8
+ * @package Bookly\Backend\Modules\Appointments
9
+ */
10
+ class Page extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render page.
14
+ */
15
+ public static function render()
16
+ {
17
+ /** @var \WP_Locale $wp_locale */
18
+ global $wp_locale;
19
+
20
+ self::enqueueStyles( array(
21
+ 'frontend' => array( 'css/ladda.min.css', ),
22
+ 'backend' => array(
23
+ 'css/select2.min.css',
24
+ 'bootstrap/css/bootstrap-theme.min.css',
25
+ 'css/daterangepicker.css',
26
+ ),
27
+ ) );
28
+
29
+ self::enqueueScripts( array(
30
+ 'backend' => array(
31
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
32
+ 'js/datatables.min.js' => array( 'jquery' ),
33
+ 'js/moment.min.js',
34
+ 'js/daterangepicker.js' => array( 'jquery' ),
35
+ 'js/select2.full.min.js' => array( 'jquery' ),
36
+ ),
37
+ 'frontend' => array(
38
+ 'js/spin.min.js' => array( 'jquery' ),
39
+ 'js/ladda.min.js' => array( 'jquery' ),
40
+ ),
41
+ 'module' => array( 'js/appointments.js' => array( 'bookly-datatables.min.js' ), ),
42
+ ) );
43
+
44
+ // Custom fields without captcha, text content field & file.
45
+ $custom_fields = $cf_columns = array();
46
+ foreach ( (array) Lib\Proxy\CustomFields::getWhichHaveData() as $cf ) {
47
+ if ( $cf->type != 'file' ) {
48
+ $cf_columns[] = $cf->id;
49
+ $custom_fields[] = $cf;
50
+ }
51
+ }
52
+ // Show column attachments.
53
+ $show_attachments = Lib\Config::filesActive() && count( Lib\Proxy\Files::getAllIds() ) > 0;
54
+ wp_localize_script( 'bookly-appointments.js', 'BooklyL10n', array(
55
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
56
+ 'tomorrow' => __( 'Tomorrow', 'bookly' ),
57
+ 'today' => __( 'Today', 'bookly' ),
58
+ 'yesterday' => __( 'Yesterday', 'bookly' ),
59
+ 'last_7' => __( 'Last 7 Days', 'bookly' ),
60
+ 'last_30' => __( 'Last 30 Days', 'bookly' ),
61
+ 'this_month' => __( 'This Month', 'bookly' ),
62
+ 'next_month' => __( 'Next Month', 'bookly' ),
63
+ 'custom_range' => __( 'Custom Range', 'bookly' ),
64
+ 'apply' => __( 'Apply', 'bookly' ),
65
+ 'cancel' => __( 'Cancel', 'bookly' ),
66
+ 'to' => __( 'To', 'bookly' ),
67
+ 'from' => __( 'From', 'bookly' ),
68
+ 'calendar' => array(
69
+ 'longMonths' => array_values( $wp_locale->month ),
70
+ 'shortMonths' => array_values( $wp_locale->month_abbrev ),
71
+ 'longDays' => array_values( $wp_locale->weekday ),
72
+ 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
73
+ ),
74
+ 'mjsDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
75
+ 'startOfWeek' => (int) get_option( 'start_of_week' ),
76
+ 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
77
+ 'zeroRecords' => __( 'No appointments for selected period.', 'bookly' ),
78
+ 'processing' => __( 'Processing...', 'bookly' ),
79
+ 'edit' => __( 'Edit', 'bookly' ),
80
+ 'add_columns' => array( 'ratings' => Lib\Config::ratingsActive(), 'number_of_persons' => Lib\Config::groupBookingActive(), 'notes' => Lib\Config::showNotes(), 'attachments' => $show_attachments, ),
81
+ 'cf_columns' => $cf_columns,
82
+ 'filter' => (array) get_user_meta( get_current_user_id(), 'bookly_filter_appointments_list', true ),
83
+ 'no_result_found' => __( 'No result found', 'bookly' ),
84
+ 'attachments' => __( 'Attachments', 'bookly' ),
85
+ 'tasks' => array(
86
+ 'enabled' => Lib\Config::tasksActive(),
87
+ 'title' => Proxy\Tasks::getFilterText(),
88
+ ),
89
+ ) );
90
+
91
+ // Filters data
92
+ $staff_members = Lib\Entities\Staff::query( 's' )->select( 's.id, s.full_name' )->fetchArray();
93
+ $customers = Lib\Entities\Customer::query( 'c' )->select( 'c.id, c.full_name, c.first_name, c.last_name' )->fetchArray();
94
+ $services = Lib\Entities\Service::query( 's' )->select( 's.id, s.title' )->where( 'type', Lib\Entities\Service::TYPE_SIMPLE )->fetchArray();
95
+
96
+ self::renderTemplate( 'index', compact( 'custom_fields', 'staff_members', 'customers', 'services', 'show_attachments' ) );
97
+ }
98
+ }
backend/modules/appointments/proxy/Pro.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appointments\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Modules\Appointments\Proxy
9
+ *
10
+ * @method static void renderExportButton() Render export button.
11
+ * @method static void renderExportDialog( array $custom_fields ) Render export dialog.
12
+ * @method static void renderPrintButton() Render print button.
13
+ * @method static void renderPrintDialog( array $custom_fields ) Render print dialog.
14
+ */
15
+ abstract class Pro extends Lib\Base\Proxy
16
+ {
17
+
18
+ }
backend/modules/appointments/proxy/Ratings.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appointments\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Ratings
8
+ * @package Bookly\Backend\Modules\Appointments\Proxy
9
+ *
10
+ * @method static void renderExport( int $column ) Render column title for Appointments table
11
+ * @method static void renderTableHeader() Render 'Ratings' in appointments export popup.
12
+ */
13
+ abstract class Ratings extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/modules/appointments/proxy/Shared.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appointments\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Shared
8
+ * @package Bookly\Backend\Modules\Appointments\Proxy
9
+ *
10
+ * @method static void renderAddOnsComponents() Render components on appointments list page.
11
+ */
12
+ abstract class Shared extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/appointments/proxy/Tasks.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appointments\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Tasks
8
+ * @package Bookly\Backend\Modules\Appointments\Proxy
9
+ *
10
+ * @method static string getFilterText() get 'Tasks' in appointments date time filter.
11
+ */
12
+ abstract class Tasks extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/appointments/resources/js/appointments.js CHANGED
@@ -10,10 +10,13 @@ jQuery(function($) {
10
  $service_filter = $('#bookly-filter-service'),
11
  $status_filter = $('#bookly-filter-status'),
12
  $add_button = $('#bookly-add'),
 
 
13
  $export_dialog = $('#bookly-export-dialog'),
14
  $export_button = $('#bookly-export'),
15
  $delete_button = $('#bookly-delete'),
16
- isMobile = false
 
17
  ;
18
 
19
  try {
@@ -24,17 +27,36 @@ jQuery(function($) {
24
  }
25
 
26
  $('.bookly-js-select').val(null);
27
- $.each(BooklyL10n.filter, function (field, value) {
28
- if (value != '') {
29
- $('#bookly-filter-' + field).val(value);
30
- }
31
- // check if select has correct values
32
- if ($('#bookly-filter-' + field).prop('type') == 'select-one') {
33
- if ($('#bookly-filter-' + field +' option[value="' + value + '"]').length == 0) {
34
- $('#bookly-filter-' + field).val(null);
 
 
 
 
 
 
 
35
  }
36
- }
37
- });
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  /**
40
  * Init DataTables.
@@ -49,13 +71,22 @@ jQuery(function($) {
49
  responsivePriority: 3,
50
  render: function (data, type, row, meta) {
51
  if (isMobile) {
52
- return '<a href="tel:' + data + '">' + $.fn.dataTable.render.text().display(data) + '</a>';
53
  } else {
54
  return $.fn.dataTable.render.text().display(data);
55
  }
56
  }
57
  },
58
- { data: 'customer.email', render: $.fn.dataTable.render.text(), responsivePriority: 3 },
 
 
 
 
 
 
 
 
 
59
  {
60
  data: 'service.title',
61
  responsivePriority: 2,
@@ -82,8 +113,23 @@ jQuery(function($) {
82
  return '<a href="#bookly-payment-details-modal" data-toggle="modal" data-payment_id="' + row.payment_id + '">' + data + '</a>';
83
  }
84
  }
85
- ];
86
- if (BooklyL10n.show_notes) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  columns.push({
88
  data: 'notes',
89
  render: $.fn.dataTable.render.text(),
@@ -98,6 +144,19 @@ jQuery(function($) {
98
  orderable: false
99
  });
100
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  var dt = $appointments_list.DataTable({
102
  order: [[ 1, 'desc' ]],
103
  info: false,
@@ -106,6 +165,11 @@ jQuery(function($) {
106
  processing: true,
107
  responsive: true,
108
  serverSide: true,
 
 
 
 
 
109
  ajax: {
110
  url : ajaxurl,
111
  type: 'POST',
@@ -127,7 +191,7 @@ jQuery(function($) {
127
  responsivePriority: 1,
128
  orderable: false,
129
  render: function ( data, type, row, meta ) {
130
- return '<button type="button" class="btn btn-default"><i class="glyphicon glyphicon-edit"></i> ' + BooklyL10n.edit + '</a>';
131
  }
132
  },
133
  {
@@ -161,18 +225,19 @@ jQuery(function($) {
161
  /**
162
  * Edit appointment.
163
  */
164
- $appointments_list.on('click', 'button', function (e) {
165
- e.preventDefault();
166
- var data = dt.row($(this).closest('td')).data();
167
- showAppointmentDialog(
168
- data.id,
169
- null,
170
- null,
171
- function(event) {
172
- dt.ajax.reload();
173
- }
174
- )
175
- });
 
176
 
177
  /**
178
  * Export.
@@ -193,8 +258,26 @@ jQuery(function($) {
193
  $.fn.dataTable.ext.buttons.csvHtml5.action(null, dt, null, $.extend({}, $.fn.dataTable.ext.buttons.csvHtml5, config));
194
  });
195
 
196
- $('.bookly-limitation').on('click', function () {
197
- booklyAlert({error: [BooklyL10n.limitations]});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  });
199
 
200
  /**
@@ -266,6 +349,9 @@ jQuery(function($) {
266
  picker_ranges[BooklyL10n.last_30] = [moment().subtract(30, 'days'), moment()];
267
  picker_ranges[BooklyL10n.this_month] = [moment().startOf('month'), moment().endOf('month')];
268
  picker_ranges[BooklyL10n.next_month] = [moment().add(1, 'month').startOf('month'), moment().add(1, 'month').endOf('month')];
 
 
 
269
 
270
  $date_filter.daterangepicker(
271
  {
@@ -273,6 +359,7 @@ jQuery(function($) {
273
  startDate: moment().startOf('month'),
274
  endDate: moment().endOf('month'),
275
  ranges: picker_ranges,
 
276
  locale: {
277
  applyLabel : BooklyL10n.apply,
278
  cancelLabel: BooklyL10n.cancel,
@@ -285,12 +372,19 @@ jQuery(function($) {
285
  format : BooklyL10n.mjsDateFormat
286
  }
287
  },
288
- function(start, end) {
289
- var format = 'YYYY-MM-DD';
290
- $date_filter
291
- .data('date', start.format(format) + ' - ' + end.format(format))
292
- .find('span')
293
- .html(start.format(BooklyL10n.mjsDateFormat) + ' - ' + end.format(BooklyL10n.mjsDateFormat));
 
 
 
 
 
 
 
294
  }
295
  );
296
 
10
  $service_filter = $('#bookly-filter-service'),
11
  $status_filter = $('#bookly-filter-status'),
12
  $add_button = $('#bookly-add'),
13
+ $print_dialog = $('#bookly-print-dialog'),
14
+ $print_button = $('#bookly-print'),
15
  $export_dialog = $('#bookly-export-dialog'),
16
  $export_button = $('#bookly-export'),
17
  $delete_button = $('#bookly-delete'),
18
+ isMobile = false,
19
+ urlParts = document.URL.split('#')
20
  ;
21
 
22
  try {
27
  }
28
 
29
  $('.bookly-js-select').val(null);
30
+
31
+ // Apply filter from anchor
32
+ if (urlParts.length > 1) {
33
+ urlParts[1].split('&').forEach(function (part) {
34
+ var params = part.split('=');
35
+ if (params[0] == 'range') {
36
+ var format = 'YYYY-MM-DD',
37
+ start = moment(params['1'].substring(0, 10)),
38
+ end = moment(params['1'].substring(11));
39
+ $date_filter
40
+ .data('date', start.format(format) + ' - ' + end.format(format))
41
+ .find('span')
42
+ .html(start.format(BooklyL10n.mjsDateFormat) + ' - ' + end.format(BooklyL10n.mjsDateFormat));
43
+ } else {
44
+ $('#bookly-filter-' + params[0]).val(params[1]);
45
  }
46
+ });
47
+ } else {
48
+ $.each(BooklyL10n.filter, function (field, value) {
49
+ if (value != '') {
50
+ $('#bookly-filter-' + field).val(value);
51
+ }
52
+ // check if select has correct values
53
+ if ($('#bookly-filter-' + field).prop('type') == 'select-one') {
54
+ if ($('#bookly-filter-' + field + ' option[value="' + value + '"]').length == 0) {
55
+ $('#bookly-filter-' + field).val(null);
56
+ }
57
+ }
58
+ });
59
+ }
60
 
61
  /**
62
  * Init DataTables.
71
  responsivePriority: 3,
72
  render: function (data, type, row, meta) {
73
  if (isMobile) {
74
+ return '<a href="tel:' + $.fn.dataTable.render.text().display(data) + '">' + $.fn.dataTable.render.text().display(data) + '</a>';
75
  } else {
76
  return $.fn.dataTable.render.text().display(data);
77
  }
78
  }
79
  },
80
+ { data: 'customer.email', render: $.fn.dataTable.render.text(), responsivePriority: 5 }
81
+ ];
82
+ if (BooklyL10n.add_columns.number_of_persons) {
83
+ columns.push({
84
+ data: 'number_of_persons',
85
+ render: $.fn.dataTable.render.text(),
86
+ responsivePriority: 4
87
+ });
88
+ }
89
+ columns = columns.concat([
90
  {
91
  data: 'service.title',
92
  responsivePriority: 2,
113
  return '<a href="#bookly-payment-details-modal" data-toggle="modal" data-payment_id="' + row.payment_id + '">' + data + '</a>';
114
  }
115
  }
116
+ ]);
117
+
118
+ if (BooklyL10n.add_columns.ratings) {
119
+ columns.push({
120
+ data: 'rating',
121
+ render: function ( data, type, row, meta ) {
122
+ if (row.rating_comment == null) {
123
+ return row.rating;
124
+ } else {
125
+ return '<a href="#" data-toggle="popover" data-trigger="focus" data-placement="bottom" data-content="' + $.fn.dataTable.render.text().display(row.rating_comment) + '">' + $.fn.dataTable.render.text().display(row.rating) + '</a>';
126
+ }
127
+ },
128
+ responsivePriority: 2
129
+ });
130
+ }
131
+
132
+ if (BooklyL10n.add_columns.notes) {
133
  columns.push({
134
  data: 'notes',
135
  render: $.fn.dataTable.render.text(),
144
  orderable: false
145
  });
146
  });
147
+ if (BooklyL10n.add_columns.attachments) {
148
+ columns.push({
149
+ data: 'attachment',
150
+ render: function (data, type, row, meta) {
151
+ if (data == '1') {
152
+ return '<button type="button" class="btn btn-link bookly-js-attachment" title="' + BooklyL10n.attachments + '"><span class="dashicons dashicons-paperclip"></span></button>';
153
+ }
154
+ return '';
155
+ },
156
+ responsivePriority: 1
157
+ });
158
+ }
159
+
160
  var dt = $appointments_list.DataTable({
161
  order: [[ 1, 'desc' ]],
162
  info: false,
165
  processing: true,
166
  responsive: true,
167
  serverSide: true,
168
+ drawCallback: function( settings ) {
169
+ $('[data-toggle="popover"]').on('click', function (e) {
170
+ e.preventDefault();
171
+ }).popover();
172
+ },
173
  ajax: {
174
  url : ajaxurl,
175
  type: 'POST',
191
  responsivePriority: 1,
192
  orderable: false,
193
  render: function ( data, type, row, meta ) {
194
+ return '<button type="button" class="btn btn-default bookly-js-edit"><i class="glyphicon glyphicon-edit"></i> ' + BooklyL10n.edit + '</a>';
195
  }
196
  },
197
  {
225
  /**
226
  * Edit appointment.
227
  */
228
+ $appointments_list
229
+ .on('click', 'button.bookly-js-edit', function (e) {
230
+ e.preventDefault();
231
+ var data = dt.row($(this).closest('td')).data();
232
+ showAppointmentDialog(
233
+ data.id,
234
+ null,
235
+ null,
236
+ function (event) {
237
+ dt.ajax.reload();
238
+ }
239
+ )
240
+ });
241
 
242
  /**
243
  * Export.
258
  $.fn.dataTable.ext.buttons.csvHtml5.action(null, dt, null, $.extend({}, $.fn.dataTable.ext.buttons.csvHtml5, config));
259
  });
260
 
261
+ /**
262
+ * Print.
263
+ */
264
+ $print_button.on('click', function () {
265
+ var columns = [];
266
+ $print_dialog.find('input:checked').each(function () {
267
+ columns.push(this.value);
268
+ });
269
+ var config = {
270
+ title: '',
271
+ exportOptions: {
272
+ columns: columns
273
+ },
274
+ customize: function (win) {
275
+ win.document.firstChild.style.backgroundColor = '#fff';
276
+ win.document.body.id = 'bookly-tbs';
277
+ $(win.document.body).find('table').removeClass('collapsed');
278
+ }
279
+ };
280
+ $.fn.dataTable.ext.buttons.print.action(null, dt, null, $.extend({}, $.fn.dataTable.ext.buttons.print, config));
281
  });
282
 
283
  /**
349
  picker_ranges[BooklyL10n.last_30] = [moment().subtract(30, 'days'), moment()];
350
  picker_ranges[BooklyL10n.this_month] = [moment().startOf('month'), moment().endOf('month')];
351
  picker_ranges[BooklyL10n.next_month] = [moment().add(1, 'month').startOf('month'), moment().add(1, 'month').endOf('month')];
352
+ if (BooklyL10n.tasks.enabled) {
353
+ picker_ranges[BooklyL10n.tasks.title] = [moment(), moment().add(1, 'days')];
354
+ }
355
 
356
  $date_filter.daterangepicker(
357
  {
359
  startDate: moment().startOf('month'),
360
  endDate: moment().endOf('month'),
361
  ranges: picker_ranges,
362
+ autoUpdateInput: false,
363
  locale: {
364
  applyLabel : BooklyL10n.apply,
365
  cancelLabel: BooklyL10n.cancel,
372
  format : BooklyL10n.mjsDateFormat
373
  }
374
  },
375
+ function(start, end, label) {
376
+ if (label != BooklyL10n.tasks.title) {
377
+ var format = 'YYYY-MM-DD';
378
+ $date_filter
379
+ .data('date', start.format(format) + ' - ' + end.format(format))
380
+ .find('span')
381
+ .html(start.format(BooklyL10n.mjsDateFormat) + ' - ' + end.format(BooklyL10n.mjsDateFormat));
382
+ } else {
383
+ $date_filter
384
+ .data('date', null)
385
+ .find('span')
386
+ .html(BooklyL10n.tasks.title);
387
+ }
388
  }
389
  );
390
 
backend/modules/appointments/templates/_export_dialog.php DELETED
@@ -1,46 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Utils\Common;
3
- use BooklyLite\Lib\Config;
4
- ?>
5
- <div id="bookly-export-dialog" 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>&times;</span></button>
10
- <div class="modal-title h2"><?php _e( 'Export to CSV', 'bookly' ) ?></div>
11
- </div>
12
- <div class="modal-body">
13
- <div class="form-group">
14
- <label for="bookly-csv-delimiter"><?php _e( 'Delimiter', 'bookly' ) ?></label>
15
- <select id="bookly-csv-delimiter" class="form-control">
16
- <option value=","><?php _e( 'Comma (,)', 'bookly' ) ?></option>
17
- <option value=";"><?php _e( 'Semicolon (;)', 'bookly' ) ?></option>
18
- </select>
19
- </div>
20
- <div class="form-group">
21
- <div class="checkbox"><label><input checked value="0" type="checkbox" /><?php _e( 'No.', 'bookly' ) ?></label></div>
22
- <div class="checkbox"><label><input checked value="1" type="checkbox" /><?php _e( 'Booking Time', 'bookly' ) ?></label></div>
23
- <div class="checkbox"><label><input checked value="2" type="checkbox" /><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_employee' ) ?></label></div>
24
- <div class="checkbox"><label><input checked value="3" type="checkbox" /><?php _e( 'Customer Name', 'bookly' ) ?></label></div>
25
- <div class="checkbox"><label><input checked value="4" type="checkbox" /><?php _e( 'Customer Phone', 'bookly' ) ?></label></div>
26
- <div class="checkbox"><label><input checked value="5" type="checkbox" /><?php _e( 'Customer Email', 'bookly' ) ?></label></div>
27
- <div class="checkbox"><label><input checked value="6" type="checkbox" /><?php echo \BooklyLite\Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_service' ) ?></label></div>
28
- <div class="checkbox"><label><input checked value="7" type="checkbox" /><?php _e( 'Duration', 'bookly' ) ?></label></div>
29
- <div class="checkbox"><label><input checked value="8" type="checkbox" /><?php _e( 'Status', 'bookly' ) ?></label></div>
30
- <div class="checkbox"><label><input checked value="9" type="checkbox" /><?php _e( 'Payment', 'bookly' ) ?></label></div>
31
- <?php $i = 10; if ( Config::showNotes() ): $i = 11; ?>
32
- <div class="checkbox"><label><input checked value="10" type="checkbox" /><?php echo esc_html( Common::getTranslatedOption( 'bookly_l10n_label_notes' ) ) ?></label></div>
33
- <?php endif ?>
34
- <?php foreach ( $custom_fields as $custom_field ) : ?>
35
- <div class="checkbox"><label><input checked value="<?php echo $i ++ ?>" type="checkbox"/><?php echo $custom_field->label ?></label></div>
36
- <?php endforeach ?>
37
- </div>
38
- </div>
39
- <div class="modal-footer">
40
- <button type="submit" class="btn btn-lg btn-success" id="bookly-export" data-dismiss="modal">
41
- <?php _e( 'Export to CSV', 'bookly' ) ?>
42
- </button>
43
- </div>
44
- </div>
45
- </div>
46
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/appointments/templates/index.php CHANGED
@@ -1,7 +1,11 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Entities\CustomerAppointment;
3
- use BooklyLite\Lib\Utils\Common;
4
- use BooklyLite\Lib\Config;
 
 
 
 
5
  ?>
6
  <div id="bookly-tbs" class="wrap">
7
  <div class="bookly-tbs-body">
@@ -9,18 +13,14 @@ use BooklyLite\Lib\Config;
9
  <div class="bookly-page-title">
10
  <?php _e( 'Appointments', 'bookly' ) ?>
11
  </div>
12
- <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
13
  </div>
14
  <div class="panel panel-default bookly-main">
15
  <div class="panel-body">
16
  <div class="row">
17
  <div class="form-inline bookly-margin-bottom-lg text-right">
18
- <div class="form-group">
19
- <button type="button" class="btn btn-default bookly-btn-block-xs" data-toggle="modal" data-target="#bookly-export-dialog"><i class="glyphicon glyphicon-export"></i> <?php _e( 'Export to CSV', 'bookly' ) ?></button>
20
- </div>
21
- <div class="form-group">
22
- <button type="button" class="btn btn-default bookly-btn-block-xs bookly-limitation"><i class="glyphicon glyphicon-print"></i> <?php _e( 'Print', 'bookly' ) ?></button>
23
- </div>
24
  <div class="form-group">
25
  <button type="button" class="btn btn-success bookly-btn-block-xs" id="bookly-add"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'New appointment', 'bookly' ) ?></button>
26
  </div>
@@ -37,7 +37,7 @@ use BooklyLite\Lib\Config;
37
  <button type="button" class="btn btn-block btn-default" id="bookly-filter-date" data-date="<?php echo date( 'Y-m-d', strtotime( 'first day of' ) ) ?> - <?php echo date( 'Y-m-d', strtotime( 'last day of' ) ) ?>">
38
  <i class="dashicons dashicons-calendar-alt"></i>
39
  <span>
40
- <?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( 'first day of this month' ) ?> - <?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( 'last day of this month' ) ?>
41
  </span>
42
  </button>
43
  </div>
@@ -81,6 +81,9 @@ use BooklyLite\Lib\Config;
81
  <?php if ( Config::waitingListActive() ): ?>
82
  <option value="<?php echo CustomerAppointment::STATUS_WAITLISTED ?>"><?php echo CustomerAppointment::statusToString( CustomerAppointment::STATUS_WAITLISTED ) ?></option>
83
  <?php endif ?>
 
 
 
84
  </select>
85
  </div>
86
  </div>
@@ -95,16 +98,23 @@ use BooklyLite\Lib\Config;
95
  <th><?php _e( 'Customer Name', 'bookly' ) ?></th>
96
  <th><?php _e( 'Customer Phone', 'bookly' ) ?></th>
97
  <th><?php _e( 'Customer Email', 'bookly' ) ?></th>
 
 
 
98
  <th><?php echo esc_html( Common::getTranslatedOption( 'bookly_l10n_label_service' ) ) ?></th>
99
  <th><?php _e( 'Duration', 'bookly' ) ?></th>
100
  <th><?php _e( 'Status', 'bookly' ) ?></th>
101
  <th><?php _e( 'Payment', 'bookly' ) ?></th>
 
102
  <?php if ( Config::showNotes() ): ?>
103
  <th><?php echo esc_html( Common::getTranslatedOption( 'bookly_l10n_label_notes' ) ) ?></th>
104
  <?php endif ?>
105
  <?php foreach ( $custom_fields as $custom_field ) : ?>
106
  <th><?php echo $custom_field->label ?></th>
107
  <?php endforeach ?>
 
 
 
108
  <th></th>
109
  <th width="16"><input type="checkbox" id="bookly-check-all" /></th>
110
  </tr>
@@ -112,15 +122,16 @@ use BooklyLite\Lib\Config;
112
  </table>
113
 
114
  <div class="text-right bookly-margin-top-lg">
115
- <?php Common::deleteButton( '', '', '#bookly-delete-dialog' ) ?>
116
  </div>
117
  </div>
118
  </div>
119
 
120
- <?php \BooklyLite\Backend\Modules\Calendar\Components::getInstance()->renderDeleteDialog(); ?>
121
- <?php include '_export_dialog.php' ?>
122
 
123
- <?php \BooklyLite\Backend\Modules\Calendar\Components::getInstance()->renderAppointmentDialog() ?>
124
- <?php \BooklyLite\Lib\Proxy\Shared::renderComponentAppointments() ?>
 
125
  </div>
126
  </div>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls;
3
+ use Bookly\Backend\Components\Dialogs;
4
+ use Bookly\Backend\Components\Support;
5
+ use Bookly\Backend\Modules\Appointments\Proxy;
6
+ use Bookly\Lib\Config;
7
+ use Bookly\Lib\Entities\CustomerAppointment;
8
+ use Bookly\Lib\Utils\Common;
9
  ?>
10
  <div id="bookly-tbs" class="wrap">
11
  <div class="bookly-tbs-body">
13
  <div class="bookly-page-title">
14
  <?php _e( 'Appointments', 'bookly' ) ?>
15
  </div>
16
+ <?php Support\Buttons::render( $self::pageSlug() ) ?>
17
  </div>
18
  <div class="panel panel-default bookly-main">
19
  <div class="panel-body">
20
  <div class="row">
21
  <div class="form-inline bookly-margin-bottom-lg text-right">
22
+ <?php Proxy\Pro::renderExportButton() ?>
23
+ <?php Proxy\Pro::renderPrintButton() ?>
 
 
 
 
24
  <div class="form-group">
25
  <button type="button" class="btn btn-success bookly-btn-block-xs" id="bookly-add"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'New appointment', 'bookly' ) ?></button>
26
  </div>
37
  <button type="button" class="btn btn-block btn-default" id="bookly-filter-date" data-date="<?php echo date( 'Y-m-d', strtotime( 'first day of' ) ) ?> - <?php echo date( 'Y-m-d', strtotime( 'last day of' ) ) ?>">
38
  <i class="dashicons dashicons-calendar-alt"></i>
39
  <span>
40
+ <?php echo \Bookly\Lib\Utils\DateTime::formatDate( 'first day of this month' ) ?> - <?php echo \Bookly\Lib\Utils\DateTime::formatDate( 'last day of this month' ) ?>
41
  </span>
42
  </button>
43
  </div>
81
  <?php if ( Config::waitingListActive() ): ?>
82
  <option value="<?php echo CustomerAppointment::STATUS_WAITLISTED ?>"><?php echo CustomerAppointment::statusToString( CustomerAppointment::STATUS_WAITLISTED ) ?></option>
83
  <?php endif ?>
84
+ <?php if ( Config::tasksActive() ): ?>
85
+ <option value="<?php echo CustomerAppointment::STATUS_DONE ?>"><?php echo CustomerAppointment::statusToString( CustomerAppointment::STATUS_DONE ) ?></option>
86
+ <?php endif ?>
87
  </select>
88
  </div>
89
  </div>
98
  <th><?php _e( 'Customer Name', 'bookly' ) ?></th>
99
  <th><?php _e( 'Customer Phone', 'bookly' ) ?></th>
100
  <th><?php _e( 'Customer Email', 'bookly' ) ?></th>
101
+ <?php if ( Config::groupBookingActive() ) : ?>
102
+ <th><?php _e( 'Number of persons', 'bookly' ) ?></th>
103
+ <?php endif ?>
104
  <th><?php echo esc_html( Common::getTranslatedOption( 'bookly_l10n_label_service' ) ) ?></th>
105
  <th><?php _e( 'Duration', 'bookly' ) ?></th>
106
  <th><?php _e( 'Status', 'bookly' ) ?></th>
107
  <th><?php _e( 'Payment', 'bookly' ) ?></th>
108
+ <?php Proxy\Ratings::renderTableHeader() ?>
109
  <?php if ( Config::showNotes() ): ?>
110
  <th><?php echo esc_html( Common::getTranslatedOption( 'bookly_l10n_label_notes' ) ) ?></th>
111
  <?php endif ?>
112
  <?php foreach ( $custom_fields as $custom_field ) : ?>
113
  <th><?php echo $custom_field->label ?></th>
114
  <?php endforeach ?>
115
+ <?php if ( $show_attachments ) : ?>
116
+ <th><?php _e( 'Attachments', 'bookly' ) ?></th>
117
+ <?php endif ?>
118
  <th></th>
119
  <th width="16"><input type="checkbox" id="bookly-check-all" /></th>
120
  </tr>
122
  </table>
123
 
124
  <div class="text-right bookly-margin-top-lg">
125
+ <?php Controls\Buttons::renderDelete( null, null, null, array( 'data-toggle' => 'modal', 'data-target'=> '#bookly-delete-dialog' ) ) ?>
126
  </div>
127
  </div>
128
  </div>
129
 
130
+ <?php Proxy\Pro::renderExportDialog( $custom_fields ) ?>
131
+ <?php Proxy\Pro::renderPrintDialog( $custom_fields ) ?>
132
 
133
+ <?php Dialogs\Appointment\Delete\Dialog::render() ?>
134
+ <?php Dialogs\Appointment\Edit\Dialog::render() ?>
135
+ <?php Proxy\Shared::renderAddOnsComponents() ?>
136
  </div>
137
  </div>
backend/modules/calendar/Ajax.php ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Calendar;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Ajax
8
+ * @package Bookly\Backend\Modules\Calendar
9
+ */
10
+ class Ajax extends Page
11
+ {
12
+ /**
13
+ * @inheritdoc
14
+ */
15
+ protected static function permissions()
16
+ {
17
+ return array( '_default' => 'user' );
18
+ }
19
+
20
+ /**
21
+ * Get data for FullCalendar.
22
+ */
23
+ public static function getStaffAppointments()
24
+ {
25
+ $result = array();
26
+ $staff = Lib\Entities\Staff::query()->findOne();
27
+ $staff_members = $staff ? Lib\Proxy\Pro::prepareStaffMembers( array( $staff ) ) : array();
28
+ $one_day = new \DateInterval( 'P1D' );
29
+ $start_date = new \DateTime( self::parameter( 'start' ) );
30
+ $end_date = new \DateTime( self::parameter( 'end' ) );
31
+ // FullCalendar sends end date as 1 day further.
32
+ $end_date->sub( $one_day );
33
+
34
+ if ( Lib\Utils\Common::isCurrentUserAdmin() ) {
35
+ $staff_ids = explode( ',', self::parameter( 'staff_ids' ) );
36
+ foreach ( $staff_members as $id => $staff ) {
37
+ if ( ! in_array( $staff->getId(), $staff_ids ) ) {
38
+ unset( $staff_members[ $id ] );
39
+ }
40
+ }
41
+ } else {
42
+ $staff_ids = array( $staff_members[0]->getId() );
43
+ }
44
+ // Load special days.
45
+ $special_days = array();
46
+ foreach ( (array) Lib\Proxy\SpecialDays::getSchedule( $staff_ids, $start_date, $end_date ) as $day ) {
47
+ $special_days[ $day['staff_id'] ][ $day['date'] ][] = $day;
48
+ }
49
+
50
+ if ( ! Lib\Config::locationsActive() || self::parameter( 'location_ids' ) ) {
51
+ foreach ( $staff_members as $staff ) {
52
+ /** @var Lib\Entities\Staff $staff */
53
+ $result = array_merge( $result, self::_getAppointmentsForFC( $staff->getId(), $start_date, $end_date ) );
54
+
55
+ // Schedule.
56
+ $items = $staff->getScheduleItems();
57
+ $day = clone $start_date;
58
+ // Find previous day end time.
59
+ $last_end = clone $day;
60
+ $last_end->sub( $one_day );
61
+ $w = (int) $day->format( 'w' );
62
+ $end_time = $items[ $w > 0 ? $w : 7 ]->getEndTime();
63
+ if ( $end_time !== null ) {
64
+ $end_time = explode( ':', $end_time );
65
+ $last_end->setTime( $end_time[0], $end_time[1] );
66
+ } else {
67
+ $last_end->setTime( 24, 0 );
68
+ }
69
+ // Do the loop.
70
+ while ( $day <= $end_date ) {
71
+ $start = $last_end->format( 'Y-m-d H:i:s' );
72
+ // Check if $day is Special Day for current staff.
73
+ if ( isset( $special_days[ $staff->getId() ][ $day->format( 'Y-m-d' ) ] ) ) {
74
+ $sp_days = $special_days[ $staff->getId() ][ $day->format( 'Y-m-d' ) ];
75
+ $end = $sp_days[0]['date'] . ' ' . $sp_days[0]['start_time'];
76
+ if ( $start < $end ) {
77
+ $result[] = array(
78
+ 'start' => $start,
79
+ 'end' => $end,
80
+ 'rendering' => 'background',
81
+ 'staffId' => $staff->getId(),
82
+ );
83
+ }
84
+ // Breaks.
85
+ foreach ( $sp_days as $sp_day ) {
86
+ $break_start = date(
87
+ 'Y-m-d H:i:s',
88
+ strtotime( $sp_day['date'] ) + Lib\Utils\DateTime::timeToSeconds( $sp_day['break_start'] )
89
+ );
90
+ $break_end = date(
91
+ 'Y-m-d H:i:s',
92
+ strtotime( $sp_day['date'] ) + Lib\Utils\DateTime::timeToSeconds( $sp_day['break_end'] )
93
+ );
94
+ $result[] = array(
95
+ 'start' => $break_start,
96
+ 'end' => $break_end,
97
+ 'rendering' => 'background',
98
+ 'staffId' => $staff->getId(),
99
+ );
100
+ }
101
+ $end_time = explode( ':', $sp_days[0]['end_time'] );
102
+ $last_end = clone $day;
103
+ $last_end->setTime( $end_time[0], $end_time[1] );
104
+ } else {
105
+ /** @var Lib\Entities\StaffScheduleItem $item */
106
+ $item = $items[ (int) $day->format( 'w' ) + 1 ];
107
+ if ( $item->getStartTime() && ! $staff->isOnHoliday( $day ) ) {
108
+ $end = $day->format( 'Y-m-d ' . $item->getStartTime() );
109
+ if ( $start < $end ) {
110
+ $result[] = array(
111
+ 'start' => $start,
112
+ 'end' => $end,
113
+ 'rendering' => 'background',
114
+ 'staffId' => $staff->getId(),
115
+ );
116
+ }
117
+ $last_end = clone $day;
118
+ $end_time = explode( ':', $item->getEndTime() );
119
+ $last_end->setTime( $end_time[0], $end_time[1] );
120
+
121
+ // Breaks.
122
+ foreach ( $item->getBreaksList() as $break ) {
123
+ $break_start = date(
124
+ 'Y-m-d H:i:s',
125
+ $day->getTimestamp() + Lib\Utils\DateTime::timeToSeconds( $break['start_time'] )
126
+ );
127
+ $break_end = date(
128
+ 'Y-m-d H:i:s',
129
+ $day->getTimestamp() + Lib\Utils\DateTime::timeToSeconds( $break['end_time'] )
130
+ );
131
+ $result[] = array(
132
+ 'start' => $break_start,
133
+ 'end' => $break_end,
134
+ 'rendering' => 'background',
135
+ 'staffId' => $staff->getId(),
136
+ );
137
+ }
138
+ } else {
139
+ $result[] = array(
140
+ 'start' => $last_end->format( 'Y-m-d H:i:s' ),
141
+ 'end' => $day->format( 'Y-m-d 24:00:00' ),
142
+ 'rendering' => 'background',
143
+ 'staffId' => $staff->getId(),
144
+ );
145
+ $last_end = clone $day;
146
+ $last_end->setTime( 24, 0 );
147
+ }
148
+ }
149
+
150
+ $day->add( $one_day );
151
+ }
152
+
153
+ if ( $last_end->format( 'H' ) != 24 ) {
154
+ $result[] = array(
155
+ 'start' => $last_end->format( 'Y-m-d H:i:s' ),
156
+ 'end' => $last_end->format( 'Y-m-d 24:00:00' ),
157
+ 'rendering' => 'background',
158
+ 'staffId' => $staff->getId(),
159
+ );
160
+ }
161
+ }
162
+ }
163
+
164
+ wp_send_json( $result );
165
+ }
166
+
167
+ /**
168
+ * Get appointments for FullCalendar.
169
+ *
170
+ * @param integer $staff_id
171
+ * @param \DateTime $start_date
172
+ * @param \DateTime $end_date
173
+ * @return array
174
+ */
175
+ private static function _getAppointmentsForFC( $staff_id, \DateTime $start_date, \DateTime $end_date )
176
+ {
177
+ $query = Lib\Entities\Appointment::query( 'a' )
178
+ ->where( 'st.id', $staff_id )
179
+ ->whereBetween( 'DATE(a.start_date)', $start_date->format( 'Y-m-d' ), $end_date->format( 'Y-m-d' ) );
180
+
181
+ Proxy\Shared::prepareAppointmentsQueryForFC( $query, $staff_id, $start_date, $end_date );
182
+
183
+ return self::buildAppointmentsForFC( $staff_id, $query );
184
+ }
185
+ }
backend/modules/calendar/Controller.php DELETED
@@ -1,932 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Calendar;
3
-
4
- use BooklyLite\Lib;
5
- use BooklyLite\Lib\DataHolders\Booking as DataHolders;
6
-
7
- /**
8
- * Class Controller
9
- * @package BooklyLite\Backend\Modules\Calendar
10
- */
11
- class Controller extends Lib\Base\Controller
12
- {
13
- const page_slug = 'bookly-calendar';
14
-
15
- protected function getPermissions()
16
- {
17
- return array( '_this' => 'user' );
18
- }
19
-
20
- public function index()
21
- {
22
- /** @var \WP_Locale $wp_locale */
23
- global $wp_locale;
24
-
25
- $this->enqueueStyles( array(
26
- 'module' => array( 'css/fullcalendar.min.css', ),
27
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css' ),
28
- ) );
29
-
30
- $this->enqueueScripts( array(
31
- 'backend' => array( 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ), ),
32
- 'module' => array(
33
- 'js/fullcalendar.min.js' => array( 'bookly-moment.min.js' ),
34
- 'js/fc-multistaff-view.js' => array( 'bookly-fullcalendar.min.js' ),
35
- 'js/calendar-common.js' => array( 'bookly-fc-multistaff-view.js' ),
36
- 'js/calendar.js' => array( 'bookly-calendar-common.js' ),
37
- ),
38
- ) );
39
-
40
- $slot_length_minutes = get_option( 'bookly_gen_time_slot_length', '15' );
41
- $slot = new \DateInterval( 'PT' . $slot_length_minutes . 'M' );
42
-
43
- $staff_members = Lib\Utils\Common::isCurrentUserAdmin()
44
- ? Lib\Entities\Staff::query()->where( 'id', 1 )->find()
45
- : Lib\Entities\Staff::query()->where( 'wp_user_id', get_current_user_id() )->find();
46
-
47
- wp_localize_script( 'bookly-calendar.js', 'BooklyL10n', array(
48
- 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
49
- 'slotDuration' => $slot->format( '%H:%I:%S' ),
50
- 'calendar' => array(
51
- 'shortMonths' => array_values( $wp_locale->month_abbrev ),
52
- 'longMonths' => array_values( $wp_locale->month ),
53
- 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
54
- 'longDays' => array_values( $wp_locale->weekday ),
55
- ),
56
- 'dpDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_JQUERY_DATEPICKER ),
57
- 'mjsDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
58
- 'mjsTimeFormat' => Lib\Utils\DateTime::convertFormat( 'time', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
59
- 'today' => __( 'Today', 'bookly' ),
60
- 'week' => __( 'Week', 'bookly' ),
61
- 'day' => __( 'Day', 'bookly' ),
62
- 'month' => __( 'Month', 'bookly' ),
63
- 'allDay' => __( 'All Day', 'bookly' ),
64
- 'delete' => __( 'Delete', 'bookly' ),
65
- 'noStaffSelected' => __( 'No staff selected', 'bookly' ),
66
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
67
- 'startOfWeek' => (int) get_option( 'start_of_week' ),
68
- 'recurring_appointments' => array(
69
- 'active' => (int) Lib\Config::recurringAppointmentsActive(),
70
- 'title' => __( 'Recurring appointments', 'bookly' ),
71
- ),
72
- 'waiting_list' => array(
73
- 'active' => (int) Lib\Config::waitingListActive(),
74
- 'title' => __( 'On waiting list', 'bookly' ),
75
- ),
76
- 'packages' => array(
77
- 'active' => (int) Lib\Config::packagesActive(),
78
- 'title' => __( 'Package', 'bookly' ),
79
- ),
80
- ) );
81
-
82
- $this->render( 'calendar', compact( 'staff_members' ) );
83
- }
84
-
85
- /**
86
- * Get data for FullCalendar.
87
- *
88
- * return string json
89
- */
90
- public function executeGetStaffAppointments()
91
- {
92
- $result = array();
93
- $staff_members = array();
94
- $one_day = new \DateInterval( 'P1D' );
95
- $start_date = new \DateTime( $this->getParameter( 'start' ) );
96
- $end_date = new \DateTime( $this->getParameter( 'end' ) );
97
- // FullCalendar sends end date as 1 day further.
98
- $end_date->sub( $one_day );
99
-
100
- $staff_ids = array( 1 );
101
- if ( Lib\Utils\Common::isCurrentUserAdmin() ) {
102
- $staff_members = Lib\Entities\Staff::query()
103
- ->where( 'id', 1 )
104
- ->find();
105
- } else {
106
- $staff_member = Lib\Entities\Staff::query()
107
- ->where( 'wp_user_id', get_current_user_id() )
108
- ->findOne();
109
- $staff_members[] = $staff_member;
110
- }
111
- // Load special days.
112
- $special_days = array();
113
- foreach ( (array) Lib\Proxy\SpecialDays::getSchedule( $staff_ids, $start_date, $end_date ) as $day ) {
114
- $special_days[ $day['staff_id'] ][ $day['date'] ][] = $day;
115
- }
116
-
117
- foreach ( $staff_members as $staff ) {
118
- /** @var Lib\Entities\Staff $staff */
119
- $result = array_merge( $result, $this->_getAppointmentsForFC( $staff->getId(), $start_date, $end_date ) );
120
-
121
- // Schedule.
122
- $items = $staff->getScheduleItems();
123
- $day = clone $start_date;
124
- // Find previous day end time.
125
- $last_end = clone $day;
126
- $last_end->sub( $one_day );
127
- $w = (int) $day->format( 'w' );
128
- $end_time = $items[ $w > 0 ? $w : 7 ]->getEndTime();
129
- if ( $end_time !== null ) {
130
- $end_time = explode( ':', $end_time );
131
- $last_end->setTime( $end_time[0], $end_time[1] );
132
- } else {
133
- $last_end->setTime( 24, 0 );
134
- }
135
- // Do the loop.
136
- while ( $day <= $end_date ) {
137
- $start = $last_end->format( 'Y-m-d H:i:s' );
138
- // Check if $day is Special Day for current staff.
139
- if ( isset( $special_days[ $staff->getId() ][ $day->format( 'Y-m-d' ) ] ) ) {
140
- $sp_days = $special_days[ $staff->getId() ][ $day->format( 'Y-m-d' ) ];
141
- $end = $sp_days[0]['date'] . ' ' . $sp_days[0]['start_time'];
142
- if ( $start < $end ) {
143
- $result[] = array(
144
- 'start' => $start,
145
- 'end' => $end,
146
- 'rendering' => 'background',
147
- 'staffId' => $staff->getId(),
148
- );
149
- }
150
- // Breaks.
151
- foreach ( $sp_days as $sp_day ) {
152
- $break_start = date(
153
- 'Y-m-d H:i:s',
154
- strtotime( $sp_day['date'] ) + Lib\Utils\DateTime::timeToSeconds( $sp_day['break_start'] )
155
- );
156
- $break_end = date(
157
- 'Y-m-d H:i:s',
158
- strtotime( $sp_day['date'] ) + Lib\Utils\DateTime::timeToSeconds( $sp_day['break_end'] )
159
- );
160
- $result[] = array(
161
- 'start' => $break_start,
162
- 'end' => $break_end,
163
- 'rendering' => 'background',
164
- 'staffId' => $staff->getId(),
165
- );
166
- }
167
- $end_time = explode( ':', $sp_days[0]['end_time'] );
168
- $last_end = clone $day;
169
- $last_end->setTime( $end_time[0], $end_time[1] );
170
- } else {
171
- /** @var Lib\Entities\StaffScheduleItem $item */
172
- $item = $items[ (int) $day->format( 'w' ) + 1 ];
173
- if ( $item->getStartTime() && ! $staff->isOnHoliday( $day ) ) {
174
- $end = $day->format( 'Y-m-d ' . $item->getStartTime() );
175
- if ( $start < $end ) {
176
- $result[] = array(
177
- 'start' => $start,
178
- 'end' => $end,
179
- 'rendering' => 'background',
180
- 'staffId' => $staff->getId(),
181
- );
182
- }
183
- $last_end = clone $day;
184
- $end_time = explode( ':', $item->getEndTime() );
185
- $last_end->setTime( $end_time[0], $end_time[1] );
186
-
187
- // Breaks.
188
- foreach ( $item->getBreaksList() as $break ) {
189
- $break_start = date(
190
- 'Y-m-d H:i:s',
191
- $day->getTimestamp() + Lib\Utils\DateTime::timeToSeconds( $break['start_time'] )
192
- );
193
- $break_end = date(
194
- 'Y-m-d H:i:s',
195
- $day->getTimestamp() + Lib\Utils\DateTime::timeToSeconds( $break['end_time'] )
196
- );
197
- $result[] = array(
198
- 'start' => $break_start,
199
- 'end' => $break_end,
200
- 'rendering' => 'background',
201
- 'staffId' => $staff->getId(),
202
- );
203
- }
204
- } else {
205
- $result[] = array(
206
- 'start' => $last_end->format( 'Y-m-d H:i:s' ),
207
- 'end' => $day->format( 'Y-m-d 24:00:00' ),
208
- 'rendering' => 'background',
209
- 'staffId' => $staff->getId(),
210
- );
211
- $last_end = clone $day;
212
- $last_end->setTime( 24, 0 );
213
- }
214
- }
215
-
216
- $day->add( $one_day );
217
- }
218
-
219
- if ( $last_end->format( 'H' ) != 24 ) {
220
- $result[] = array(
221
- 'start' => $last_end->format( 'Y-m-d H:i:s' ),
222
- 'end' => $last_end->format( 'Y-m-d 24:00:00' ),
223
- 'rendering' => 'background',
224
- 'staffId' => $staff->getId(),
225
- );
226
- }
227
- }
228
-
229
- wp_send_json( $result );
230
- }
231
-
232
- /**
233
- * Get data needed for appointment form initialisation.
234
- */
235
- public function executeGetDataForAppointmentForm()
236
- {
237
- $type = $this->getParameter( 'type', false ) == 'package' ? Lib\Entities\Service::TYPE_PACKAGE : Lib\Entities\Service::TYPE_SIMPLE;
238
- $result = array(
239
- 'staff' => array(),
240
- 'customers' => array(),
241
- 'start_time' => array(),
242
- 'end_time' => array(),
243
- 'week_days' => array(),
244
- 'time_interval' => Lib\Config::getTimeSlotLength(),
245
- 'status' => array(
246
- 'items' => array(
247
- 'pending' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_PENDING ),
248
- 'approved' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_APPROVED ),
249
- 'cancelled' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_CANCELLED ),
250
- 'rejected' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_REJECTED ),
251
- 'waitlisted' => Lib\Entities\CustomerAppointment::statusToString( Lib\Entities\CustomerAppointment::STATUS_WAITLISTED ),
252
- ),
253
- 'default' => get_option( 'bookly_gen_default_appointment_status' ),
254
- ),
255
- );
256
-
257
- // Staff list.
258
- $staff_members = Lib\Utils\Common::isCurrentUserAdmin()
259
- ? Lib\Entities\Staff::query()->sortBy( 'position' )->find()
260
- : Lib\Entities\Staff::query()->where( 'wp_user_id', get_current_user_id() )->find();
261
-
262
- /** @var Lib\Entities\Staff $staff_member */
263
- foreach ( $staff_members as $staff_member ) {
264
- $services = array();
265
- if ( $type == Lib\Entities\Service::TYPE_SIMPLE ) {
266
- $services[] = array(
267
- 'id' => null,
268
- 'title' => __( 'Custom', 'bookly' ),
269
- 'duration' => Lib\Config::getTimeSlotLength(),
270
- 'capacity_min' => 1,
271
- 'capacity_max' => 1,
272
- );
273
- }
274
- foreach ( $staff_member->getStaffServices( $type ) as $staff_service ) {
275
- $sub_services = $staff_service->service->getSubServices();
276
- if ( $type == Lib\Entities\Service::TYPE_SIMPLE || ! empty( $sub_services ) ) {
277
- $services[] = array(
278
- 'id' => $staff_service->service->getId(),
279
- 'title' => sprintf(
280
- '%s (%s)',
281
- $staff_service->service->getTitle(),
282
- Lib\Utils\DateTime::secondsToInterval( $staff_service->service->getDuration() )
283
- ),
284
- 'duration' => $staff_service->service->getDuration(),
285
- 'capacity_min' => 1,
286
- 'capacity_max' => 1,
287
- );
288
- }
289
- }
290
- $locations = array();
291
- foreach ( (array) Lib\Proxy\Locations::findByStaffId( $staff_member->getId() ) as $location ) {
292
- $locations[] = array(
293
- 'id' => $location->getId(),
294
- 'name' => $location->getName(),
295
- );
296
- }
297
- $result['staff'][] = array(
298
- 'id' => $staff_member->getId(),
299
- 'full_name' => $staff_member->getFullName(),
300
- 'services' => $services,
301
- 'locations' => $locations,
302
- );
303
- }
304
-
305
- /** @var Lib\Entities\Customer $customer */
306
- // Customers list.
307
- foreach ( Lib\Entities\Customer::query()->sortBy( 'full_name' )->find() as $customer ) {
308
- $name = $customer->getFullName();
309
- if ( $customer->getEmail() != '' || $customer->getPhone() != '' ) {
310
- $name .= ' (' . trim( $customer->getEmail() . ', ' . $customer->getPhone(), ', ' ) . ')';
311
- }
312
-
313
- $result['customers'][] = array(
314
- 'id' => $customer->getId(),
315
- 'name' => $name,
316
- 'custom_fields' => array(),
317
- 'number_of_persons' => 1,
318
- );
319
- }
320
-
321
- // Time list.
322
- $ts_length = Lib\Config::getTimeSlotLength();
323
- $time_start = 0;
324
- $time_end = DAY_IN_SECONDS * 2;
325
-
326
- // Run the loop.
327
- while ( $time_start <= $time_end ) {
328
- $slot = array(
329
- 'value' => Lib\Utils\DateTime::buildTimeString( $time_start, false ),
330
- 'title' => Lib\Utils\DateTime::formatTime( $time_start ),
331
- );
332
- if ( $time_start < DAY_IN_SECONDS ) {
333
- $result['start_time'][] = $slot;
334
- }
335
- $result['end_time'][] = $slot;
336
- $time_start += $ts_length;
337
- }
338
-
339
- $days_times = Lib\Config::getDaysAndTimes();
340
- $weekdays = array( 1 => 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', );
341
- foreach ( $days_times['days'] as $index => $abbrev ) {
342
- $result['week_days'][] = $weekdays[ $index ];
343
- }
344
-
345
- wp_send_json( $result );
346
- }
347
-
348
- /**
349
- * Get appointment data when editing an appointment.
350
- */
351
- public function executeGetDataForAppointment()
352
- {
353
- $response = array( 'success' => false, 'data' => array( 'customers' => array() ) );
354
-
355
- $appointment = new Lib\Entities\Appointment();
356
- if ( $appointment->load( $this->getParameter( 'id' ) ) ) {
357
- $response['success'] = true;
358
-
359
- $info = Lib\Entities\Appointment::query( 'a' )
360
- ->select( 'ss.capacity_min as min_capacity,
361
- ss.capacity_max AS max_capacity,
362
- SUM(ca.number_of_persons) AS total_number_of_persons,
363
- a.staff_id,
364
- a.staff_any,
365
- a.service_id,
366
- a.custom_service_name,
367
- a.custom_service_price,
368
- a.start_date,
369
- a.end_date,
370
- a.internal_note,
371
- a.series_id,
372
- a.location_id' )
373
- ->leftJoin( 'CustomerAppointment', 'ca', 'ca.appointment_id = a.id' )
374
- ->leftJoin( 'StaffService', 'ss', 'ss.staff_id = a.staff_id AND ss.service_id = a.service_id' )
375
- ->where( 'a.id', $appointment->getId() )
376
- ->fetchRow();
377
-
378
- $response['data']['total_number_of_persons'] = $info['total_number_of_persons'];
379
- $response['data']['min_capacity'] = $info['min_capacity'];
380
- $response['data']['max_capacity'] = $info['max_capacity'];
381
- $response['data']['start_date'] = $info['start_date'];
382
- $response['data']['end_date'] = $info['end_date'];
383
- $response['data']['staff_id'] = $info['staff_id'];
384
- $response['data']['staff_any'] = (int) $info['staff_any'];
385
- $response['data']['service_id'] = $info['service_id'];
386
- $response['data']['custom_service_name'] = $info['custom_service_name'];
387
- $response['data']['custom_service_price'] = (float) $info['custom_service_price'];
388
- $response['data']['internal_note'] = $info['internal_note'];
389
- $response['data']['series_id'] = $info['series_id'];
390
- $response['data']['location_id'] = $info['location_id'];
391
-
392
- $customers = Lib\Entities\CustomerAppointment::query( 'ca' )
393
- ->select( 'ca.id,
394
- ca.customer_id,
395
- ca.package_id,
396
- ca.custom_fields,
397
- ca.extras,
398
- ca.number_of_persons,
399
- ca.notes,
400
- ca.status,
401
- ca.payment_id,
402
- ca.compound_service_id,
403
- ca.compound_token,
404
- p.paid AS payment,
405
- p.total AS payment_total,
406
- p.type AS payment_type,
407
- p.details AS payment_details,
408
- p.status AS payment_status' )
409
- ->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
410
- ->where( 'ca.appointment_id', $appointment->getId() )
411
- ->fetchArray();
412
- foreach ( $customers as $customer ) {
413
- $payment_title = '';
414
- if ( $customer['payment'] !== null ) {
415
- $payment_title = Lib\Utils\Price::format( $customer['payment'] );
416
- if ( $customer['payment'] != $customer['payment_total'] ) {
417
- $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Price::format( $customer['payment_total'] ) );
418
- }
419
- $payment_title .= sprintf(
420
- ' %s <span%s>%s</span>',
421
- Lib\Entities\Payment::typeToString( $customer['payment_type'] ),
422
- $customer['payment_status'] == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
423
- Lib\Entities\Payment::statusToString( $customer['payment_status'] )
424
- );
425
- }
426
- $compound_service = '';
427
- if ( $customer['compound_service_id'] !== null ) {
428
- $service = new Lib\Entities\Service();
429
- if ( $service->load( $customer['compound_service_id'] ) ) {
430
- $compound_service = $service->getTranslatedTitle();
431
- }
432
- }
433
- $response['data']['customers'][] = array(
434
- 'id' => $customer['customer_id'],
435
- 'ca_id' => $customer['id'],
436
- 'package_id' => $customer['package_id'],
437
- 'compound_service' => $compound_service,
438
- 'compound_token' => $customer['compound_token'],
439
- 'custom_fields' => (array) json_decode( $customer['custom_fields'], true ),
440
- 'extras' => (array) json_decode( $customer['extras'], true ),
441
- 'number_of_persons' => $customer['number_of_persons'],
442
- 'notes' => $customer['notes'],
443
- 'payment_id' => $customer['payment_id'],
444
- 'payment_type' => $customer['payment'] != $customer['payment_total'] ? 'partial' : 'full',
445
- 'payment_title' => $payment_title,
446
- 'status' => $customer['status'],
447
- );
448
- }
449
- }
450
- wp_send_json( $response );
451
- }
452
-
453
- /**
454
- * Save appointment form (for both create and edit).
455
- */
456
- public function executeSaveAppointmentForm()
457
- {
458
- $response = array( 'success' => false );
459
-
460
- $appointment_id = (int) $this->getParameter( 'id', 0 );
461
- $staff_id = (int) $this->getParameter( 'staff_id', 0 );
462
- $service_id = (int) $this->getParameter( 'service_id', -1 );
463
- $custom_service_name = trim( $this->getParameter( 'custom_service_name' ) );
464
- $custom_service_price = trim( $this->getParameter( 'custom_service_price' ) );
465
- $location_id = (int) $this->getParameter( 'location_id', 0 );
466
- $start_date = $this->getParameter( 'start_date' );
467
- $end_date = $this->getParameter( 'end_date' );
468
- $repeat = json_decode( $this->getParameter( 'repeat', '[]' ), true );
469
- $schedule = $this->getParameter( 'schedule', array() );
470
- $customers = json_decode( $this->getParameter( 'customers', '[]' ), true );
471
- $notification = $this->getParameter( 'notification', 'no' );
472
- $internal_note = $this->getParameter( 'internal_note' );
473
- $created_from = $this->getParameter( 'created_from' );
474
-
475
- if ( ! $service_id ) {
476
- // Custom service.
477
- $service_id = null;
478
- }
479
- if ( $service_id || $custom_service_name == '' ) {
480
- $custom_service_name = null;
481
- }
482
- if ( $service_id || $custom_service_price == '' ) {
483
- $custom_service_price = null;
484
- }
485
- if ( ! $location_id ) {
486
- $location_id = null;
487
- }
488
-
489
- // Check for errors.
490
- if ( ! $start_date ) {
491
- $response['errors']['time_interval'] = __( 'Start time must not be empty', 'bookly' );
492
- } elseif ( ! $end_date ) {
493
- $response['errors']['time_interval'] = __( 'End time must not be empty', 'bookly' );
494
- } elseif ( $start_date == $end_date ) {
495
- $response['errors']['time_interval'] = __( 'End time must not be equal to start time', 'bookly' );
496
- }
497
- if ( $service_id == -1 ) {
498
- $response['errors']['service_required'] = true;
499
- } else if ( $service_id === null && $custom_service_name === null ) {
500
- $response['errors']['custom_service_name_required'] = true;
501
- }
502
- $total_number_of_persons = 0;
503
- $max_extras_duration = 0;
504
- foreach ( $customers as $i => $customer ) {
505
- if ( $customer['status'] == Lib\Entities\CustomerAppointment::STATUS_PENDING ||
506
- $customer['status'] == Lib\Entities\CustomerAppointment::STATUS_APPROVED
507
- ) {
508
- $total_number_of_persons += $customer['number_of_persons'];
509
- $extras_duration = Lib\Proxy\ServiceExtras::getTotalDuration( $customer['extras'] );
510
- if ( $extras_duration > $max_extras_duration ) {
511
- $max_extras_duration = $extras_duration;
512
- }
513
- }
514
- $customers[ $i ]['created_from'] = ( $created_from == 'backend' ) ? 'backend' : 'frontend';
515
- }
516
- if ( $service_id ) {
517
- $staff_service = new Lib\Entities\StaffService();
518
- $staff_service->loadBy( array(
519
- 'staff_id' => 1,
520
- 'service_id' => $service_id
521
- ) );
522
- if ( $total_number_of_persons > $staff_service->getCapacityMax() ) {
523
- $response['errors']['overflow_capacity'] = sprintf(
524
- __( 'The number of customers should not be more than %d', 'bookly' ),
525
- $staff_service->getCapacityMax()
526
- );
527
- }
528
- }
529
-
530
- // If no errors then try to save the appointment.
531
- if ( ! isset ( $response['errors'] ) ) {
532
- if ( $repeat['enabled'] ) {
533
- // Series.
534
- if ( ! empty ( $schedule ) ) {
535
- // Create new series.
536
- $series = new Lib\Entities\Series();
537
- $series
538
- ->setRepeat( $this->getParameter( 'repeat' ) )
539
- ->setToken( Lib\Utils\Common::generateToken( get_class( $series ), 'token' ) )
540
- ->save();
541
-
542
- if ( $notification != 'no' ) {
543
- // Create order per each customer to send notifications.
544
- /** @var DataHolders\Order[] $orders */
545
- $orders = array();
546
- foreach ( $customers as $customer ) {
547
- $order = DataHolders\Order::create( Lib\Entities\Customer::find( $customer['id'] ) )
548
- ->addItem( 0, DataHolders\Series::create( $series ) )
549
- ;
550
- $orders[ $customer['id'] ] = $order;
551
- }
552
- }
553
-
554
- $service = Lib\Entities\Service::find( $service_id );
555
-
556
- foreach ( $schedule as $slot ) {
557
- $slot = json_decode( $slot );
558
- $appointment = new Lib\Entities\Appointment();
559
- $appointment
560
- ->setSeries( $series )
561
- ->setLocationId( $location_id )
562
- ->setStaffId( $staff_id )
563
- ->setServiceId( $service_id )
564
- ->setStartDate( $slot[0][2] )
565
- ->setEndDate( date( 'Y-m-d H:i:s', strtotime( $slot[0][2] ) + $service->getDuration() ) )
566
- ->setInternalNote( $internal_note )
567
- ->setExtrasDuration( $max_extras_duration )
568
- ;
569
-
570
- if ( $appointment->save() !== false ) {
571
- // Save customer appointments.
572
- $ca_list = $appointment->saveCustomerAppointments( $customers );
573
- // Google Calendar.
574
- $appointment->handleGoogleCalendar();
575
- // Waiting list.
576
- Lib\Proxy\WaitingList::handleParticipantsChange( $appointment );
577
-
578
- if ( $notification != 'no' ) {
579
- foreach ( $ca_list as $ca ) {
580
- $item = DataHolders\Simple::create( $ca )
581
- ->setService( $service )
582
- ->setAppointment( $appointment )
583
- ;
584
- $orders[ $ca->getCustomerId() ]->getItem( 0 )->addItem( $item );
585
- }
586
- }
587
- }
588
- }
589
- if ( $notification != 'no' ) {
590
- foreach ( $orders as $order ) {
591
- Lib\Proxy\RecurringAppointments::sendRecurring( $order->getItem( 0 ), $order );
592
- }
593
- }
594
- }
595
- $response['success'] = true;
596
- $response['data'] = array( 'staffId' => 1 ); // make FullCalendar refetch events
597
- } else {
598
- // Single appointment.
599
- $appointment = new Lib\Entities\Appointment();
600
- if ( $appointment_id ) {
601
- // Edit.
602
- $appointment->load( $appointment_id );
603
- if ( $appointment->getStaffId() != $staff_id ) {
604
- $appointment->setStaffAny( 0 );
605
- }
606
- }
607
- $appointment
608
- ->setLocationId( $location_id )
609
- ->setStaffId( 1 )
610
- ->setServiceId( $service_id )
611
- ->setCustomServiceName( $custom_service_name )
612
- ->setCustomServicePrice( $custom_service_price )
613
- ->setStartDate( $start_date )
614
- ->setEndDate( $end_date )
615
- ->setInternalNote( $internal_note )
616
- ->setExtrasDuration( $max_extras_duration )
617
- ;
618
-
619
- if ( $appointment->save() !== false ) {
620
- // Save customer appointments.
621
- $ca_status_changed = $appointment->saveCustomerAppointments( $customers );
622
-
623
- // Google Calendar.
624
- $appointment->handleGoogleCalendar();
625
- // Waiting list.
626
- Lib\Proxy\WaitingList::handleParticipantsChange( $appointment );
627
-
628
- // Send notifications.
629
- if ( $notification == 'changed_status' ) {
630
- foreach ( $ca_status_changed as $ca ) {
631
- Lib\NotificationSender::sendSingle( DataHolders\Simple::create( $ca )->setAppointment( $appointment ) );
632
- }
633
- } else if ( $notification == 'all' ) {
634
- $ca_list = $appointment->getCustomerAppointments( true );
635
- foreach ( $ca_list as $ca ) {
636
- Lib\NotificationSender::sendSingle( DataHolders\Simple::create( $ca )->setAppointment( $appointment ) );
637
- }
638
- }
639
-
640
- $response['success'] = true;
641
- $response['data'] = $this->_getAppointmentForFC( $staff_id, $appointment->getId() );
642
- } else {
643
- $response['errors'] = array( 'db' => __( 'Could not save appointment in database.', 'bookly' ) );
644
- }
645
- }
646
- }
647
- update_user_meta( get_current_user_id(), 'bookly_appointment_form_send_notifications', $notification );
648
- wp_send_json( $response );
649
- }
650
-
651
- public function executeCheckAppointmentErrors()
652
- {
653
- $start_date = $this->getParameter( 'start_date' );
654
- $end_date = $this->getParameter( 'end_date' );
655
- $staff_id = (int) $this->getParameter( 'staff_id' );
656
- $service_id = (int) $this->getParameter( 'service_id' );
657
- $appointment_id = (int) $this->getParameter( 'appointment_id' );
658
- $timestamp_diff = strtotime( $end_date ) - strtotime( $start_date );
659
- $customers = json_decode( $this->getParameter( 'customers', '[]' ), true );
660
-
661
- $result = array(
662
- 'date_interval_not_available' => false,
663
- 'date_interval_warning' => false,
664
- 'customers_appointments_limit' => array(),
665
- );
666
-
667
- $max_extras_duration = 0;
668
- foreach ( $customers as $customer ) {
669
- if ( $customer['status'] == Lib\Entities\CustomerAppointment::STATUS_PENDING ||
670
- $customer['status'] == Lib\Entities\CustomerAppointment::STATUS_APPROVED
671
- ) {
672
- $extras_duration = Lib\Proxy\ServiceExtras::getTotalDuration( $customer['extras'] );
673
- if ( $extras_duration > $max_extras_duration ) {
674
- $max_extras_duration = $extras_duration;
675
- }
676
- }
677
- }
678
-
679
- $total_end_date = $end_date;
680
- if ( $max_extras_duration > 0 ) {
681
- $total_end_date = date_create( $end_date )->modify( '+' . $max_extras_duration . ' sec' )->format( 'Y-m-d H:i:s' );
682
- }
683
- if ( ! $this->dateIntervalIsAvailableForAppointment( $start_date, $total_end_date, 1, $appointment_id ) ) {
684
- $result['date_interval_not_available'] = true;
685
- }
686
-
687
- if ( $service_id ) {
688
- $service = new Lib\Entities\Service();
689
- $service->load( $service_id );
690
-
691
- $duration = $service->getDuration();
692
-
693
- // Service duration interval is not equal to.
694
- $result['date_interval_warning'] = ( $timestamp_diff != $duration );
695
-
696
- // Check customers for appointments limit
697
- if ( $start_date ) {
698
- foreach ( $customers as $index => $customer ) {
699
- if ( $service->appointmentsLimitReached( $customer['id'], $start_date ) ) {
700
- $customer_error = Lib\Entities\Customer::find( $customer['id'] );
701
- $result['customers_appointments_limit'][] = sprintf( __( '%s has reached the limit of bookings for this service', 'bookly' ), $customer_error->getFullName() );
702
- }
703
- }
704
- }
705
- }
706
-
707
- wp_send_json( $result );
708
- }
709
-
710
- /**
711
- * Delete single appointment.
712
- */
713
- public function executeDeleteAppointment()
714
- {
715
- $appointment_id = $this->getParameter( 'appointment_id' );
716
- $reason = $this->getParameter( 'reason' );
717
-
718
- if ( $this->getParameter( 'notify' ) ) {
719
- $ca_list = Lib\Entities\CustomerAppointment::query()
720
- ->where( 'appointment_id', $appointment_id )
721
- ->find();
722
- /** @var Lib\Entities\CustomerAppointment $ca */
723
- foreach ( $ca_list as $ca ) {
724
- switch ( $ca->getStatus() ) {
725
- case Lib\Entities\CustomerAppointment::STATUS_PENDING:
726
- case Lib\Entities\CustomerAppointment::STATUS_WAITLISTED:
727
- $ca->setStatus( Lib\Entities\CustomerAppointment::STATUS_REJECTED );
728
- break;
729
- case Lib\Entities\CustomerAppointment::STATUS_APPROVED:
730
- $ca->setStatus( Lib\Entities\CustomerAppointment::STATUS_CANCELLED );
731
- break;
732
- }
733
- Lib\NotificationSender::sendSingle(
734
- DataHolders\Simple::create( $ca ),
735
- null,
736
- array( 'cancellation_reason' => $reason )
737
- );
738
- }
739
- }
740
-
741
- Lib\Entities\Appointment::find( $appointment_id )->delete();
742
-
743
- wp_send_json_success();
744
- }
745
-
746
- /**
747
- * @param $start_date
748
- * @param $end_date
749
- * @param $staff_id
750
- * @param $appointment_id
751
- * @return bool
752
- */
753
- private function dateIntervalIsAvailableForAppointment( $start_date, $end_date, $staff_id, $appointment_id )
754
- {
755
- return Lib\Entities\Appointment::query( 'a' )
756
- ->whereNot( 'a.id', $appointment_id )
757
- ->where( 'a.staff_id', $staff_id )
758
- ->whereLt( 'a.start_date', $end_date )
759
- ->whereGt( 'a.end_date', $start_date )
760
- ->count() == 0;
761
- }
762
-
763
- /**
764
- * Get appointments for FullCalendar.
765
- *
766
- * @param integer $staff_id
767
- * @param \DateTime $start_date
768
- * @param \DateTime $end_date
769
- * @return array
770
- */
771
- private function _getAppointmentsForFC( $staff_id, \DateTime $start_date, \DateTime $end_date )
772
- {
773
- $query = Lib\Entities\Appointment::query( 'a' )
774
- ->where( 'st.id', $staff_id )
775
- ->whereBetween( 'DATE(a.start_date)', $start_date->format( 'Y-m-d' ), $end_date->format( 'Y-m-d' ) );
776
-
777
- return $this->_buildAppointmentsForFC( $staff_id, $query );
778
- }
779
-
780
- /**
781
- * Get appointment for FullCalendar.
782
- *
783
- * @param integer $staff_id
784
- * @param int $appointment_id
785
- * @return array
786
- */
787
- private function _getAppointmentForFC( $staff_id, $appointment_id )
788
- {
789
- $query = Lib\Entities\Appointment::query( 'a' )
790
- ->where( 'a.id', $appointment_id );
791
-
792
- $appointments = $this->_buildAppointmentsForFC( $staff_id, $query );
793
-
794
- return $appointments[0];
795
- }
796
-
797
- /**
798
- * Build appointments for FullCalendar.
799
- *
800
- * @param integer $staff_id
801
- * @param Lib\Query $query
802
- * @return mixed
803
- */
804
- private function _buildAppointmentsForFC( $staff_id, Lib\Query $query )
805
- {
806
- $one_participant = '<div>' . str_replace( "\n", '</div><div>', get_option( 'bookly_cal_one_participant' ) ) . '</div>';
807
- $many_participants = '<div>' . str_replace( "\n", '</div><div>', get_option( 'bookly_cal_many_participants' ) ) . '</div>';
808
- $postfix_any = sprintf( ' (%s)', get_option( 'bookly_l10n_option_employee' ) );
809
- $participants = null;
810
- $default_codes = array(
811
- '{amount_due}' => '',
812
- '{amount_paid}' => '',
813
- '{appointment_date}' => '',
814
- '{appointment_time}' => '',
815
- '{booking_number}' => '',
816
- '{category_name}' => '',
817
- '{client_email}' => '',
818
- '{client_name}' => '',
819
- '{client_first_name}' => '',
820
- '{client_last_name}' => '',
821
- '{client_phone}' => '',
822
- '{company_address}' => get_option( 'bookly_co_address' ),
823
- '{company_name}' => get_option( 'bookly_co_name' ),
824
- '{company_phone}' => get_option( 'bookly_co_phone' ),
825
- '{company_website}' => get_option( 'bookly_co_website' ),
826
- '{custom_fields}' => '',
827
- '{extras}' => '',
828
- '{extras_total_price}'=> 0,
829
- '{location_name}' => '',
830
- '{location_info}' => '',
831
- '{on_waiting_list}' => '',
832
- '{payment_status}' => '',
833
- '{payment_type}' => '',
834
- '{service_capacity}' => '',
835
- '{service_info}' => '',
836
- '{service_name}' => '',
837
- '{service_price}' => '',
838
- '{signed_up}' => '',
839
- '{staff_email}' => '',
840
- '{staff_info}' => '',
841
- '{staff_name}' => '',
842
- '{staff_phone}' => '',
843
- '{status}' => '',
844
- '{total_price}' => '',
845
- );
846
- $appointments = $query
847
- ->select( 'a.id, a.series_id, a.staff_any, a.location_id, a.start_date, DATE_ADD(a.end_date, INTERVAL a.extras_duration SECOND) AS end_date,
848
- COALESCE(s.title,a.custom_service_name) AS service_name, COALESCE(s.color,"silver") AS service_color, s.info AS service_info,
849
- 1 AS service_capacity, COALESCE(ss.price,a.custom_service_price) AS service_price,
850
- st.full_name AS staff_name, st.email AS staff_email, st.info AS staff_info, st.phone AS staff_phone,
851
- (SELECT SUM(ca.number_of_persons) FROM ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca WHERE ca.appointment_id = a.id) AS total_number_of_persons,
852
- ca.number_of_persons,
853
- ca.custom_fields,
854
- ca.status AS appointment_status,
855
- ca.extras,
856
- ca.package_id,
857
- ct.name AS category_name,
858
- c.full_name AS client_name, c.first_name AS client_first_name, c.last_name AS client_last_name, c.phone AS client_phone, c.email AS client_email, c.id AS customer_id,
859
- p.total, p.type AS payment_gateway, p.status AS payment_status, p.paid,
860
- (SELECT SUM(ca.number_of_persons) FROM ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca WHERE ca.appointment_id = a.id AND ca.status = "waitlisted") AS on_waiting_list' )
861
- ->leftJoin( 'CustomerAppointment', 'ca', 'ca.appointment_id = a.id' )
862
- ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
863
- ->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
864
- ->leftJoin( 'Service', 's', 's.id = a.service_id' )
865
- ->leftJoin( 'Category', 'ct', 'ct.id = s.category_id' )
866
- ->leftJoin( 'Staff', 'st', 'st.id = a.staff_id' )
867
- ->leftJoin( 'StaffService', 'ss', 'ss.staff_id = a.staff_id AND ss.service_id = a.service_id' )
868
- ->groupBy( 'a.id' )
869
- ->fetchArray();
870
-
871
- foreach ( $appointments as $key => $appointment ) {
872
- $codes = $default_codes;
873
- $codes['{appointment_date}'] = Lib\Utils\DateTime::formatDate( $appointment['start_date'] );
874
- $codes['{appointment_time}'] = Lib\Utils\DateTime::formatTime( $appointment['start_date'] );
875
- $codes['{booking_number}'] = $appointment['id'];
876
- $codes['{on_waiting_list}'] = $appointment['on_waiting_list'];
877
- $codes['{service_name}'] = $appointment['service_name'] ? esc_html( $appointment['service_name'] ) : __( 'Untitled', 'bookly' );
878
- $codes['{service_price}'] = Lib\Utils\Price::format( $appointment['service_price'] );
879
- $codes['{signed_up}'] = $appointment['total_number_of_persons'];
880
- foreach ( array( 'staff_name', 'staff_phone', 'staff_info', 'staff_email', 'service_info', 'service_capacity', 'category_name' ) as $field ) {
881
- $codes[ '{' . $field . '}' ] = esc_html( $appointment[ $field ] );
882
- }
883
- if ( $appointment['staff_any'] ) {
884
- $codes['{staff_name}'] .= $postfix_any;
885
- }
886
- // Display customer information only if there is 1 customer. Don't confuse with number_of_persons.
887
- if ( $appointment['number_of_persons'] == $appointment['total_number_of_persons'] ) {
888
- $participants = 'one';
889
- $template = $one_participant;
890
- foreach ( array( 'client_name', 'client_first_name', 'client_last_name', 'client_phone', 'client_email' ) as $data_entry ) {
891
- if ( $appointment[ $data_entry ] ) {
892
- $codes[ '{' . $data_entry . '}' ] = esc_html( $appointment[ $data_entry ] );
893
- }
894
- }
895
-
896
- // Payment.
897
- if ( $appointment['total'] ) {
898
- $codes['{total_price}'] = Lib\Utils\Price::format( $appointment['total'] );
899
- $codes['{amount_paid}'] = Lib\Utils\Price::format( $appointment['paid'] );
900
- $codes['{amount_due}'] = Lib\Utils\Price::format( $appointment['total'] - $appointment['paid'] );
901
- $codes['{total_price}'] = Lib\Utils\Price::format( $appointment['total'] );
902
- $codes['{payment_type}'] = Lib\Entities\Payment::typeToString( $appointment['payment_gateway'] );
903
- $codes['{payment_status}'] = Lib\Entities\Payment::statusToString( $appointment['payment_status'] );
904
- }
905
- // Status.
906
- $codes['{status}'] = Lib\Entities\CustomerAppointment::statusToString( $appointment['appointment_status'] );
907
- } else {
908
- $participants = 'many';
909
- $template = $many_participants;
910
- }
911
-
912
- $codes = Lib\Proxy\Shared::prepareCalendarAppointmentCodesData( $codes, $appointment, $participants );
913
-
914
- $appointments[ $key ] = array(
915
- 'id' => $appointment['id'],
916
- 'start' => $appointment['start_date'],
917
- 'end' => $appointment['end_date'],
918
- 'title' => ' ',
919
- 'desc' => strtr( $template, $codes ),
920
- 'color' => $appointment['service_color'],
921
- 'staffId' => $staff_id,
922
- 'series_id' => (int) $appointment['series_id'],
923
- 'package_id' => (int) $appointment['package_id'],
924
- 'waitlisted' => (int) $appointment['on_waiting_list'],
925
- 'staff_any' => (int) $appointment['staff_any'],
926
- );
927
- }
928
-
929
- return $appointments;
930
- }
931
-
932
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/calendar/Page.php ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Calendar;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Page
8
+ * @package Bookly\Backend\Modules\Calendar
9
+ */
10
+ class Page extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * Render page.
14
+ */
15
+ public static function render()
16
+ {
17
+ /** @var \WP_Locale $wp_locale */
18
+ global $wp_locale;
19
+
20
+ self::enqueueStyles( array(
21
+ 'module' => array( 'css/fullcalendar.min.css', ),
22
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css' ),
23
+ ) );
24
+
25
+ self::enqueueScripts( array(
26
+ 'backend' => array(
27
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
28
+ 'js/alert.js' => array( 'jquery' ),
29
+ ),
30
+ 'module' => array(
31
+ 'js/fullcalendar.min.js' => array( 'bookly-moment.min.js' ),
32
+ 'js/fc-multistaff-view.js' => array( 'bookly-fullcalendar.min.js' ),
33
+ 'js/calendar-common.js' => array( 'bookly-fc-multistaff-view.js' ),
34
+ 'js/calendar.js' => array( 'bookly-calendar-common.js' ),
35
+ ),
36
+ ) );
37
+
38
+ $slot_length_minutes = get_option( 'bookly_gen_time_slot_length', '15' );
39
+ $slot = new \DateInterval( 'PT' . $slot_length_minutes . 'M' );
40
+
41
+ wp_localize_script( 'bookly-calendar.js', 'BooklyL10n', array(
42
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
43
+ 'slotDuration' => $slot->format( '%H:%I:%S' ),
44
+ 'calendar' => array(
45
+ 'shortMonths' => array_values( $wp_locale->month_abbrev ),
46
+ 'longMonths' => array_values( $wp_locale->month ),
47
+ 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
48
+ 'longDays' => array_values( $wp_locale->weekday ),
49
+ ),
50
+ 'dpDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_JQUERY_DATEPICKER ),
51
+ 'mjsDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
52
+ 'mjsTimeFormat' => Lib\Utils\DateTime::convertFormat( 'time', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
53
+ 'today' => __( 'Today', 'bookly' ),
54
+ 'week' => __( 'Week', 'bookly' ),
55
+ 'day' => __( 'Day', 'bookly' ),
56
+ 'month' => __( 'Month', 'bookly' ),
57
+ 'allDay' => __( 'All Day', 'bookly' ),
58
+ 'delete' => __( 'Delete', 'bookly' ),
59
+ 'noStaffSelected' => __( 'No staff selected', 'bookly' ),
60
+ 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
61
+ 'startOfWeek' => (int) get_option( 'start_of_week' ),
62
+ 'recurring_appointments' => array(
63
+ 'active' => (int) Lib\Config::recurringAppointmentsActive(),
64
+ 'title' => __( 'Recurring appointments', 'bookly' ),
65
+ ),
66
+ 'waiting_list' => array(
67
+ 'active' => (int) Lib\Config::waitingListActive(),
68
+ 'title' => __( 'On waiting list', 'bookly' ),
69
+ ),
70
+ 'packages' => array(
71
+ 'active' => (int) Lib\Config::packagesActive(),
72
+ 'title' => __( 'Package', 'bookly' ),
73
+ ),
74
+ ) );
75
+ $staff = Lib\Entities\Staff::query()->findOne();
76
+ $staff_members = $staff ? Lib\Proxy\Pro::prepareStaffMembers( array( $staff ) ) : array();
77
+
78
+ self::renderTemplate( 'calendar', compact( 'staff_members' ) );
79
+ }
80
+
81
+ /**
82
+ * Build appointments for FullCalendar.
83
+ *
84
+ * @param integer $staff_id
85
+ * @param Lib\Query $query
86
+ * @return mixed
87
+ */
88
+ public static function buildAppointmentsForFC( $staff_id, Lib\Query $query )
89
+ {
90
+ $one_participant = '<div>' . str_replace( "\n", '</div><div>', get_option( 'bookly_cal_one_participant' ) ) . '</div>';
91
+ $many_participants = '<div>' . str_replace( "\n", '</div><div>', get_option( 'bookly_cal_many_participants' ) ) . '</div>';
92
+ $postfix_any = sprintf( ' (%s)', get_option( 'bookly_l10n_option_employee' ) );
93
+ $participants = null;
94
+ $default_codes = array(
95
+ '{amount_due}' => '',
96
+ '{amount_paid}' => '',
97
+ '{appointment_date}' => '',
98
+ '{appointment_time}' => '',
99
+ '{booking_number}' => '',
100
+ '{category_name}' => '',
101
+ '{client_email}' => '',
102
+ '{client_name}' => '',
103
+ '{client_first_name}' => '',
104
+ '{client_last_name}' => '',
105
+ '{client_phone}' => '',
106
+ '{company_address}' => get_option( 'bookly_co_address' ),
107
+ '{company_name}' => get_option( 'bookly_co_name' ),
108
+ '{company_phone}' => get_option( 'bookly_co_phone' ),
109
+ '{company_website}' => get_option( 'bookly_co_website' ),
110
+ '{custom_fields}' => '',
111
+ '{extras}' => '',
112
+ '{extras_total_price}'=> 0,
113
+ '{location_name}' => '',
114
+ '{location_info}' => '',
115
+ '{on_waiting_list}' => '',
116
+ '{payment_status}' => '',
117
+ '{payment_type}' => '',
118
+ '{service_capacity}' => '',
119
+ '{service_info}' => '',
120
+ '{service_name}' => '',
121
+ '{service_price}' => '',
122
+ '{signed_up}' => '',
123
+ '{staff_email}' => '',
124
+ '{staff_info}' => '',
125
+ '{staff_name}' => '',
126
+ '{staff_phone}' => '',
127
+ '{status}' => '',
128
+ '{total_price}' => '',
129
+ );
130
+ $query
131
+ ->select( 'a.id, a.series_id, a.staff_any, a.location_id, a.start_date, DATE_ADD(a.end_date, INTERVAL a.extras_duration SECOND) AS end_date,
132
+ COALESCE(s.title,a.custom_service_name) AS service_name, COALESCE(s.color,"silver") AS service_color, s.info AS service_info,
133
+ COALESCE(ss.price,a.custom_service_price) AS service_price,
134
+ st.full_name AS staff_name, st.email AS staff_email, st.info AS staff_info, st.phone AS staff_phone,
135
+ (SELECT SUM(ca.number_of_persons) FROM ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca WHERE ca.appointment_id = a.id) AS total_number_of_persons,
136
+ s.duration,
137
+ s.start_time_info,
138
+ s.end_time_info,
139
+ ca.number_of_persons,
140
+ ca.units,
141
+ ca.custom_fields,
142
+ ca.status AS appointment_status,
143
+ ca.extras,
144
+ ca.package_id,
145
+ ct.name AS category_name,
146
+ c.full_name AS client_name, c.first_name AS client_first_name, c.last_name AS client_last_name, c.phone AS client_phone, c.email AS client_email, c.id AS customer_id,
147
+ p.total, p.type AS payment_gateway, p.status AS payment_status, p.paid,
148
+ (SELECT SUM(ca.number_of_persons) FROM ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca WHERE ca.appointment_id = a.id AND ca.status = "waitlisted") AS on_waiting_list' )
149
+ ->leftJoin( 'CustomerAppointment', 'ca', 'ca.appointment_id = a.id' )
150
+ ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
151
+ ->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
152
+ ->leftJoin( 'Service', 's', 's.id = a.service_id' )
153
+ ->leftJoin( 'Category', 'ct', 'ct.id = s.category_id' )
154
+ ->leftJoin( 'Staff', 'st', 'st.id = a.staff_id' )
155
+ ->leftJoin( 'StaffService', 'ss', 'ss.staff_id = a.staff_id AND ss.service_id = a.service_id' )
156
+ ->groupBy( 'a.id' );
157
+
158
+ if ( Lib\Config::groupBookingActive() ) {
159
+ $query->addSelect( 'COALESCE(ss.capacity_max,9999) AS service_capacity' );
160
+ } else {
161
+ $query->addSelect( '1 AS service_capacity' );
162
+ }
163
+
164
+ $appointments = $query->fetchArray();
165
+
166
+ foreach ( $appointments as $key => $appointment ) {
167
+ $codes = $default_codes;
168
+ $codes['{appointment_date}'] = Lib\Utils\DateTime::formatDate( $appointment['start_date'] );
169
+ $codes['{appointment_time}'] = $appointment['duration'] >= DAY_IN_SECONDS ? $appointment['start_time_info'] : Lib\Utils\DateTime::formatTime( $appointment['start_date'] );
170
+ $codes['{booking_number}'] = $appointment['id'];
171
+ $codes['{on_waiting_list}'] = $appointment['on_waiting_list'];
172
+ $codes['{service_name}'] = $appointment['service_name'] ? esc_html( $appointment['service_name'] ) : __( 'Untitled', 'bookly' );
173
+ $codes['{service_price}'] = Lib\Utils\Price::format( $appointment['service_price'] * $appointment['units'] );
174
+ $codes['{signed_up}'] = $appointment['total_number_of_persons'];
175
+ foreach ( array( 'staff_name', 'staff_phone', 'staff_info', 'staff_email', 'service_info', 'service_capacity', 'category_name' ) as $field ) {
176
+ $codes[ '{' . $field . '}' ] = esc_html( $appointment[ $field ] );
177
+ }
178
+ if ( $appointment['staff_any'] ) {
179
+ $codes['{staff_name}'] .= $postfix_any;
180
+ }
181
+ // Display customer information only if there is 1 customer. Don't confuse with number_of_persons.
182
+ if ( $appointment['number_of_persons'] == $appointment['total_number_of_persons'] ) {
183
+ $participants = 'one';
184
+ $template = $one_participant;
185
+ foreach ( array( 'client_name', 'client_first_name', 'client_last_name', 'client_phone', 'client_email' ) as $data_entry ) {
186
+ if ( $appointment[ $data_entry ] ) {
187
+ $codes[ '{' . $data_entry . '}' ] = esc_html( $appointment[ $data_entry ] );
188
+ }
189
+ }
190
+
191
+ // Payment.
192
+ if ( $appointment['total'] ) {
193
+ $codes['{total_price}'] = Lib\Utils\Price::format( $appointment['total'] );
194
+ $codes['{amount_paid}'] = Lib\Utils\Price::format( $appointment['paid'] );
195
+ $codes['{amount_due}'] = Lib\Utils\Price::format( $appointment['total'] - $appointment['paid'] );
196
+ $codes['{total_price}'] = Lib\Utils\Price::format( $appointment['total'] );
197
+ $codes['{payment_type}'] = Lib\Entities\Payment::typeToString( $appointment['payment_gateway'] );
198
+ $codes['{payment_status}'] = Lib\Entities\Payment::statusToString( $appointment['payment_status'] );
199
+ }
200
+ // Status.
201
+ $codes['{status}'] = Lib\Entities\CustomerAppointment::statusToString( $appointment['appointment_status'] );
202
+ } else {
203
+ $participants = 'many';
204
+ $template = $many_participants;
205
+ }
206
+
207
+ $codes = Proxy\Shared::prepareAppointmentCodesData( $codes, $appointment, $participants );
208
+
209
+ $appointments[ $key ] = array(
210
+ 'id' => $appointment['id'],
211
+ 'start' => $appointment['start_date'],
212
+ 'end' => $appointment['end_date'],
213
+ 'title' => ' ',
214
+ 'desc' => strtr( $template, $codes ),
215
+ 'color' => $appointment['service_color'],
216
+ 'staffId' => $staff_id,
217
+ 'series_id' => (int) $appointment['series_id'],
218
+ 'package_id' => (int) $appointment['package_id'],
219
+ 'waitlisted' => (int) $appointment['on_waiting_list'],
220
+ 'staff_any' => (int) $appointment['staff_any'],
221
+ );
222
+ if ( $appointment['duration'] * $appointment['units'] >= DAY_IN_SECONDS && $appointment['start_time_info'] ) {
223
+ $appointments[ $key ]['header_text'] = sprintf( '%s - %s', $appointment['start_time_info'], $appointment['end_time_info'] );
224
+ }
225
+ }
226
+
227
+ return $appointments;
228
+ }
229
+ }
backend/modules/calendar/proxy/AdvancedGoogleCalendar.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Calendar\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class AdvancedGoogleCalendar
8
+ * @package Bookly\Backend\Modules\Calendar\Proxy
9
+ *
10
+ * @method static void renderSyncButton( array $staff_members ) Render Google Calendar sync button.
11
+ */
12
+ abstract class AdvancedGoogleCalendar extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/calendar/proxy/Locations.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Calendar\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Locations
8
+ * @package Bookly\Backend\Modules\Calendar\Proxy
9
+ *
10
+ * @method static void renderCalendarLocationFilter()
11
+ */
12
+ abstract class Locations extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/calendar/proxy/Shared.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Calendar\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Shared
8
+ * @package Bookly\Backend\Modules\Calendar\Proxy
9
+ *
10
+ * @method static array prepareAppointmentCodesData( array $codes, array $appointment_data, string $participants ) Prepare codes data for appointment description displayed in calendar.
11
+ * @method static void prepareAppointmentsQueryForFC( Lib\Query $query, int $staff_id, \DateTime $start_date, \DateTime $end_date ) Prepare appointments query for full calendar
12
+ * @method static void renderAddOnsComponents() Render components on calendar page.
13
+ */
14
+ abstract class Shared extends Lib\Base\Proxy
15
+ {
16
+
17
+ }
backend/modules/calendar/resources/js/calendar-common.js CHANGED
@@ -33,7 +33,7 @@ jQuery(function ($) {
33
  url: ajaxurl,
34
  data: {
35
  action: 'bookly_get_staff_appointments',
36
- csrf_token : obj.options.l10n.csrf_token,
37
  staff_ids: function () {
38
  var ids = [];
39
  if (obj.options.is_backend && obj.options.getCurrentStaffId() == 0) {
@@ -45,6 +45,20 @@ jQuery(function ($) {
45
  ids.push(obj.options.getCurrentStaffId());
46
  }
47
  return ids;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
  }
50
  }],
@@ -88,17 +102,24 @@ jQuery(function ($) {
88
  $container.fullCalendar('refetchEvents');
89
  } else {
90
  if (visible_staff_id == event.staffId || visible_staff_id == 0) {
91
- if (event.id) {
92
- // Create event in calendar.
93
- $container.fullCalendar('renderEvent', event);
94
- } else {
95
- $container.fullCalendar('refetchEvents');
 
 
96
  }
97
  } else {
98
  // Switch to the event owner tab.
99
  jQuery('li[data-staff_id=' + event.staffId + ']').click();
100
  }
101
  }
 
 
 
 
 
102
  }
103
  );
104
  },
@@ -111,6 +132,9 @@ jQuery(function ($) {
111
  }
112
 
113
  var $time = $event.find('.fc-time');
 
 
 
114
  if (obj.options.l10n.recurring_appointments.active == '1' && calEvent.series_id) {
115
  $time.prepend(
116
  $('<a class="bookly-fc-icon dashicons dashicons-admin-links"></a>')
@@ -175,15 +199,22 @@ jQuery(function ($) {
175
  if (event == 'refresh') {
176
  $container.fullCalendar('refetchEvents');
177
  } else {
178
- if (visible_staff_id == event.staffId || visible_staff_id == 0) {
179
- // Update event in calendar.
180
- jQuery.extend(calEvent, event);
181
- $container.fullCalendar('updateEvent', calEvent);
182
- } else {
183
- // Switch to the event owner tab.
184
- jQuery('li[data-staff_id=' + event.staffId + ']').click();
 
 
185
  }
186
  }
 
 
 
 
 
187
  }
188
  );
189
  },
@@ -238,7 +269,9 @@ jQuery(function ($) {
238
  /**
239
  * On delete appointment click.
240
  */
241
- if (obj.$deleteDialog.data('events') == undefined) {
 
 
242
  obj.$deleteDialog.on('click', '#bookly-delete', function (e) {
243
  var calEvent = obj.$deleteDialog.data('calEvent'),
244
  ladda = Ladda.create(this);
@@ -266,6 +299,11 @@ jQuery(function ($) {
266
  }
267
  };
268
 
 
 
 
 
 
269
  Calendar.prototype.$deleteDialog = $('#bookly-delete-dialog');
270
  Calendar.prototype.options = {
271
  fullcalendar: {},
33
  url: ajaxurl,
34
  data: {
35
  action: 'bookly_get_staff_appointments',
36
+ csrf_token: obj.options.l10n.csrf_token,
37
  staff_ids: function () {
38
  var ids = [];
39
  if (obj.options.is_backend && obj.options.getCurrentStaffId() == 0) {
45
  ids.push(obj.options.getCurrentStaffId());
46
  }
47
  return ids;
48
+ },
49
+ location_ids: function() {
50
+ if (obj.options.is_backend) {
51
+ var ids = [];
52
+ $('.bookly-js-locations-filter input').each(function () {
53
+ var input = $(this);
54
+ if (input.prop('checked')) {
55
+ ids.push(input.val());
56
+ }
57
+ });
58
+ return ids;
59
+ } else {
60
+ return ['all'];
61
+ }
62
  }
63
  }
64
  }],
102
  $container.fullCalendar('refetchEvents');
103
  } else {
104
  if (visible_staff_id == event.staffId || visible_staff_id == 0) {
105
+ if (event.start !== null) {
106
+ if (event.id) {
107
+ // Create event in calendar.
108
+ $container.fullCalendar('renderEvent', event);
109
+ } else {
110
+ $container.fullCalendar('refetchEvents');
111
+ }
112
  }
113
  } else {
114
  // Switch to the event owner tab.
115
  jQuery('li[data-staff_id=' + event.staffId + ']').click();
116
  }
117
  }
118
+
119
+ if (locationChanged) {
120
+ $container.fullCalendar('refetchEvents');
121
+ locationChanged = false;
122
+ }
123
  }
124
  );
125
  },
132
  }
133
 
134
  var $time = $event.find('.fc-time');
135
+ if (calEvent.header_text !== undefined) {
136
+ $time.html(calEvent.header_text);
137
+ }
138
  if (obj.options.l10n.recurring_appointments.active == '1' && calEvent.series_id) {
139
  $time.prepend(
140
  $('<a class="bookly-fc-icon dashicons dashicons-admin-links"></a>')
199
  if (event == 'refresh') {
200
  $container.fullCalendar('refetchEvents');
201
  } else {
202
+ if (event.start !== null) {
203
+ if (visible_staff_id == event.staffId || visible_staff_id == 0) {
204
+ // Update event in calendar.
205
+ jQuery.extend(calEvent, event);
206
+ $container.fullCalendar('updateEvent', calEvent);
207
+ } else {
208
+ // Switch to the event owner tab.
209
+ jQuery('li[data-staff_id=' + event.staffId + ']').click();
210
+ }
211
  }
212
  }
213
+
214
+ if (locationChanged) {
215
+ $container.fullCalendar('refetchEvents');
216
+ locationChanged = false;
217
+ }
218
  }
219
  );
220
  },
269
  /**
270
  * On delete appointment click.
271
  */
272
+ if (obj.$deleteDialog.data('events') == undefined
273
+ || obj.$deleteDialog.data('events').click == undefined)
274
+ {
275
  obj.$deleteDialog.on('click', '#bookly-delete', function (e) {
276
  var calEvent = obj.$deleteDialog.data('calEvent'),
277
  ladda = Ladda.create(this);
299
  }
300
  };
301
 
302
+ var locationChanged = false;
303
+ $('body').on('change', '#bookly-appointment-location', function() {
304
+ locationChanged = true;
305
+ });
306
+
307
  Calendar.prototype.$deleteDialog = $('#bookly-delete-dialog');
308
  Calendar.prototype.options = {
309
  fullcalendar: {},
backend/modules/calendar/resources/js/calendar.js CHANGED
@@ -5,6 +5,7 @@ jQuery(function ($) {
5
  $staff = $('input.bookly-js-check-entity'),
6
  $showAll = $('input#bookly-check-all-entities'),
7
  firstHour = new Date().getHours(),
 
8
  $staffButton = $('#bookly-staff-button'),
9
  staffMembers = [],
10
  staffIds = getCookie('bookly_cal_st_ids'),
@@ -27,7 +28,7 @@ jQuery(function ($) {
27
  $tabs.filter('[data-staff_id=' + value + ']').show();
28
  });
29
  } else {
30
- $('.dropdown-toggle').dropdown('toggle');
31
  }
32
 
33
  $tabs.filter('[data-staff_id=' + tabId + ']').addClass('active');
@@ -43,23 +44,9 @@ jQuery(function ($) {
43
  * @return {number}
44
  */
45
  function heightFC() {
46
- var window_height = $(window).height(),
47
- wp_admin_bar_height = $('#wpadminbar').height(),
48
- bookly_calendar_tabs_height = $('#bookly-fc-wrapper .tabbable').outerHeight(true),
49
- height_to_reduce = wp_admin_bar_height + bookly_calendar_tabs_height,
50
- $wrap = $('#wpbody-content .wrap');
51
-
52
- if ($wrap.css('margin-top')) {
53
- height_to_reduce += parseInt($wrap.css('margin-top').replace('px', ''), 10);
54
- }
55
-
56
- if ($wrap.css('margin-bottom')) {
57
- height_to_reduce += parseInt($wrap.css('margin-bottom').replace('px', ''), 10);
58
- }
59
 
60
- var res = window_height - height_to_reduce - 130;
61
-
62
- return res > 620 ? res : 620;
63
  }
64
 
65
  var options = {
@@ -95,6 +82,117 @@ jQuery(function ($) {
95
  l10n: BooklyL10n
96
  };
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  var calendar = new BooklyCalendar($fullCalendar, options);
99
 
100
  $('.fc-agendaDay-button').addClass('fc-corner-right');
@@ -210,4 +308,24 @@ jQuery(function ($) {
210
  return keyValue ? keyValue[2] : null;
211
  }
212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  });
5
  $staff = $('input.bookly-js-check-entity'),
6
  $showAll = $('input#bookly-check-all-entities'),
7
  firstHour = new Date().getHours(),
8
+ $syncButton = $('#bookly-google-calendar-sync'),
9
  $staffButton = $('#bookly-staff-button'),
10
  staffMembers = [],
11
  staffIds = getCookie('bookly_cal_st_ids'),
28
  $tabs.filter('[data-staff_id=' + value + ']').show();
29
  });
30
  } else {
31
+ $('.bookly-js-staff-filter.dropdown-toggle').dropdown('toggle');
32
  }
33
 
34
  $tabs.filter('[data-staff_id=' + tabId + ']').addClass('active');
44
  * @return {number}
45
  */
46
  function heightFC() {
47
+ var height = $(window).height() - $('#bookly-fc-wrapper').offset().top - 20;
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
+ return height > 620 ? height : 620;
 
 
50
  }
51
 
52
  var options = {
82
  l10n: BooklyL10n
83
  };
84
 
85
+ /**
86
+ * @param {jQuery} $container
87
+ * @param {Object} callbacks
88
+ */
89
+ function initLocationFilter($container, callbacks)
90
+ {
91
+ var elements = {};
92
+
93
+ var initElements = function() {
94
+ elements.toggleButton = $container.find('.bookly-js-toggle-button');
95
+ elements.title = $container.find('.bookly-js-locations-title');
96
+
97
+ elements.items = $container.find('input[type=checkbox]');
98
+ elements.itemAllLocations = elements.items.filter('.bookly-js-all-locations');
99
+ elements.itemNoLocations = elements.items.filter('.bookly-js-no-locations');
100
+ elements.locationItems = elements.items.filter('.bookly-js-locations-item');
101
+ };
102
+
103
+ var restoreItemsFromCookie = function () {
104
+ var selectedValues = getCookie('bookly_cal_location_ids');
105
+
106
+ if (!selectedValues) {
107
+ return;
108
+ }
109
+
110
+ selectedValues = selectedValues.split(',');
111
+
112
+ if (selectedValues.indexOf('all') !== -1) {
113
+ elements.locationItems.prop('checked', true);
114
+ } else {
115
+ selectedValues.forEach(function (value) {
116
+ elements.locationItems.filter('[value=' + value + ']').prop('checked', true);
117
+ });
118
+ }
119
+ };
120
+
121
+ var getSelectedItems = function () {
122
+ return elements.locationItems.filter(':checked');
123
+ };
124
+
125
+ var updateTitle = function() {
126
+ var selectedItems = getSelectedItems(),
127
+ selectedCount = selectedItems.length,
128
+ locationCount = elements.locationItems.length;
129
+
130
+ if (selectedCount === 0) {
131
+ elements.title.text($container.data('nothing_selected'));
132
+ } else if (selectedCount === locationCount) {
133
+ elements.title.text($container.data('all_selected'));
134
+ } else if (selectedCount === 1) {
135
+ elements.title.text(selectedItems.data('name'));
136
+ } else {
137
+ elements.title.text(selectedCount + '/' + locationCount);
138
+ }
139
+
140
+ elements.itemAllLocations.prop('checked', selectedCount === locationCount);
141
+ };
142
+
143
+ var saveCookie = function () {
144
+ var selectedItems = getSelectedItems(),
145
+ selectedValues = [];
146
+
147
+ selectedItems.each(function () {
148
+ selectedValues.push(this.value);
149
+ });
150
+
151
+ setCookie('bookly_cal_location_ids', selectedValues);
152
+ };
153
+
154
+ var initEvents = function () {
155
+ elements.itemAllLocations.on('change', function () {
156
+ elements.locationItems.prop('checked', elements.itemAllLocations.prop('checked'));
157
+ });
158
+
159
+ elements.items.on('change', function() {
160
+ updateTitle();
161
+ saveCookie();
162
+
163
+ if (callbacks.change instanceof Function) {
164
+ callbacks.change();
165
+ }
166
+ });
167
+ };
168
+
169
+ function setDefaultLocations()
170
+ {
171
+ var locationIds = getCookie('bookly_cal_location_ids');
172
+
173
+ if (!locationIds) {
174
+ $container.find('input[type="checkbox"]').prop('checked', true);
175
+ setCookie('bookly_cal_location_ids', ['all']);
176
+ }
177
+ }
178
+
179
+ initElements();
180
+ setDefaultLocations();
181
+ restoreItemsFromCookie();
182
+ initEvents();
183
+ updateTitle();
184
+ }
185
+
186
+ initLocationFilter($('.bookly-js-locations-filter'), {
187
+ change: function() {
188
+ var view = $fullCalendar.fullCalendar('getView');
189
+ if (view.type === 'multiStaffDay') {
190
+ view.displayView($fullCalendar.fullCalendar('getDate'));
191
+ }
192
+ $fullCalendar.fullCalendar('refetchEvents');
193
+ }
194
+ });
195
+
196
  var calendar = new BooklyCalendar($fullCalendar, options);
197
 
198
  $('.fc-agendaDay-button').addClass('fc-corner-right');
308
  return keyValue ? keyValue[2] : null;
309
  }
310
 
311
+ /**
312
+ * Sync with Google Calendar.
313
+ */
314
+ $syncButton.on('click', function () {
315
+ var ladda = Ladda.create(this);
316
+ ladda.start();
317
+ $.post(
318
+ ajaxurl,
319
+ {action: 'bookly_advanced_google_calendar_sync', csrf_token: BooklyL10n.csrf_token},
320
+ function (response) {
321
+ if (response.success) {
322
+ $fullCalendar.fullCalendar('refetchEvents');
323
+ }
324
+ booklyAlert(response.data.alert);
325
+ ladda.stop();
326
+ },
327
+ 'json'
328
+ );
329
+ });
330
+
331
  });
backend/modules/calendar/templates/calendar.php CHANGED
@@ -1,5 +1,9 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- /** @var \BooklyLite\Lib\Entities\Staff[] $staff_members */
 
 
 
 
3
  ?>
4
  <style>
5
  .fc-slats tr { height: <?php echo max( 21, (int) ( 0.43 * get_option( 'bookly_gen_time_slot_length' ) ) ) ?>px; }
@@ -11,27 +15,27 @@
11
  <div class="bookly-page-title">
12
  <?php _e( 'Calendar', 'bookly' ) ?>
13
  </div>
14
- <?php if ( \BooklyLite\Lib\Utils\Common::isCurrentUserAdmin() ) : ?>
15
- <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
16
  <?php endif ?>
17
  </div>
18
  <div class="panel panel-default bookly-main bookly-fc-inner">
19
  <div class="panel-body">
20
  <?php if ( $staff_members ) : ?>
21
  <ul class="bookly-nav bookly-nav-tabs">
22
- <?php if ( \BooklyLite\Lib\Utils\Common::isCurrentUserAdmin() ) : ?>
23
  <li class="bookly-nav-item bookly-js-calendar-tab" data-staff_id="0">
24
  <?php _e( 'All', 'bookly' ) ?>
25
  </li>
26
  <?php endif ?>
27
  <?php foreach ( $staff_members as $staff ) : ?>
28
  <li class="bookly-nav-item bookly-js-calendar-tab" data-staff_id="<?php echo $staff->getId() ?>" style="display: none">
29
- <?php echo $staff->getFullName() ?>
30
  </li>
31
  <?php endforeach ?>
32
- <?php if ( \BooklyLite\Lib\Utils\Common::isCurrentUserAdmin() ) : ?>
33
- <div class="btn-group pull-right" style="margin-top: 5px;">
34
- <button class="btn btn-default dropdown-toggle bookly-flexbox" data-toggle="dropdown">
35
  <div class="bookly-flex-cell"><i class="dashicons dashicons-admin-users bookly-margin-right-md"></i></div>
36
  <div class="bookly-flex-cell text-left"><span id="bookly-staff-button"></span></div>
37
  <div class="bookly-flex-cell"><div class="bookly-margin-left-md"><span class="caret"></span></div></div>
@@ -47,7 +51,7 @@
47
  <a class="checkbox" href="javascript:void(0)">
48
  <label>
49
  <input type="checkbox" id="bookly-filter-staff-<?php echo $staff->getId() ?>" value="<?php echo $staff->getId() ?>" data-staff_name="<?php echo esc_attr( $staff->getFullName() ) ?>" class="bookly-js-check-entity">
50
- <?php echo $staff->getFullName() ?>
51
  </label>
52
  </a>
53
  </li>
@@ -55,6 +59,8 @@
55
  </ul>
56
  </div>
57
  <?php endif ?>
 
 
58
  </ul>
59
  <?php endif ?>
60
  <div class="bookly-margin-top-xlg">
@@ -65,24 +71,30 @@
65
  <div id="bookly-fc-wrapper" class="bookly-calendar">
66
  <div class="bookly-js-calendar-element"></div>
67
  </div>
68
- <?php \BooklyLite\Backend\Modules\Calendar\Components::getInstance()->renderAppointmentDialog() ?>
69
- <?php \BooklyLite\Lib\Proxy\Shared::renderComponentCalendar() ?>
 
 
70
  <?php else : ?>
71
  <div class="well">
72
- <div class="h1"><?php _e( 'Welcome to Bookly!', 'bookly' ) ?></div>
73
- <h3><?php _e( 'Thank you for purchasing our product.', 'bookly' ) ?></h3>
74
- <h3><?php _e( 'Bookly offers a simple solution for making appointments. With our plugin you will be able to easily manage your availability time and handle the flow of your clients.', 'bookly' ) ?></h3>
75
- <p><?php _e( 'To start using Bookly, you need to follow these steps which are the minimum requirements to get it running!', 'bookly' ) ?></p>
76
  <ol>
77
- <li><?php _e( 'Add staff members.', 'bookly' ) ?></li>
78
- <li><?php _e( 'Add services and assign them to staff members.', 'bookly' ) ?></li>
 
79
  </ol>
 
80
  <hr>
81
- <a class="btn btn-success" href="<?php echo \BooklyLite\Lib\Utils\Common::escAdminUrl( BooklyLite\Backend\Modules\Staff\Controller::page_slug ) ?>">
82
- <?php _e( 'Add Staff Members', 'bookly' ) ?>
83
  </a>
84
- <a class="btn btn-success" href="<?php echo \BooklyLite\Lib\Utils\Common::escAdminUrl( BooklyLite\Backend\Modules\Services\Controller::page_slug ) ?>">
85
- <?php _e( 'Add Services', 'bookly' ) ?>
 
 
 
86
  </a>
87
  </div>
88
  <?php endif ?>
@@ -90,6 +102,6 @@
90
  </div>
91
  </div>
92
 
93
- <?php \BooklyLite\Backend\Modules\Calendar\Components::getInstance()->renderDeleteDialog(); ?>
94
  </div>
95
  </div>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Lib\Utils\Common;
3
+ use Bookly\Backend\Components;
4
+ use Bookly\Backend\Modules as Backend;
5
+ use Bookly\Backend\Modules\Calendar\Proxy;
6
+ /** @var Bookly\Lib\Entities\Staff[] $staff_members */
7
  ?>
8
  <style>
9
  .fc-slats tr { height: <?php echo max( 21, (int) ( 0.43 * get_option( 'bookly_gen_time_slot_length' ) ) ) ?>px; }
15
  <div class="bookly-page-title">
16
  <?php _e( 'Calendar', 'bookly' ) ?>
17
  </div>
18
+ <?php if ( Common::isCurrentUserAdmin() ) : ?>
19
+ <?php Components\Support\Buttons::render( $self::pageSlug() ) ?>
20
  <?php endif ?>
21
  </div>
22
  <div class="panel panel-default bookly-main bookly-fc-inner">
23
  <div class="panel-body">
24
  <?php if ( $staff_members ) : ?>
25
  <ul class="bookly-nav bookly-nav-tabs">
26
+ <?php if ( Common::isCurrentUserAdmin() ) : ?>
27
  <li class="bookly-nav-item bookly-js-calendar-tab" data-staff_id="0">
28
  <?php _e( 'All', 'bookly' ) ?>
29
  </li>
30
  <?php endif ?>
31
  <?php foreach ( $staff_members as $staff ) : ?>
32
  <li class="bookly-nav-item bookly-js-calendar-tab" data-staff_id="<?php echo $staff->getId() ?>" style="display: none">
33
+ <?php echo esc_html( $staff->getFullName() ) ?>
34
  </li>
35
  <?php endforeach ?>
36
+ <?php if ( Common::isCurrentUserAdmin() ) : ?>
37
+ <div class="btn-group pull-right bookly-margin-top-xs">
38
+ <button class="btn btn-default dropdown-toggle bookly-js-staff-filter bookly-flexbox" data-toggle="dropdown">
39
  <div class="bookly-flex-cell"><i class="dashicons dashicons-admin-users bookly-margin-right-md"></i></div>
40
  <div class="bookly-flex-cell text-left"><span id="bookly-staff-button"></span></div>
41
  <div class="bookly-flex-cell"><div class="bookly-margin-left-md"><span class="caret"></span></div></div>
51
  <a class="checkbox" href="javascript:void(0)">
52
  <label>
53
  <input type="checkbox" id="bookly-filter-staff-<?php echo $staff->getId() ?>" value="<?php echo $staff->getId() ?>" data-staff_name="<?php echo esc_attr( $staff->getFullName() ) ?>" class="bookly-js-check-entity">
54
+ <?php echo esc_html( $staff->getFullName() ) ?>
55
  </label>
56
  </a>
57
  </li>
59
  </ul>
60
  </div>
61
  <?php endif ?>
62
+ <?php Proxy\Locations::renderCalendarLocationFilter() ?>
63
+ <?php Proxy\AdvancedGoogleCalendar::renderSyncButton( $staff_members ) ?>
64
  </ul>
65
  <?php endif ?>
66
  <div class="bookly-margin-top-xlg">
71
  <div id="bookly-fc-wrapper" class="bookly-calendar">
72
  <div class="bookly-js-calendar-element"></div>
73
  </div>
74
+ <?php Components\Dialogs\Appointment\Edit\Dialog::render() ?>
75
+ <?php Proxy\Shared::renderAddOnsComponents() ?>
76
+ <?php elseif( Bookly\Lib\Config::proActive() ) : ?>
77
+ <?php Components\Notices\Proxy\Pro::renderWelcome() ?>
78
  <?php else : ?>
79
  <div class="well">
80
+ <div class="h1"><?php esc_html_e( 'Welcome to Bookly and thank you for your choice!', 'bookly' ) ?></div>
81
+ <h3><?php esc_html_e( 'Bookly will simplify the booking process for your customers. This plugin creates another touchpoint to convert your visitors into customers. With Bookly your clients can see your availability, pick the services you provide, book them online and much more.', 'bookly' ) ?></h3>
82
+ <p><?php esc_html_e( 'To start using Bookly, you need to set up the services you provide and specify the staff members who will provide those services.', 'bookly' ) ?></p>
 
83
  <ol>
84
+ <li><?php esc_html_e( 'Add a staff member (you can add only one service provider with a free version of Bookly).', 'bookly' ) ?></li>
85
+ <li><?php esc_html_e( 'Add services you provide (up to five with a free version of Bookly) and assign them to a staff member.', 'bookly' ) ?></li>
86
+ <li><?php esc_html_e( 'Go to Posts/Pages and click on the “Add Bookly booking form” button in the page editor to publish the booking form on your website.', 'bookly' ) ?></li>
87
  </ol>
88
+ <p><?php printf( __( 'Bookly can boost your sales and scale together with your business. Get more features and remove the limits by upgrading to the paid version with the <a href="%s" target="_blank">Bookly Pro add-on</a>, which allows you to use a vast number of additional features and settings for booking services, install other add-ons for Bookly, and includes six months of customer support.', 'bookly' ), Common::prepareUrlReferrers( 'https://codecanyon.net/item/bookly/7226091?ref=ladela', 'welcome' ) ) ?></p>
89
  <hr>
90
+ <a class="btn btn-success" href="<?php echo Common::escAdminUrl( Backend\Staff\Ajax::pageSlug() ) ?>">
91
+ <?php esc_html_e( 'Add Staff Members', 'bookly' ) ?>
92
  </a>
93
+ <a class="btn btn-success" href="<?php echo Common::escAdminUrl( Backend\Services\Ajax::pageSlug() ) ?>">
94
+ <?php esc_html_e( 'Add Services', 'bookly' ) ?>
95
+ </a>
96
+ <a class="btn btn-success" href="<?php echo Common::prepareUrlReferrers( 'https://codecanyon.net/item/bookly/7226091?ref=ladela', 'welcome' ) ?>" target="_blank">
97
+ <?php esc_html_e( 'Try Bookly Pro add-on', 'bookly' ) ?>
98
  </a>
99
  </div>
100
  <?php endif ?>
102
  </div>
103
  </div>
104
 
105
+ <?php Components\Dialogs\Appointment\Delete\Dialog::render() ?>
106
  </div>
107
  </div>
backend/modules/customers/Ajax.php ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Customers;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Ajax
8
+ * @package Bookly\Backend\Modules\Customers
9
+ */
10
+ class Ajax extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * @inheritdoc
14
+ */
15
+ protected static function permissions()
16
+ {
17
+ return array( '_default' => 'user' );
18
+ }
19
+
20
+ /**
21
+ * Get list of customers.
22
+ */
23
+ public static function getCustomers()
24
+ {
25
+ global $wpdb;
26
+
27
+ $columns = self::parameter( 'columns' );
28
+ $order = self::parameter( 'order' );
29
+ $filter = self::parameter( 'filter' );
30
+
31
+ $query = Lib\Entities\Customer::query( 'c' );
32
+
33
+ $total = $query->count();
34
+
35
+ $select = 'SQL_CALC_FOUND_ROWS c.*,
36
+ (
37
+ SELECT MAX(a.start_date) FROM ' . Lib\Entities\Appointment::getTableName() . ' a
38
+ LEFT JOIN ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca ON ca.appointment_id = a.id
39
+ WHERE ca.customer_id = c.id
40
+ ) AS last_appointment,
41
+ (
42
+ SELECT COUNT(DISTINCT ca.appointment_id) FROM ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca
43
+ WHERE ca.customer_id = c.id
44
+ ) AS total_appointments,
45
+ (
46
+ SELECT SUM(p.total) FROM ' . Lib\Entities\Payment::getTableName() . ' p
47
+ WHERE p.id IN (
48
+ SELECT DISTINCT ca.payment_id FROM ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca
49
+ WHERE ca.customer_id = c.id
50
+ )
51
+ ) AS payments,
52
+ wpu.display_name AS wp_user';
53
+
54
+ $select = Proxy\CustomerGroups::prepareCustomerSelect( $select );
55
+
56
+ $query
57
+ ->select( $select )
58
+ ->tableJoin( $wpdb->users, 'wpu', 'wpu.ID = c.wp_user_id' )
59
+ ->groupBy( 'c.id' );
60
+
61
+ $query = Proxy\CustomerGroups::prepareCustomerQuery( $query );
62
+
63
+ if ( $filter != '' ) {
64
+ $search_value = Lib\Query::escape( $filter );
65
+ $query
66
+ ->whereLike( 'c.full_name', "%{$search_value}%" )
67
+ ->whereLike( 'c.phone', "%{$search_value}%", 'OR' )
68
+ ->whereLike( 'c.email', "%{$search_value}%", 'OR' )
69
+ ->whereLike( 'c.info_fields', "%{$search_value}%", 'OR' )
70
+ ;
71
+ }
72
+
73
+ foreach ( $order as $sort_by ) {
74
+ $query
75
+ ->sortBy( str_replace( '.', '_', $columns[ $sort_by['column'] ]['data'] ) )
76
+ ->order( $sort_by['dir'] == 'desc' ? Lib\Query::ORDER_DESCENDING : Lib\Query::ORDER_ASCENDING );
77
+ }
78
+
79
+ $query->limit( self::parameter( 'length' ) )->offset( self::parameter( 'start' ) );
80
+
81
+ $data = array();
82
+ foreach ( $query->fetchArray() as $row ) {
83
+
84
+ $address = Lib\Proxy\Pro::getFullAddressByCustomerData( $row );
85
+
86
+ $customer_data = array(
87
+ 'id' => $row['id'],
88
+ 'wp_user_id' => $row['wp_user_id'],
89
+ 'wp_user' => $row['wp_user'],
90
+ 'facebook_id' => $row['facebook_id'],
91
+ 'group_id' => $row['group_id'],
92
+ 'full_name' => $row['full_name'],
93
+ 'first_name' => $row['first_name'],
94
+ 'last_name' => $row['last_name'],
95
+ 'phone' => $row['phone'],
96
+ 'email' => $row['email'],
97
+ 'country' => $row['country'],
98
+ 'state' => $row['state'],
99
+ 'postcode' => $row['postcode'],
100
+ 'city' => $row['city'],
101
+ 'street' => $row['street'],
102
+ 'street_number' => $row['street_number'],
103
+ 'additional_address' => $row['additional_address'],
104
+ 'address' => $address,
105
+ 'notes' => $row['notes'],
106
+ 'birthday' => $row['birthday'] !== null && date_create( $row['birthday'] )->format( 'Y' ) == '0000' ? date_create( $row['birthday'] )->modify( '+ 1900 years' )->format( 'Y-m-d' ) : $row['birthday'],
107
+ 'last_appointment' => $row['last_appointment'] ? Lib\Utils\DateTime::formatDateTime( $row['last_appointment'] ) : '',
108
+ 'total_appointments' => $row['total_appointments'],
109
+ 'payments' => Lib\Utils\Price::format( $row['payments'] ),
110
+ );
111
+
112
+ $customer_data = Proxy\CustomerGroups::prepareCustomerListData( $customer_data, $row );
113
+ $customer_data = Proxy\CustomerInformation::prepareCustomerListData( $customer_data, $row );
114
+
115
+ $data[] = $customer_data;
116
+ }
117
+
118
+ wp_send_json( array(
119
+ 'draw' => ( int ) self::parameter( 'draw' ),
120
+ 'recordsTotal' => $total,
121
+ 'recordsFiltered' => ( int ) $wpdb->get_var( 'SELECT FOUND_ROWS()' ),
122
+ 'data' => $data,
123
+ ) );
124
+ }
125
+
126
+ /**
127
+ * Delete customers.
128
+ */
129
+ public static function deleteCustomers()
130
+ {
131
+ foreach ( self::parameter( 'data', array() ) as $id ) {
132
+ $customer = new Lib\Entities\Customer();
133
+ $customer->load( $id );
134
+ $customer->deleteWithWPUser( (bool) self::parameter( 'with_wp_user' ) );
135
+ }
136
+ wp_send_json_success();
137
+ }
138
+
139
+ /**
140
+ * Merge customers.
141
+ */
142
+ public static function mergeCustomers()
143
+ {
144
+ $target_id = self::parameter( 'target_id' );
145
+ $ids = self::parameter( 'ids', array() );
146
+
147
+ // Move appointments.
148
+ Lib\Entities\CustomerAppointment::query()
149
+ ->update()
150
+ ->set( 'customer_id', $target_id )
151
+ ->whereIn( 'customer_id', $ids )
152
+ ->execute();
153
+
154
+ // Let add-ons do their stuff.
155
+ Proxy\Shared::mergeCustomers( $target_id, $ids );
156
+
157
+ // Merge customer data.
158
+ $target_customer = Lib\Entities\Customer::find( $target_id );
159
+ foreach ( $ids as $id ) {
160
+ if ( $id != $target_id ) {
161
+ $customer = Lib\Entities\Customer::find( $id );
162
+ if ( ! $target_customer->getWpUserId() && $customer->getWpUserId() ) {
163
+ $target_customer->setWpUserId( $customer->getWpUserId() );
164
+ }
165
+ if ( ! $target_customer->getGroupId() ) {
166
+ $target_customer->setGroupId( $customer->getGroupId() );
167
+ }
168
+ if ( ! $target_customer->getFacebookId() ) {
169
+ $target_customer->setFacebookId( $customer->getFacebookId() );
170
+ }
171
+ if ( $target_customer->getFullName() == '' ) {
172
+ $target_customer->setFullName( $customer->getFullName() );
173
+ }
174
+ if ( $target_customer->getFirstName() == '' ) {
175
+ $target_customer->setFirstName( $customer->getFirstName() );
176
+ }
177
+ if ( $target_customer->getLastName() == '' ) {
178
+ $target_customer->setLastName( $customer->getLastName() );
179
+ }
180
+ if ( $target_customer->getPhone() == '' ) {
181
+ $target_customer->setPhone( $customer->getPhone() );
182
+ }
183
+ if ( $target_customer->getEmail() == '' ) {
184
+ $target_customer->setEmail( $customer->getEmail() );
185
+ }
186
+ if ( $target_customer->getBirthday() == '' ) {
187
+ $target_customer->setBirthday( $customer->getBirthday() );
188
+ }
189
+ if ( $target_customer->getCountry() == '' ) {
190
+ $target_customer->setCountry( $customer->getCountry() );
191
+ }
192
+ if ( $target_customer->getState() == '' ) {
193
+ $target_customer->setState( $customer->getState() );
194
+ }
195
+ if ( $target_customer->getPostcode() == '' ) {
196
+ $target_customer->setPostcode( $customer->getPostcode() );
197
+ }
198
+ if ( $target_customer->getCity() == '' ) {
199
+ $target_customer->setCity( $customer->getCity() );
200
+ }
201
+ if ( $target_customer->getStreet() == '' ) {
202
+ $target_customer->setStreet( $customer->getStreet() );
203
+ }
204
+ if ( $target_customer->getAdditionalAddress() == '' ) {
205
+ $target_customer->setAdditionalAddress( $customer->getAdditionalAddress() );
206
+ }
207
+ if ( $target_customer->getNotes() == '' ) {
208
+ $target_customer->setNotes( $customer->getNotes() );
209
+ }
210
+ // Delete merged customer.
211
+ $customer->delete();
212
+ }
213
+ $target_customer->save();
214
+ }
215
+
216
+ wp_send_json_success();
217
+ }
218
+
219
+ /**
220
+ * Check if the current user has access to the action.
221
+ *
222
+ * @param string $action
223
+ * @return bool
224
+ */
225
+ protected static function hasAccess( $action )
226
+ {
227
+ if ( parent::hasAccess( $action ) ) {
228
+ if ( ! Lib\Utils\Common::isCurrentUserAdmin() ) {
229
+ switch ( $action ) {
230
+ case 'getCustomers':
231
+ case 'deleteCustomers':
232
+ return Lib\Entities\Staff::query()
233
+ ->where( 'wp_user_id', get_current_user_id() )
234
+ ->count() > 0;
235
+ }
236
+ } else {
237
+ return true;
238
+ }
239
+ }
240
+
241
+ return false;
242
+ }
243
+ }
backend/modules/customers/Controller.php DELETED
@@ -1,249 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Customers;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Customers
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- const page_slug = 'bookly-customers';
13
-
14
- protected function getPermissions()
15
- {
16
- return array(
17
- 'executeSaveCustomer' => 'user',
18
- );
19
- }
20
-
21
- public function index()
22
- {
23
- if ( $this->hasParameter( 'import-customers' ) ) {
24
- $this->importCustomers();
25
- }
26
-
27
- $this->enqueueStyles( array(
28
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
29
- 'frontend' => array( 'css/ladda.min.css', ),
30
- ) );
31
-
32
- $this->enqueueScripts( array(
33
- 'backend' => array(
34
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
35
- 'js/datatables.min.js' => array( 'jquery' ),
36
- ),
37
- 'frontend' => array(
38
- 'js/spin.min.js' => array( 'jquery' ),
39
- 'js/ladda.min.js' => array( 'jquery' ),
40
- ),
41
- 'module' => array(
42
- 'js/customers.js' => array( 'bookly-datatables.min.js', 'bookly-ng-customer_dialog.js' ),
43
- ),
44
- ) );
45
-
46
- wp_localize_script( 'bookly-customers.js', 'BooklyL10n', array(
47
- 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
48
- 'first_last_name' => (int) Lib\Config::showFirstLastName(),
49
- 'edit' => __( 'Edit', 'bookly' ),
50
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
51
- 'wp_users' => get_users( array( 'fields' => array( 'ID', 'display_name' ), 'orderby' => 'display_name' ) ),
52
- 'zeroRecords' => __( 'No customers found.', 'bookly' ),
53
- 'processing' => __( 'Processing...', 'bookly' ),
54
- 'edit_customer' => __( 'Edit customer', 'bookly' ),
55
- 'new_customer' => __( 'New customer', 'bookly' ),
56
- 'create_customer' => __( 'Create customer', 'bookly' ),
57
- 'save' => __( 'Save', 'bookly' ),
58
- 'search' => __( 'Quick search customer', 'bookly' ),
59
- 'limitations' => __( '<b class="h4">This function is not available in the Lite version of Bookly.</b><br><br>To get access to all Bookly features, lifetime free updates and 24/7 support, please upgrade to the Standard version of Bookly.<br>For more information visit', 'bookly' ) . ' <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
60
- ) );
61
-
62
- $this->render( 'index' );
63
- }
64
-
65
- /**
66
- * Get list of customers.
67
- */
68
- public function executeGetCustomers()
69
- {
70
- global $wpdb;
71
-
72
- $columns = $this->getParameter( 'columns' );
73
- $order = $this->getParameter( 'order' );
74
- $filter = $this->getParameter( 'filter' );
75
-
76
- $query = Lib\Entities\Customer::query( 'c' );
77
-
78
- $total = $query->count();
79
-
80
- $query
81
- ->select( 'SQL_CALC_FOUND_ROWS c.*,
82
- (
83
- SELECT MAX(a.start_date) FROM ' . Lib\Entities\Appointment::getTableName() . ' a
84
- LEFT JOIN ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca ON ca.appointment_id = a.id
85
- WHERE ca.customer_id = c.id
86
- ) AS last_appointment,
87
- (
88
- SELECT COUNT(DISTINCT ca.appointment_id) FROM ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca
89
- WHERE ca.customer_id = c.id
90
- ) AS total_appointments,
91
- (
92
- SELECT SUM(p.total) FROM ' . Lib\Entities\Payment::getTableName() . ' p
93
- WHERE p.id IN (
94
- SELECT DISTINCT ca.payment_id FROM ' . Lib\Entities\CustomerAppointment::getTableName() . ' ca
95
- WHERE ca.customer_id = c.id
96
- )
97
- ) AS payments,
98
- wpu.display_name AS wp_user' )
99
- ->tableJoin( $wpdb->users, 'wpu', 'wpu.ID = c.wp_user_id' )
100
- ->groupBy( 'c.id' );
101
-
102
- if ( $filter != '' ) {
103
- $search_value = Lib\Query::escape( $filter );
104
- $query
105
- ->whereLike( 'c.full_name', "%{$search_value}%" )
106
- ->whereLike( 'c.phone', "%{$search_value}%", 'OR' )
107
- ->whereLike( 'c.email', "%{$search_value}%", 'OR' );
108
- }
109
-
110
- foreach ( $order as $sort_by ) {
111
- $query->sortBy( str_replace( '.', '_', $columns[ $sort_by['column'] ]['data'] ) )
112
- ->order( $sort_by['dir'] == 'desc' ? Lib\Query::ORDER_DESCENDING : Lib\Query::ORDER_ASCENDING );
113
- }
114
-
115
- $query->limit( $this->getParameter( 'length' ) )->offset( $this->getParameter( 'start' ) );
116
-
117
- $data = array();
118
- foreach ( $query->fetchArray() as $row ) {
119
- $data[] = array(
120
- 'id' => $row['id'],
121
- 'full_name' => $row['full_name'],
122
- 'first_name' => $row['first_name'],
123
- 'last_name' => $row['last_name'],
124
- 'wp_user' => $row['wp_user'],
125
- 'wp_user_id' => $row['wp_user_id'],
126
- 'phone' => $row['phone'],
127
- 'email' => $row['email'],
128
- 'notes' => $row['notes'],
129
- 'birthday' => $row['birthday'],
130
- 'last_appointment' => $row['last_appointment'] ? Lib\Utils\DateTime::formatDateTime( $row['last_appointment'] ) : '',
131
- 'total_appointments' => $row['total_appointments'],
132
- 'payments' => Lib\Utils\Price::format( $row['payments'] ),
133
- );
134
- }
135
-
136
- wp_send_json( array(
137
- 'draw' => ( int ) $this->getParameter( 'draw' ),
138
- 'recordsTotal' => $total,
139
- 'recordsFiltered' => ( int ) $wpdb->get_var( 'SELECT FOUND_ROWS()' ),
140
- 'data' => $data,
141
- ) );
142
- }
143
-
144
- /**
145
- * Create or edit a customer.
146
- */
147
- public function executeSaveCustomer()
148
- {
149
- $response = array();
150
- $form = new Forms\Customer();
151
-
152
- do {
153
- if ( ( get_option( 'bookly_cst_first_last_name' ) && $this->getParameter( 'first_name' ) !== '' && $this->getParameter( 'last_name' ) !== '' ) || ( ! get_option( 'bookly_cst_first_last_name' ) && $this->getParameter( 'full_name' ) !== '' ) ) {
154
- $params = $this->getPostParameters();
155
- if ( ! $params['wp_user_id'] ) {
156
- $params['wp_user_id'] = null;
157
- }
158
- if ( ! $params['birthday'] ) {
159
- $params['birthday'] = null;
160
- }
161
- $form->bind( $params );
162
- /** @var Lib\Entities\Customer $customer */
163
- $customer = $form->save();
164
- if ( $customer ) {
165
- $response['success'] = true;
166
- $response['customer'] = array(
167
- 'id' => $customer->getId(),
168
- 'wp_user_id' => $customer->getWpUserId(),
169
- 'full_name' => $customer->getFullName(),
170
- 'first_name' => $customer->getFirstName(),
171
- 'last_name' => $customer->getLastName(),
172
- 'phone' => $customer->getPhone(),
173
- 'email' => $customer->getEmail(),
174
- 'notes' => $customer->getNotes(),
175
- 'birthday' => $customer->getBirthday(),
176
- );
177
- break;
178
- }
179
- }
180
- $response['success'] = false;
181
- $response['errors'] = array();
182
- if (get_option( 'bookly_cst_first_last_name' )) {
183
- if ( $this->getParameter( 'first_name' ) == '' ) {
184
- $response['errors']['first_name'] = array( 'required' );
185
- }
186
- if ( $this->getParameter( 'last_name' ) == '' ) {
187
- $response['errors']['last_name'] = array( 'required' );
188
- }
189
- } else {
190
- $response['errors'] = array( 'full_name' => array( 'required' ) );
191
- }
192
- } while ( 0 );
193
-
194
- wp_send_json( $response );
195
- }
196
-
197
- /**
198
- * Import customers from CSV.
199
- */
200
- private function importCustomers()
201
- {
202
- @ini_set( 'auto_detect_line_endings', true );
203
- $fields = array();
204
- foreach ( array( 'full_name', 'first_name', 'last_name', 'phone', 'email', 'birthday' ) as $field ) {
205
- if ( $this->getParameter( $field ) ) {
206
- $fields[] = $field;
207
- }
208
- }
209
- $file = fopen( $_FILES['import_customers_file']['tmp_name'], 'r' );
210
- while ( $line = fgetcsv( $file, null, $this->getParameter( 'import_customers_delimiter' ) ) ) {
211
- if ( $line[0] != '' ) {
212
- $customer = new Lib\Entities\Customer();
213
- foreach ( $line as $number => $value ) {
214
- if ( $number < count( $fields ) ) {
215
- if ( $fields[ $number ] == 'birthday' ) {
216
- $dob = date_create( $value );
217
- if ( $dob !== false ) {
218
- $customer->setBirthday( $dob->format( 'Y-m-d' ) );
219
- }
220
- } else {
221
- $method = 'set' . implode( '', array_map( 'ucfirst', explode( '_', $fields[ $number ] ) ) );
222
- $customer->$method( $value );
223
- }
224
- }
225
- }
226
- $customer->save();
227
- }
228
- }
229
- }
230
-
231
- /**
232
- * Delete customers.
233
- */
234
- public function executeDeleteCustomers()
235
- {
236
- foreach ( $this->getParameter( 'data', array() ) as $id ) {
237
- $customer = new Lib\Entities\Customer();
238
- $customer->load( $id );
239
- $customer->deleteWithWPUser( (bool) $this->getParameter( 'with_wp_user' ) );
240
- }
241
- wp_send_json_success();
242
- }
243
-
244
- /**
245
- * Export Customers to CSV
246
- */
247
- public function executeExportCustomers() { exit; }
248
-
249
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/customers/Page.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Customers;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Page
8
+ * @package Bookly\Backend\Modules\Customers
9
+ */
10
+ class Page extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render page.
14
+ */
15
+ public static function render()
16
+ {
17
+ if ( self::hasParameter( 'import-customers' ) ) {
18
+ Proxy\Pro::importCustomers();
19
+ }
20
+
21
+ self::enqueueStyles( array(
22
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
23
+ 'frontend' => array( 'css/ladda.min.css', ),
24
+ ) );
25
+
26
+ self::enqueueScripts( array(
27
+ 'backend' => array(
28
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
29
+ 'js/datatables.min.js' => array( 'jquery' ),
30
+ ),
31
+ 'frontend' => array(
32
+ 'js/spin.min.js' => array( 'jquery' ),
33
+ 'js/ladda.min.js' => array( 'jquery' ),
34
+ ),
35
+ 'module' => array(
36
+ 'js/customers.js' => array( 'bookly-datatables.min.js', 'bookly-ng-customer.js' ),
37
+ ),
38
+ ) );
39
+
40
+ // Customer information fields.
41
+ $info_fields = (array) Lib\Proxy\CustomerInformation::getFieldsWhichMayHaveData();
42
+
43
+ wp_localize_script( 'bookly-customers.js', 'BooklyL10n', array(
44
+ 'csrfToken' => Lib\Utils\Common::getCsrfToken(),
45
+ 'first_last_name' => (int) Lib\Config::showFirstLastName(),
46
+ 'groupsActive' => (int) Lib\Config::customerGroupsActive(),
47
+ 'infoFields' => $info_fields,
48
+ 'edit' => __( 'Edit', 'bookly' ),
49
+ 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
50
+ 'wp_users' => get_users( array( 'fields' => array( 'ID', 'display_name' ), 'orderby' => 'display_name' ) ),
51
+ 'zeroRecords' => __( 'No customers found.', 'bookly' ),
52
+ 'processing' => __( 'Processing...', 'bookly' ),
53
+ 'edit_customer' => __( 'Edit customer', 'bookly' ),
54
+ 'new_customer' => __( 'New customer', 'bookly' ),
55
+ 'create_customer' => __( 'Create customer', 'bookly' ),
56
+ 'save' => __( 'Save', 'bookly' ),
57
+ 'search' => __( 'Quick search customer', 'bookly' ),
58
+ 'proEnabled' => (int) Lib\Config::proActive(),
59
+ ) );
60
+
61
+ self::renderTemplate( 'index', compact( 'info_fields' ) );
62
+ }
63
+ }
backend/modules/customers/proxy/CustomerGroups.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Customers\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CustomerGroups
8
+ * @package Bookly\Backend\Modules\Customers\Proxy
9
+ *
10
+ * @method static array prepareCustomerListData( array $data, array $row ) Prepare 'Customer Groups' data in customers table.
11
+ * @method static Lib\Query prepareCustomerQuery( Lib\Query $query ) Prepare 'Customer Groups' query in customers table.
12
+ * @method static string prepareCustomerSelect( string $select ) Prepare 'Customer Groups' select in customers table.
13
+ * @method static array prepareExportTitles( array $titles ) Prepare 'Customer Groups' titles in customers export.
14
+ * @method static void renderExportDialogRow() Render 'Customer Group' row in export customer dialog.
15
+ * @method static void renderCustomerTableHeader() Render 'Customer Group' in customers table.
16
+ */
17
+ abstract class CustomerGroups extends Lib\Base\Proxy
18
+ {
19
+
20
+ }
backend/modules/customers/proxy/CustomerInformation.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Customers\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CustomerInformation
8
+ * @package Bookly\Backend\Modules\Customers\Proxy
9
+ *
10
+ * @method static array prepareCustomerListData( array $customer_data, array $row ) Prepare customer info fields for customers list.
11
+ */
12
+ abstract class CustomerInformation extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/customers/proxy/Pro.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Customers\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Modules\Customers\Proxy
9
+ *
10
+ * @method static void importCustomers() Import customers from CSV.
11
+ * @method static void renderCustomerAddressTableHeader() Render 'address' column header.
12
+ * @method static void renderImportButton() Render import button.
13
+ * @method static void renderExportButton() Render export button.
14
+ * @method static void renderImportDialog() Render import dialog.
15
+ * @method static void renderExportDialog( array $info_fields ) Render export dialog.
16
+ */
17
+ abstract class Pro extends Lib\Base\Proxy
18
+ {
19
+
20
+ }
backend/modules/customers/proxy/Shared.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Customers\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Shared
8
+ * @package Bookly\Backend\Modules\Customers\Proxy
9
+ *
10
+ * @method static void mergeCustomers( int $target_id, array $ids ) Merge customers.
11
+ */
12
+ abstract class Shared extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/customers/resources/js/customers.js CHANGED
@@ -1,72 +1,109 @@
1
  jQuery(function($) {
2
 
3
  var
4
- $customers_list = $('#bookly-customers-list'),
5
- $filter = $('#bookly-filter'),
6
- $check_all_button = $('#bookly-check-all'),
7
- $customer_dialog = $('#bookly-customer-dialog'),
8
- $add_button = $('#bookly-add'),
9
- $delete_button = $('#bookly-delete'),
10
- $delete_dialog = $('#bookly-delete-dialog'),
11
- $delete_button_no = $('#bookly-delete-no'),
12
- $delete_button_yes = $('#bookly-delete-yes'),
13
- $remember_choice = $('#bookly-delete-remember-choice'),
14
- remembered_choice,
 
 
 
 
 
 
15
  row
16
- ;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  /**
19
  * Init DataTables.
20
  */
21
- var dt = $customers_list.DataTable({
22
- order: [[ 0, 'asc' ]],
23
- info: false,
24
- searching: false,
25
  lengthChange: false,
26
- pageLength: 25,
27
- pagingType: 'numbers',
28
- processing: true,
29
- responsive: true,
30
- serverSide: true,
31
- ajax: {
32
  url : ajaxurl,
33
  type: 'POST',
34
  data: function (d) {
35
  return $.extend({}, d, {
36
- action: 'bookly_get_customers',
37
- csrf_token : BooklyL10n.csrf_token,
38
- filter: $filter.val()
39
  });
40
  }
41
  },
42
- columns: [
43
- { data: 'full_name', render: $.fn.dataTable.render.text(), visible: BooklyL10n.first_last_name == 0 },
44
- { data: 'first_name', render: $.fn.dataTable.render.text(), visible: BooklyL10n.first_last_name == 1 },
45
- { data: 'last_name', render: $.fn.dataTable.render.text(), visible: BooklyL10n.first_last_name == 1 },
46
- { data: 'wp_user', render: $.fn.dataTable.render.text() },
47
- { data: 'phone', render: $.fn.dataTable.render.text() },
48
- { data: 'email', render: $.fn.dataTable.render.text() },
49
- { data: 'notes', render: $.fn.dataTable.render.text() },
50
- { data: 'last_appointment' },
51
- { data: 'total_appointments' },
52
- { data: 'payments' },
53
  {
54
  responsivePriority: 1,
55
- orderable: false,
56
- searchable: false,
57
- render: function ( data, type, row, meta ) {
58
  return '<button type="button" class="btn btn-default" data-toggle="modal" data-target="#bookly-customer-dialog"><i class="glyphicon glyphicon-edit"></i> ' + BooklyL10n.edit + '</button>';
59
  }
60
  },
61
  {
62
  responsivePriority: 1,
63
- orderable: false,
64
- searchable: false,
65
- render: function ( data, type, row, meta ) {
66
- return '<input type="checkbox" value="' + row.id + '">';
67
  }
68
  }
69
- ],
70
  dom: "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
71
  "<'row'<'col-sm-12'tr>>" +
72
  "<'row pull-left'<'col-sm-12 bookly-margin-top-lg'p>>",
@@ -79,54 +116,66 @@ jQuery(function($) {
79
  /**
80
  * Select all customers.
81
  */
82
- $check_all_button.on('change', function () {
83
- $customers_list.find('tbody input:checkbox').prop('checked', this.checked);
84
  });
85
 
86
  /**
87
  * On customer select.
88
  */
89
- $customers_list.on('change', 'tbody input:checkbox', function () {
90
- $check_all_button.prop('checked', $customers_list.find('tbody input:not(:checked)').length == 0);
 
91
  });
92
 
93
  /**
94
  * Edit customer.
95
  */
96
- $customers_list.on('click', 'button', function () {
97
  row = dt.row($(this).closest('td'));
98
  });
99
 
100
  /**
101
  * New customer.
102
  */
103
- $add_button.on('click', function () {
104
  row = null;
105
  });
106
 
107
  /**
108
  * On show modal.
109
  */
110
- $customer_dialog.on('show.bs.modal', function () {
111
- var $title = $customer_dialog.find('.modal-title');
112
- var $button = $customer_dialog.find('.modal-footer button:first');
113
  var customer;
114
  if (row) {
115
- customer = $.extend({}, row.data());
116
  $title.text(BooklyL10n.edit_customer);
117
  $button.text(BooklyL10n.save);
118
  } else {
119
  customer = {
120
- id : '',
121
- wp_user_id : '',
122
- full_name : '',
123
- first_name : '',
124
- last_name : '',
125
- phone : '',
126
- email : '',
127
- notes : '',
128
- birthday : ''
 
 
 
 
 
 
 
 
129
  };
 
 
 
130
  $title.text(BooklyL10n.new_customer);
131
  $button.text(BooklyL10n.create_customer);
132
  }
@@ -135,7 +184,11 @@ jQuery(function($) {
135
  $scope.$apply(function ($scope) {
136
  $scope.customer = customer;
137
  setTimeout(function() {
138
- $customer_dialog.find('#phone').intlTelInput('setNumber', customer.phone);
 
 
 
 
139
  }, 0);
140
  });
141
  });
@@ -143,24 +196,24 @@ jQuery(function($) {
143
  /**
144
  * Delete customers.
145
  */
146
- $delete_button.on('click', function () {
147
- if (remembered_choice === undefined) {
148
- $delete_dialog.modal('show');
149
  } else {
150
- deleteCustomers(this, remembered_choice);
151
  }}
152
  );
153
 
154
- $delete_button_no.on('click', function () {
155
- if ($remember_choice.prop('checked')) {
156
- remembered_choice = false;
157
  }
158
  deleteCustomers(this, false);
159
  });
160
 
161
- $delete_button_yes.on('click', function () {
162
- if ($remember_choice.prop('checked')) {
163
- remembered_choice = true;
164
  }
165
  deleteCustomers(this, true);
166
  });
@@ -170,7 +223,7 @@ jQuery(function($) {
170
  ladda.start();
171
 
172
  var data = [];
173
- var $checkboxes = $customers_list.find('tbody input:checked');
174
  $checkboxes.each(function () {
175
  data.push(this.value);
176
  });
@@ -180,14 +233,14 @@ jQuery(function($) {
180
  type : 'POST',
181
  data : {
182
  action : 'bookly_delete_customers',
183
- csrf_token : BooklyL10n.csrf_token,
184
  data : data,
185
  with_wp_user : with_wp_user ? 1 : 0
186
  },
187
  dataType : 'json',
188
  success : function(response) {
189
  ladda.stop();
190
- $delete_dialog.modal('hide');
191
  if (response.success) {
192
  dt.ajax.reload(null, false);
193
  } else {
@@ -202,24 +255,122 @@ jQuery(function($) {
202
  */
203
  $filter.on('keyup', function () { dt.ajax.reload(); });
204
 
205
- $('.bookly-limitation').on('click', function () {
206
- booklyAlert({error: [BooklyL10n.limitations]});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  });
209
 
210
  (function() {
211
  var module = angular.module('customer', ['customerDialog']);
212
  module.controller('customerCtrl', function($scope) {
213
  $scope.customer = {
214
- id : '',
215
- wp_user_id : '',
216
- full_name : '',
217
- first_name : '',
218
- last_name : '',
219
- phone : '',
220
- email : '',
221
- notes : '',
222
- birthday : ''
 
 
 
 
 
 
 
 
223
  };
224
  $scope.saveCustomer = function(customer) {
225
  jQuery('#bookly-customers-list').DataTable().ajax.reload(null, false);
1
  jQuery(function($) {
2
 
3
  var
4
+ $customersList = $('#bookly-customers-list'),
5
+ $mergeListContainer = $('#bookly-merge-list'),
6
+ $mergeList = $customersList.clone().prop('id', '').find('th:last').remove().end().appendTo($mergeListContainer),
7
+ $filter = $('#bookly-filter'),
8
+ $checkAllButton = $('#bookly-check-all'),
9
+ $customerDialog = $('#bookly-customer-dialog'),
10
+ $addButton = $('#bookly-add'),
11
+ $deleteButton = $('#bookly-delete'),
12
+ $deleteDialog = $('#bookly-delete-dialog'),
13
+ $deleteButtonNo = $('#bookly-delete-no'),
14
+ $deleteButtonYes = $('#bookly-delete-yes'),
15
+ $selectForMergeButton = $('#bookly-select-for-merge'),
16
+ $mergeWithButton = $('#bookly-merge-with'),
17
+ $mergeDialog = $('#bookly-merge-dialog'),
18
+ $mergeButton = $('#bookly-merge'),
19
+ $rememberChoice = $('#bookly-delete-remember-choice'),
20
+ rememberedChoice,
21
  row
22
+ ;
23
+
24
+ var columns = [
25
+ {data: 'full_name', render: $.fn.dataTable.render.text(), responsivePriority: 2, visible: BooklyL10n.first_last_name == 0},
26
+ {data: 'first_name', render: $.fn.dataTable.render.text(), responsivePriority: 2, visible: BooklyL10n.first_last_name == 1},
27
+ {data: 'last_name', render: $.fn.dataTable.render.text(), responsivePriority: 2, visible: BooklyL10n.first_last_name == 1},
28
+ {data: 'wp_user', render: $.fn.dataTable.render.text(), responsivePriority: 2}
29
+ ];
30
+ if (BooklyL10n.groupsActive == 1) {
31
+ columns.push({data: 'group_name', render: $.fn.dataTable.render.text(), responsivePriority: 2});
32
+ }
33
+ columns = columns.concat([
34
+ {data: 'phone', render: $.fn.dataTable.render.text(), responsivePriority: 2},
35
+ {data: 'email', render: $.fn.dataTable.render.text(), responsivePriority: 2}
36
+ ]);
37
+ BooklyL10n.infoFields.forEach(function (field, i) {
38
+ columns.push({
39
+ data: 'info_fields.' + i + '.value' + (field.type === 'checkboxes' ? '[, ]' : ''),
40
+ render: $.fn.dataTable.render.text(),
41
+ responsivePriority: 3,
42
+ orderable: false
43
+ });
44
+ });
45
+ columns = columns.concat([
46
+ {data: 'notes', render: $.fn.dataTable.render.text(), responsivePriority: 2},
47
+ {data: 'last_appointment', responsivePriority: 2},
48
+ {data: 'total_appointments', responsivePriority: 2},
49
+ {data: 'payments', responsivePriority: 2}
50
+ ]);
51
+
52
+ if (BooklyL10n.proEnabled == 1){
53
+ columns = columns.concat([
54
+ {data: 'address', responsivePriority: 3, orderable: false},
55
+ {
56
+ data: 'facebook_id',
57
+ responsivePriority: 2,
58
+ render: function (data, type, row, meta) {
59
+ return data ? '<a href="https://www.facebook.com/app_scoped_user_id/' + data + '/" target="_blank"><span class="dashicons dashicons-facebook"></span></a>' : '';
60
+ }
61
+ }
62
+ ]);
63
+ }
64
 
65
  /**
66
  * Init DataTables.
67
  */
68
+ var dt = $customersList.DataTable({
69
+ order : [[0, 'asc']],
70
+ info : false,
71
+ searching : false,
72
  lengthChange: false,
73
+ pageLength : 25,
74
+ pagingType : 'numbers',
75
+ processing : true,
76
+ responsive : true,
77
+ serverSide : true,
78
+ ajax : {
79
  url : ajaxurl,
80
  type: 'POST',
81
  data: function (d) {
82
  return $.extend({}, d, {
83
+ action : 'bookly_get_customers',
84
+ csrf_token: BooklyL10n.csrfToken,
85
+ filter : $filter.val()
86
  });
87
  }
88
  },
89
+ columns: columns.concat([
 
 
 
 
 
 
 
 
 
 
90
  {
91
  responsivePriority: 1,
92
+ orderable : false,
93
+ searchable : false,
94
+ render : function (data, type, row, meta) {
95
  return '<button type="button" class="btn btn-default" data-toggle="modal" data-target="#bookly-customer-dialog"><i class="glyphicon glyphicon-edit"></i> ' + BooklyL10n.edit + '</button>';
96
  }
97
  },
98
  {
99
  responsivePriority: 1,
100
+ orderable : false,
101
+ searchable : false,
102
+ render : function (data, type, row, meta) {
103
+ return '<input type="checkbox" value="' + row.id + '" />';
104
  }
105
  }
106
+ ]),
107
  dom: "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
108
  "<'row'<'col-sm-12'tr>>" +
109
  "<'row pull-left'<'col-sm-12 bookly-margin-top-lg'p>>",
116
  /**
117
  * Select all customers.
118
  */
119
+ $checkAllButton.on('change', function () {
120
+ $customersList.find('tbody input:checkbox').prop('checked', this.checked);
121
  });
122
 
123
  /**
124
  * On customer select.
125
  */
126
+ $customersList.on('change', 'tbody input:checkbox', function () {
127
+ $checkAllButton.prop('checked', $customersList.find('tbody input:not(:checked)').length == 0);
128
+ $mergeWithButton.prop('disabled', $customersList.find('tbody input:checked').length != 1);
129
  });
130
 
131
  /**
132
  * Edit customer.
133
  */
134
+ $customersList.on('click', 'button', function () {
135
  row = dt.row($(this).closest('td'));
136
  });
137
 
138
  /**
139
  * New customer.
140
  */
141
+ $addButton.on('click', function () {
142
  row = null;
143
  });
144
 
145
  /**
146
  * On show modal.
147
  */
148
+ $customerDialog.on('show.bs.modal', function () {
149
+ var $title = $customerDialog.find('.modal-title');
150
+ var $button = $customerDialog.find('.modal-footer button:first');
151
  var customer;
152
  if (row) {
153
+ customer = $.extend(true, {}, row.data());
154
  $title.text(BooklyL10n.edit_customer);
155
  $button.text(BooklyL10n.save);
156
  } else {
157
  customer = {
158
+ id : '',
159
+ wp_user_id : '',
160
+ group_id : '',
161
+ full_name : '',
162
+ first_name : '',
163
+ last_name : '',
164
+ phone : '',
165
+ email : '',
166
+ country : '',
167
+ state : '',
168
+ postcode : '',
169
+ city : '',
170
+ street : '',
171
+ address : '',
172
+ info_fields : [],
173
+ notes : '',
174
+ birthday : ''
175
  };
176
+ BooklyL10n.infoFields.forEach(function (field) {
177
+ customer.info_fields.push({id: field.id, value: field.type === 'checkboxes' ? [] : ''});
178
+ });
179
  $title.text(BooklyL10n.new_customer);
180
  $button.text(BooklyL10n.create_customer);
181
  }
184
  $scope.$apply(function ($scope) {
185
  $scope.customer = customer;
186
  setTimeout(function() {
187
+ if (BooklyL10nCustDialog.intlTelInput.enabled) {
188
+ $customerDialog.find('#phone').intlTelInput('setNumber', customer.phone);
189
+ } else {
190
+ $customerDialog.find('#phone').val(customer.phone);
191
+ }
192
  }, 0);
193
  });
194
  });
196
  /**
197
  * Delete customers.
198
  */
199
+ $deleteButton.on('click', function () {
200
+ if (rememberedChoice === undefined) {
201
+ $deleteDialog.modal('show');
202
  } else {
203
+ deleteCustomers(this, rememberedChoice);
204
  }}
205
  );
206
 
207
+ $deleteButtonNo.on('click', function () {
208
+ if ($rememberChoice.prop('checked')) {
209
+ rememberedChoice = false;
210
  }
211
  deleteCustomers(this, false);
212
  });
213
 
214
+ $deleteButtonYes.on('click', function () {
215
+ if ($rememberChoice.prop('checked')) {
216
+ rememberedChoice = true;
217
  }
218
  deleteCustomers(this, true);
219
  });
223
  ladda.start();
224
 
225
  var data = [];
226
+ var $checkboxes = $customersList.find('tbody input:checked');
227
  $checkboxes.each(function () {
228
  data.push(this.value);
229
  });
233
  type : 'POST',
234
  data : {
235
  action : 'bookly_delete_customers',
236
+ csrf_token : BooklyL10n.csrfToken,
237
  data : data,
238
  with_wp_user : with_wp_user ? 1 : 0
239
  },
240
  dataType : 'json',
241
  success : function(response) {
242
  ladda.stop();
243
+ $deleteDialog.modal('hide');
244
  if (response.success) {
245
  dt.ajax.reload(null, false);
246
  } else {
255
  */
256
  $filter.on('keyup', function () { dt.ajax.reload(); });
257
 
258
+ /**
259
+ * Merge list.
260
+ */
261
+ var mdt = $mergeList.DataTable({
262
+ order : [[0, 'asc']],
263
+ info : false,
264
+ searching : false,
265
+ paging : false,
266
+ responsive : true,
267
+ columns: columns.concat([
268
+ {
269
+ responsivePriority: 1,
270
+ orderable : false,
271
+ searchable : false,
272
+ render : function (data, type, row, meta) {
273
+ return '<button type="button" class="btn btn-default"><i class="glyphicon glyphicon-remove"></i></button>';
274
+ }
275
+ }
276
+ ]),
277
+ language: {
278
+ zeroRecords: BooklyL10n.zeroRecords
279
+ }
280
+ });
281
+
282
+ /**
283
+ * Select for merge.
284
+ */
285
+ $selectForMergeButton.on('click', function () {
286
+ var $checkboxes = $customersList.find('tbody input:checked');
287
+
288
+ if ($checkboxes.length) {
289
+ $checkboxes.each(function () {
290
+ var data = dt.row($(this).closest('td')).data();
291
+ if (mdt.rows().data().indexOf(data) < 0) {
292
+ mdt.row.add(data).draw();
293
+ }
294
+ this.checked = false;
295
+ }).trigger('change');
296
+ $mergeWithButton.show();
297
+ $mergeListContainer.show();
298
+ }
299
+ });
300
+
301
+ /**
302
+ * Merge customers.
303
+ */
304
+ $mergeButton.on('click', function () {
305
+ var ladda = Ladda.create(this);
306
+ ladda.start();
307
+ var ids = [];
308
+ mdt.rows().every(function () {
309
+ ids.push(this.data().id);
310
+ });
311
+ $.ajax({
312
+ url : ajaxurl,
313
+ type : 'POST',
314
+ data : {
315
+ action : 'bookly_merge_customers',
316
+ csrf_token : BooklyL10n.csrfToken,
317
+ target_id : $customersList.find('tbody input:checked').val(),
318
+ ids : ids
319
+ },
320
+ dataType : 'json',
321
+ success : function(response) {
322
+ ladda.stop();
323
+ $mergeDialog.modal('hide');
324
+ if (response.success) {
325
+ dt.ajax.reload(null, false);
326
+ mdt.clear();
327
+ $mergeListContainer.hide();
328
+ $mergeWithButton.hide();
329
+ } else {
330
+ alert(response.data.message);
331
+ }
332
+ }
333
+ });
334
  });
335
+
336
+ /**
337
+ * Remove customer from merge list.
338
+ */
339
+ $mergeList.on('click', 'button', function () {
340
+ mdt.row($(this).closest('td')).remove().draw();
341
+ var any = mdt.rows().any();
342
+ $mergeWithButton.toggle(any);
343
+ $mergeListContainer.toggle(any);
344
+ });
345
+
346
+ /**
347
+ * Import & export customers.
348
+ */
349
+ Ladda.bind('#bookly-import-customers-dialog button[type=submit]');
350
+ Ladda.bind('#bookly-export-customers-dialog button[type=submit]', {timeout: 2000});
351
  });
352
 
353
  (function() {
354
  var module = angular.module('customer', ['customerDialog']);
355
  module.controller('customerCtrl', function($scope) {
356
  $scope.customer = {
357
+ id : '',
358
+ wp_user_id : '',
359
+ group_id : '',
360
+ full_name : '',
361
+ first_name : '',
362
+ last_name : '',
363
+ phone : '',
364
+ email : '',
365
+ country : '',
366
+ state : '',
367
+ postcode : '',
368
+ city : '',
369
+ street : '',
370
+ address : '',
371
+ info_fields : [],
372
+ notes : '',
373
+ birthday : ''
374
  };
375
  $scope.saveCustomer = function(customer) {
376
  jQuery('#bookly-customers-list').DataTable().ajax.reload(null, false);
backend/modules/customers/templates/_import.php DELETED
@@ -1,44 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div id="bookly-import-customers-dialog" class="modal fade" tabindex=-1 role="dialog">
3
- <div class="modal-dialog">
4
- <form enctype="multipart/form-data" action="<?php echo \BooklyLite\Lib\Utils\Common::escAdminUrl( \BooklyLite\Backend\Modules\Customers\Controller::page_slug ) ?>" method="POST">
5
- <div class="modal-content">
6
- <div class="modal-header">
7
- <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
8
- <div class="modal-title h2"><?php _e( 'Import', 'bookly' ) ?></div>
9
- </div>
10
- <div class="modal-body">
11
- <h4><?php _e( 'Note', 'bookly' ) ?></h4>
12
- <p>
13
- <?php _e( 'You may import list of clients in CSV format. You can choose the columns contained in your file. The sequence of columns should coincide with the specified one.', 'bookly' ) ?>
14
- </p>
15
- <div class="form-group">
16
- <label for="import_customers_file"><?php _e( 'Select file', 'bookly' ) ?></label>
17
- <input name="import_customers_file" id="import_customers_file" type="file">
18
- </div>
19
- <div class="form-group">
20
- <div class="checkbox"><label><input checked name="full_name" type="checkbox"> <?php echo esc_html( get_option( 'bookly_l10n_label_name' ) ) ?></label></div>
21
- <div class="checkbox"><label><input name="first_name" type="checkbox"> <?php echo esc_html( get_option( 'bookly_l10n_label_first_name' ) ) ?></label></div>
22
- <div class="checkbox"><label><input name="last_name" type="checkbox"> <?php echo esc_html( get_option( 'bookly_l10n_label_last_name' ) ) ?></label></div>
23
- <div class="checkbox"><label><input checked name="phone" type="checkbox"><?php echo esc_html( get_option( 'bookly_l10n_label_phone' ) ) ?></label></div>
24
- <div class="checkbox"><label><input checked name="email" type="checkbox"><?php echo esc_html( get_option( 'bookly_l10n_label_email' ) ) ?></label></div>
25
- <div class="checkbox"><label><input checked name="birthday" type="checkbox"><?php _e( 'Date of birth', 'bookly' ) ?></label></div>
26
- </div>
27
- <div class="form-group">
28
- <label for="import_customers_delimiter"><?php _e( 'Delimiter', 'bookly' ) ?></label>
29
- <select name="import_customers_delimiter" id="import_customers_delimiter" class="form-control">
30
- <option value=","><?php _e( 'Comma (,)', 'bookly' ) ?></option>
31
- <option value=";"><?php _e( 'Semicolon (;)', 'bookly' ) ?></option>
32
- </select>
33
- </div>
34
- <input type="hidden" name="import">
35
- </div>
36
- <div class="modal-footer">
37
- <button type="submit" class="btn btn-lg btn-success" name="import-customers">
38
- <?php _e( 'Import', 'bookly' ) ?>
39
- </button>
40
- </div>
41
- </div>
42
- </form>
43
- </div>
44
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/customers/templates/index.php CHANGED
@@ -1,11 +1,16 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
 
 
 
2
  <div id="bookly-tbs" class="wrap">
3
  <div class="bookly-tbs-body">
4
  <div class="page-header text-right clearfix">
5
  <div class="bookly-page-title">
6
- <?php _e( 'Customers', 'bookly' ) ?>
7
  </div>
8
- <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
9
  </div>
10
  <div class="panel panel-default bookly-main">
11
  <div class="panel-body">
@@ -15,15 +20,13 @@
15
  <input class="form-control" type="text" id="bookly-filter" placeholder="<?php esc_attr_e( 'Quick search customer', 'bookly' ) ?>" />
16
  </div>
17
  </div>
18
- <div class="col-md-8 form-inline bookly-margin-bottom-lg text-right">
 
 
 
 
19
  <div class="form-group">
20
- <button type="button" class="btn btn-default bookly-btn-block-xs bookly-limitation" data-toggle="modal" data-target="#bookly-export-customers-dialog"><i class="glyphicon glyphicon-export"></i> <?php _e( 'Export to CSV', 'bookly' ) ?></button>
21
- </div>
22
- <div class="form-group">
23
- <button type="button" class="btn btn-default bookly-btn-block-xs" data-toggle="modal" data-target="#bookly-import-customers-dialog"><i class="glyphicon glyphicon-import"></i> <?php _e( 'Import', 'bookly' ) ?></button>
24
- </div>
25
- <div class="form-group">
26
- <button type="button" class="btn btn-success bookly-btn-block-xs" id="bookly-add" data-toggle="modal" data-target="#bookly-customer-dialog"><i class="glyphicon glyphicon-plus"></i> <?php _e( 'New customer', 'bookly' ) ?></button>
27
  </div>
28
  </div>
29
  </div>
@@ -31,29 +34,52 @@
31
  <table id="bookly-customers-list" class="table table-striped" width="100%">
32
  <thead>
33
  <tr>
34
- <th><?php echo esc_html( get_option( 'bookly_l10n_label_name' ) ) ?></th>
35
- <th><?php echo esc_html( get_option( 'bookly_l10n_label_first_name' ) ) ?></th>
36
- <th><?php echo esc_html( get_option( 'bookly_l10n_label_last_name' ) ) ?></th>
37
- <th><?php _e( 'User', 'bookly' ) ?></th>
38
- <th><?php echo esc_html( get_option( 'bookly_l10n_label_phone' ) ) ?></th>
39
- <th><?php echo esc_html( get_option( 'bookly_l10n_label_email' ) ) ?></th>
40
- <th><?php _e( 'Notes', 'bookly' ) ?></th>
41
- <th><?php _e( 'Last appointment', 'bookly' ) ?></th>
42
- <th><?php _e( 'Total appointments', 'bookly' ) ?></th>
43
- <th><?php _e( 'Payments', 'bookly' ) ?></th>
 
 
 
 
 
 
 
 
44
  <th></th>
45
- <th width="16"><input type="checkbox" id="bookly-check-all"></th>
46
  </tr>
47
  </thead>
48
  </table>
49
 
50
- <div class="text-right bookly-margin-top-lg">
51
- <?php \BooklyLite\Lib\Utils\Common::deleteButton() ?>
 
 
 
 
 
 
 
 
 
 
 
 
52
  </div>
53
  </div>
54
  </div>
55
 
56
- <?php include '_import.php' ?>
 
 
 
57
 
58
  <div id="bookly-delete-dialog" class="modal fade" tabindex=-1 role="dialog">
59
  <div class="modal-dialog">
@@ -63,20 +89,40 @@
63
  <div class="modal-title h2"><?php _e( 'Delete customers', 'bookly' ) ?></div>
64
  </div>
65
  <div class="modal-body">
66
- <?php _e( 'You are about to delete customers which may have WordPress accounts associated to them. Do you want to delete those accounts too (if there are any)?', 'bookly' ) ?>
67
  <div class="checkbox">
68
  <label>
69
- <input id="bookly-delete-remember-choice" type="checkbox" /><?php _e( 'Remember my choice', 'bookly' ) ?>
70
  </label>
71
  </div>
72
  </div>
73
  <div class="modal-footer">
74
  <button type="button" class="btn btn-danger ladda-button" id="bookly-delete-no" data-spinner-size="40" data-style="zoom-in">
75
- <span class="ladda-label"><i class="glyphicon glyphicon-trash"></i> <?php _e( 'No, delete just customers', 'bookly' ) ?></span>
76
  </button>
77
  <button type="button" class="btn btn-danger ladda-button" id="bookly-delete-yes" data-spinner-size="40" data-style="zoom-in">
78
- <span class="ladda-label"><i class="glyphicon glyphicon-trash"></i> <?php _e( 'Yes', 'bookly' ) ?></span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  </button>
 
80
  </div>
81
  </div>
82
  </div>
@@ -84,7 +130,7 @@
84
 
85
  <div ng-app="customer" ng-controller="customerCtrl">
86
  <div customer-dialog=saveCustomer(customer) customer="customer"></div>
87
- <?php \BooklyLite\Backend\Modules\Customers\Components::getInstance()->renderCustomerDialog() ?>
88
  </div>
89
  </div>
90
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ use Bookly\Backend\Components\Dialogs;
4
+ use Bookly\Backend\Components\Support;
5
+ use Bookly\Backend\Modules\Customers\Proxy;
6
+ ?>
7
  <div id="bookly-tbs" class="wrap">
8
  <div class="bookly-tbs-body">
9
  <div class="page-header text-right clearfix">
10
  <div class="bookly-page-title">
11
+ <?php esc_html_e( 'Customers', 'bookly' ) ?>
12
  </div>
13
+ <?php Support\Buttons::render( $self::pageSlug() ) ?>
14
  </div>
15
  <div class="panel panel-default bookly-main">
16
  <div class="panel-body">
20
  <input class="form-control" type="text" id="bookly-filter" placeholder="<?php esc_attr_e( 'Quick search customer', 'bookly' ) ?>" />
21
  </div>
22
  </div>
23
+ <div class="col-md-8 form-inline bookly-margin-bottom-lg text-right">
24
+ <?php
25
+ Proxy\Pro::renderExportButton();
26
+ Proxy\Pro::renderImportButton();
27
+ ?>
28
  <div class="form-group">
29
+ <button type="button" class="btn btn-success bookly-btn-block-xs" id="bookly-add" data-toggle="modal" data-target="#bookly-customer-dialog"><i class="glyphicon glyphicon-plus"></i> <?php esc_html_e( 'New customer', 'bookly' ) ?></button>
 
 
 
 
 
 
30
  </div>
31
  </div>
32
  </div>
34
  <table id="bookly-customers-list" class="table table-striped" width="100%">
35
  <thead>
36
  <tr>
37
+ <th><?php esc_html_e( get_option( 'bookly_l10n_label_name' ) ) ?></th>
38
+ <th><?php esc_html_e( get_option( 'bookly_l10n_label_first_name' ) ) ?></th>
39
+ <th><?php esc_html_e( get_option( 'bookly_l10n_label_last_name' ) ) ?></th>
40
+ <th><?php esc_html_e( 'User', 'bookly' ) ?></th>
41
+ <?php Proxy\CustomerGroups::renderCustomerTableHeader() ?>
42
+ <th><?php esc_html_e( get_option( 'bookly_l10n_label_phone' ) ) ?></th>
43
+ <th><?php esc_html_e( get_option( 'bookly_l10n_label_email' ) ) ?></th>
44
+ <?php foreach ( $info_fields as $field ) : ?>
45
+ <th><?php echo $field->label ?></th>
46
+ <?php endforeach ?>
47
+ <th><?php esc_html_e( 'Notes', 'bookly' ) ?></th>
48
+ <th><?php esc_html_e( 'Last appointment', 'bookly' ) ?></th>
49
+ <th><?php esc_html_e( 'Total appointments', 'bookly' ) ?></th>
50
+ <th><?php esc_html_e( 'Payments', 'bookly' ) ?></th>
51
+ <?php Proxy\Pro::renderCustomerAddressTableHeader() ?>
52
+ <?php if ( \Bookly\Lib\Config::proActive() ) : ?>
53
+ <th>Facebook</th>
54
+ <?php endif ?>
55
  <th></th>
56
+ <th width="16"><input type="checkbox" id="bookly-check-all" /></th>
57
  </tr>
58
  </thead>
59
  </table>
60
 
61
+ <div class="text-right form-inline bookly-margin-top-lg">
62
+ <div class="form-group">
63
+ <button type="button" id="bookly-merge-with" class="btn btn-default" data-toggle="modal" data-target="#bookly-merge-dialog" disabled="disabled" style="display:none"><i class="glyphicon glyphicon-road"></i> <?php esc_html_e( 'Merge with', 'bookly' ) ?></button>
64
+ </div>
65
+ <div class="form-group">
66
+ <button type="button" id="bookly-select-for-merge" class="btn btn-default"><i class="glyphicon glyphicon-plus"></i> <?php esc_html_e( 'Select for merge', 'bookly' ) ?></button>
67
+ </div>
68
+ <div class="form-group">
69
+ <?php Buttons::renderDelete() ?>
70
+ </div>
71
+ </div>
72
+
73
+ <div id="bookly-merge-list" class="bookly-margin-top-xlg" style="display:none">
74
+ <h4><?php esc_html_e( 'Merge list', 'bookly' ) ?></h4>
75
  </div>
76
  </div>
77
  </div>
78
 
79
+ <?php
80
+ Proxy\Pro::renderImportDialog();
81
+ Proxy\Pro::renderExportDialog( $info_fields );
82
+ ?>
83
 
84
  <div id="bookly-delete-dialog" class="modal fade" tabindex=-1 role="dialog">
85
  <div class="modal-dialog">
89
  <div class="modal-title h2"><?php _e( 'Delete customers', 'bookly' ) ?></div>
90
  </div>
91
  <div class="modal-body">
92
+ <?php esc_html_e( 'You are about to delete customers which may have WordPress accounts associated to them. Do you want to delete those accounts too (if there are any)?', 'bookly' ) ?>
93
  <div class="checkbox">
94
  <label>
95
+ <input id="bookly-delete-remember-choice" type="checkbox" /><?php esc_html_e( 'Remember my choice', 'bookly' ) ?>
96
  </label>
97
  </div>
98
  </div>
99
  <div class="modal-footer">
100
  <button type="button" class="btn btn-danger ladda-button" id="bookly-delete-no" data-spinner-size="40" data-style="zoom-in">
101
+ <span class="ladda-label"><i class="glyphicon glyphicon-trash"></i> <?php esc_html_e( 'No, delete just customers', 'bookly' ) ?></span>
102
  </button>
103
  <button type="button" class="btn btn-danger ladda-button" id="bookly-delete-yes" data-spinner-size="40" data-style="zoom-in">
104
+ <span class="ladda-label"><i class="glyphicon glyphicon-trash"></i> <?php esc_html_e( 'Yes', 'bookly' ) ?></span>
105
+ </button>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ <div id="bookly-merge-dialog" class="modal fade" tabindex=-1 role="dialog">
112
+ <div class="modal-dialog">
113
+ <div class="modal-content">
114
+ <div class="modal-header">
115
+ <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
116
+ <div class="modal-title h2"><?php _e( 'Merge customers', 'bookly' ) ?></div>
117
+ </div>
118
+ <div class="modal-body">
119
+ <?php esc_html_e( 'You are about to merge customers from the merge list with the selected one. This will result in losing the merged customers and moving all their appointments to the selected customer. Are you sure you want to continue?', 'bookly' ) ?>
120
+ </div>
121
+ <div class="modal-footer">
122
+ <button type="button" class="btn btn-danger ladda-button" id="bookly-merge" data-spinner-size="40" data-style="zoom-in">
123
+ <span class="ladda-label"><i class="glyphicon glyphicon-road"></i> <?php esc_html_e( 'Merge', 'bookly' ) ?></span>
124
  </button>
125
+ <?php Buttons::renderCustom( null, 'btn-default btn-lg', __( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
126
  </div>
127
  </div>
128
  </div>
130
 
131
  <div ng-app="customer" ng-controller="customerCtrl">
132
  <div customer-dialog=saveCustomer(customer) customer="customer"></div>
133
+ <?php Dialogs\Customer\Edit::render() ?>
134
  </div>
135
  </div>
136
  </div>
backend/modules/debug/Ajax.php ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Debug;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Ajax
8
+ * @package Bookly\Backend\Modules\Debug
9
+ */
10
+ class Ajax extends Page
11
+ {
12
+ /**
13
+ * Export database data.
14
+ */
15
+ public static function exportData()
16
+ {
17
+ /** @var \wpdb $wpdb */
18
+ global $wpdb;
19
+
20
+ $result = array();
21
+
22
+ foreach ( apply_filters( 'bookly_plugins', array() ) as $plugin ) {
23
+ /** @var Lib\Base\Plugin $plugin */
24
+ $installer_class = $plugin::getRootNamespace() . '\Lib\Installer';
25
+ /** @var Lib\Base\Installer $installer */
26
+ $installer = new $installer_class();
27
+
28
+ foreach ( $plugin::getEntityClasses() as $entity_class ) {
29
+ $table_name = $entity_class::getTableName();
30
+ $result['entities'][ $entity_class ] = array(
31
+ 'fields' => self::_getTableStructure( $table_name ),
32
+ 'values' => $wpdb->get_results( 'SELECT * FROM ' . $table_name, ARRAY_N )
33
+ );
34
+ }
35
+ $plugin_prefix = $plugin::getPrefix();
36
+ $options_postfix = array( 'data_loaded', 'grace_start', 'db_version', 'installation_time' );
37
+ if ( $plugin_prefix != 'bookly_' ) {
38
+ $options_postfix[] = 'enabled';
39
+ }
40
+ foreach ( $options_postfix as $option ) {
41
+ $option_name = $plugin_prefix . $option;
42
+ $result['options'][ $option_name ] = get_option( $option_name );
43
+ }
44
+
45
+ $result['options'][ $plugin::getPurchaseCodeOption() ] = $plugin::getPurchaseCode();
46
+ foreach ( $installer->getOptions() as $option_name => $option_value ) {
47
+ $result['options'][ $option_name ] = get_option( $option_name );
48
+ }
49
+ }
50
+
51
+ header( 'Content-type: application/json' );
52
+ header( 'Content-Disposition: attachment; filename=bookly_db_export_' . date( 'YmdHis' ) . '.json' );
53
+ echo json_encode( $result );
54
+
55
+ exit ( 0 );
56
+ }
57
+
58
+ /**
59
+ * Import database data.
60
+ */
61
+ public static function importData()
62
+ {
63
+ /** @var \wpdb $wpdb */
64
+ global $wpdb;
65
+
66
+ if ( $file = $_FILES['import']['name'] ) {
67
+ $json = file_get_contents( $_FILES['import']['tmp_name'] );
68
+ if ( $json !== false) {
69
+ $wpdb->query( 'SET FOREIGN_KEY_CHECKS = 0' );
70
+
71
+ $data = json_decode( $json, true );
72
+ /** @var Lib\Base\Plugin[] $bookly_plugins */
73
+ $bookly_plugins = apply_filters( 'bookly_plugins', array() );
74
+ foreach ( array_merge( array( 'bookly-responsive-appointment-booking-tool', 'bookly-addon-pro' ), array_keys( $bookly_plugins ) ) as $slug ) {
75
+ if ( ! array_key_exists( $slug, $bookly_plugins ) ) {
76
+ continue;
77
+ }
78
+ /** @var Lib\Base\Plugin $plugin */
79
+ $plugin = $bookly_plugins[ $slug ];
80
+ unset( $bookly_plugins[ $slug ] );
81
+ $installer_class = $plugin::getRootNamespace() . '\Lib\Installer';
82
+ /** @var Lib\Base\Installer $installer */
83
+ $installer = new $installer_class();
84
+
85
+ // Drop all data and options.
86
+ $installer->removeData();
87
+ $installer->dropTables();
88
+ $installer->createTables();
89
+
90
+ // Insert tables data.
91
+ foreach ( $plugin::getEntityClasses() as $entity_class ) {
92
+ if ( isset ( $data['entities'][ $entity_class ]['values'][0] ) ) {
93
+ $table_name = $entity_class::getTableName();
94
+ $query = sprintf(
95
+ 'INSERT INTO `%s` (`%s`) VALUES (%%s)',
96
+ $table_name,
97
+ implode( '`,`', $data['entities'][ $entity_class ]['fields'] )
98
+ );
99
+ $placeholders = array();
100
+ $values = array();
101
+ $counter = 0;
102
+ foreach ( $data['entities'][ $entity_class ]['values'] as $row ) {
103
+ $params = array();
104
+ foreach ( $row as $value ) {
105
+ if ( $value === null ) {
106
+ $params[] = 'NULL';
107
+ } else {
108
+ $params[] = '%s';
109
+ $values[] = $value;
110
+ }
111
+ }
112
+ $placeholders[] = implode( ',', $params );
113
+ if ( ++ $counter > 50 ) {
114
+ // Flush.
115
+ $wpdb->query( $wpdb->prepare( sprintf( $query, implode( '),(', $placeholders ) ), $values ) );
116
+ $placeholders = array();
117
+ $values = array();
118
+ $counter = 0;
119
+ }
120
+ }
121
+ if ( ! empty ( $placeholders ) ) {
122
+ $wpdb->query( $wpdb->prepare( sprintf( $query, implode( '),(', $placeholders ) ), $values ) );
123
+ }
124
+ }
125
+ }
126
+
127
+ // Insert options data.
128
+ foreach ( $installer->getOptions() as $option_name => $option_value ) {
129
+ add_option( $option_name, $data['options'][ $option_name ] );
130
+ }
131
+
132
+ $plugin_prefix = $plugin::getPrefix();
133
+ $options_postfix = array( 'data_loaded', 'grace_start', 'db_version' );
134
+ if ( $plugin_prefix != 'bookly_' ) {
135
+ $options_postfix[] = 'enabled';
136
+ }
137
+ foreach ( $options_postfix as $option ) {
138
+ $option_name = $plugin_prefix . $option;
139
+ add_option( $option_name, $data['options'][ $option_name ] );
140
+ }
141
+ }
142
+
143
+ header( 'Location: ' . admin_url( 'admin.php?page=bookly-debug&status=imported' ) );
144
+ }
145
+ }
146
+
147
+ header( 'Location: ' . admin_url( 'admin.php?page=bookly-debug' ) );
148
+
149
+ exit ( 0 );
150
+ }
151
+ }
backend/modules/debug/Controller.php DELETED
@@ -1,274 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Debug;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Debug
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- const page_slug = 'bookly-debug';
13
-
14
- const TABLE_STATUS_OK = 1;
15
- const TABLE_STATUS_ERROR = 0;
16
- const TABLE_STATUS_WARNING = 2;
17
-
18
- /**
19
- * Default action
20
- */
21
- public function index()
22
- {
23
- $this->enqueueStyles( array(
24
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
25
- 'module' => array( 'css/style.css' ),
26
- ) );
27
-
28
- $this->enqueueScripts( array(
29
- 'backend' => array( 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ) ),
30
- 'module' => array( 'js/debug.js' => array( 'jquery' ) ),
31
- ) );
32
-
33
- $debug = array();
34
- foreach ( Lib\Plugin::getEntityClasses() as $entity_class ) {
35
- $tableName = $entity_class::getTableName();
36
- $debug[ $tableName ] = array(
37
- 'fields' => null,
38
- 'constraints' => null,
39
- 'status' => null,
40
- );
41
- if ( $this->_tableExists( $tableName ) ) {
42
- $tableStructure = $this->_getTableStructure( $tableName );
43
- $tableConstraints = $this->_getTableConstraints( $tableName );
44
- $entitySchema = $entity_class::getSchema();
45
- $entityConstraints = $entity_class::getConstraints();
46
- $debug[ $tableName ]['status'] = self::TABLE_STATUS_OK;
47
- $debug[ $tableName ]['fields'] = array();
48
-
49
- // Comparing model schema with real DB schema
50
- foreach ( $entitySchema as $field => $data ) {
51
- if ( in_array( $field, $tableStructure ) ) {
52
- $debug[ $tableName ]['fields'][ $field ] = 1;
53
- } else {
54
- $debug[ $tableName ]['fields'][ $field ] = 0;
55
- $debug[ $tableName ]['status'] = self::TABLE_STATUS_WARNING;
56
- }
57
- }
58
-
59
- // Comparing model constraints with real DB constraints
60
- foreach ( $entityConstraints as $constraint ) {
61
- $key = $constraint['column_name'] . $constraint['referenced_table_name'] . $constraint['referenced_column_name'];
62
- $debug[ $tableName ]['constraints'][ $key ] = $constraint;
63
- if ( array_key_exists ( $key, $tableConstraints ) ) {
64
- $debug[ $tableName ]['constraints'][ $key ]['status'] = 1;
65
- } else {
66
- $debug[ $tableName ]['constraints'][ $key ]['status'] = 0;
67
- $debug[ $tableName ]['status'] = self::TABLE_STATUS_WARNING;
68
- }
69
- }
70
-
71
- } else {
72
- $debug[ $tableName ]['status'] = self::TABLE_STATUS_ERROR;
73
- }
74
- }
75
-
76
- $import_status = $this->getParameter( 'status' );
77
- $this->render( 'index', compact( 'debug', 'import_status' ) );
78
- }
79
-
80
- /**
81
- * Export database data.
82
- */
83
- public function executeExportData()
84
- {
85
- /** @var \wpdb $wpdb */
86
- global $wpdb;
87
-
88
- $result = array();
89
- $installer = new Lib\Installer();
90
-
91
- foreach ( Lib\Plugin::getEntityClasses() as $entity_class ) {
92
- $table_name = $entity_class::getTableName();
93
- $result['entities'][ $entity_class ] = array(
94
- 'fields' => $this->_getTableStructure( $table_name ),
95
- 'values' => $wpdb->get_results( 'SELECT * FROM ' . $table_name, ARRAY_N )
96
- );
97
- }
98
- $plugin_prefix = Lib\Plugin::getPrefix();
99
- $options_postfix = array( 'data_loaded', 'grace_start', 'db_version', 'installation_time' );
100
- if ( $plugin_prefix != 'bookly_' ) {
101
- $options_postfix[] = 'enabled';
102
- }
103
- foreach ( $options_postfix as $option ) {
104
- $option_name = $plugin_prefix . $option;
105
- $result['options'][ $option_name ] = get_option( $option_name );
106
- }
107
-
108
- $result['options'][ Lib\Plugin::getPurchaseCodeOption() ] = Lib\Plugin::getPurchaseCode();
109
- foreach ( $installer->getOptions() as $option_name => $option_value ) {
110
- $result['options'][ $option_name ] = get_option( $option_name );
111
- }
112
-
113
- header( 'Content-type: application/json' );
114
- header( 'Content-Disposition: attachment; filename=bookly_db_export_' . date( 'YmdHis' ) . '.json' );
115
- echo json_encode( $result );
116
-
117
- exit ( 0 );
118
- }
119
-
120
- /**
121
- * Import database data.
122
- */
123
- public function executeImportData()
124
- {
125
- /** @var \wpdb $wpdb */
126
- global $wpdb;
127
-
128
- if ( $file = $_FILES['import']['name'] ) {
129
- $json = file_get_contents( $_FILES['import']['tmp_name'] );
130
- if ( $json !== false) {
131
- $wpdb->query( 'SET FOREIGN_KEY_CHECKS = 0' );
132
-
133
- $data = json_decode( $json, true );
134
-
135
- $installer = new Lib\Installer();
136
-
137
- // Drop all data and options.
138
- $installer->removeData();
139
- $installer->dropTables();
140
- $installer->createTables();
141
-
142
- // Insert tables data.
143
- foreach ( Lib\Plugin::getEntityClasses() as $entity_class ) {
144
- if ( isset ( $data['entities'][ $entity_class ]['values'][0] ) ) {
145
- $table_name = $entity_class::getTableName();
146
- $query = sprintf(
147
- 'INSERT INTO `%s` (`%s`) VALUES (%%s)',
148
- $table_name,
149
- implode( '`,`', $data['entities'][ $entity_class ]['fields'] )
150
- );
151
- $placeholders = array();
152
- $values = array();
153
- $counter = 0;
154
- foreach ( $data['entities'][ $entity_class ]['values'] as $row ) {
155
- $params = array();
156
- foreach ( $row as $value ) {
157
- if ( $value === null ) {
158
- $params[] = 'NULL';
159
- } else {
160
- $params[] = '%s';
161
- $values[] = $value;
162
- }
163
- }
164
- $placeholders[] = implode( ',', $params );
165
- if ( ++ $counter > 50 ) {
166
- // Flush.
167
- $wpdb->query( $wpdb->prepare( sprintf( $query, implode( '),(', $placeholders ) ), $values ) );
168
- $placeholders = array();
169
- $values = array();
170
- $counter = 0;
171
- }
172
- }
173
- if ( ! empty ( $placeholders ) ) {
174
- $wpdb->query( $wpdb->prepare( sprintf( $query, implode( '),(', $placeholders ) ), $values ) );
175
- }
176
- }
177
- }
178
-
179
- // Insert options data.
180
- foreach ( $installer->getOptions() as $option_name => $option_value ) {
181
- add_option( $option_name, $data['options'][ $option_name ] );
182
- }
183
-
184
- $plugin_prefix = Lib\Plugin::getPrefix();
185
- $options_postfix = array( 'data_loaded', 'grace_start', 'db_version' );
186
- if ( $plugin_prefix != 'bookly_' ) {
187
- $options_postfix[] = 'enabled';
188
- }
189
- foreach ( $options_postfix as $option ) {
190
- $option_name = $plugin_prefix . $option;
191
- add_option( $option_name, $data['options'][ $option_name ] );
192
- }
193
-
194
- header( 'Location: ' . admin_url( 'admin.php?page=bookly-debug&status=imported' ) );
195
- }
196
- }
197
-
198
- header( 'Location: ' . admin_url( 'admin.php?page=bookly-debug' ) );
199
-
200
- exit ( 0 );
201
- }
202
-
203
- /**
204
- * Get table structure
205
- *
206
- * @param string $tableName
207
- * @return array
208
- */
209
- private function _getTableStructure( $tableName )
210
- {
211
- global $wpdb;
212
-
213
- $tableStructure = array();
214
- $results = $wpdb->get_results( 'DESCRIBE `' . $tableName . '`;' );
215
- if ( $results ) {
216
- foreach ( $results as $row ) {
217
- $tableStructure[] = $row->Field;
218
- }
219
- }
220
-
221
- return $tableStructure;
222
- }
223
-
224
- /**
225
- * Get table constraints
226
- *
227
- * @param string $tableName
228
- * @return array
229
- */
230
- private function _getTableConstraints( $tableName )
231
- {
232
- global $wpdb;
233
-
234
- $tableConstraints = array();
235
- $results = $wpdb->get_results(
236
- 'SELECT
237
- COLUMN_NAME,
238
- CONSTRAINT_NAME,
239
- REFERENCED_COLUMN_NAME,
240
- REFERENCED_TABLE_NAME
241
- FROM information_schema.KEY_COLUMN_USAGE
242
- WHERE
243
- TABLE_NAME = "' . $tableName . '"
244
- AND CONSTRAINT_SCHEMA = SCHEMA()
245
- AND CONSTRAINT_NAME <> "PRIMARY";'
246
- );
247
- if ( $results ) {
248
- foreach ( $results as $row ) {
249
- $constraint = array(
250
- 'column_name' => $row->COLUMN_NAME,
251
- 'referenced_table_name' => $row->REFERENCED_COLUMN_NAME,
252
- 'referenced_column_name' => $row->REFERENCED_TABLE_NAME,
253
- );
254
- $key = $row->COLUMN_NAME . $row->REFERENCED_TABLE_NAME . $row->REFERENCED_COLUMN_NAME;
255
- $tableConstraints[ $key ] = $constraint;
256
- }
257
- }
258
-
259
- return $tableConstraints;
260
- }
261
-
262
- /**
263
- * Verifying if table exists
264
- *
265
- * @param string $tableName
266
- * @return int
267
- */
268
- private function _tableExists( $tableName )
269
- {
270
- global $wpdb;
271
-
272
- return $wpdb->query( 'SHOW TABLES LIKE "' . $tableName . '"' );
273
- }
274
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/debug/Page.php ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Debug;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Page
8
+ * @package Bookly\Backend\Modules\Debug
9
+ */
10
+ class Page extends Lib\Base\Ajax
11
+ {
12
+ const TABLE_STATUS_OK = 1;
13
+ const TABLE_STATUS_ERROR = 0;
14
+ const TABLE_STATUS_WARNING = 2;
15
+
16
+ /**
17
+ * Render page.
18
+ */
19
+ public static function render()
20
+ {
21
+ self::enqueueStyles( array(
22
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
23
+ 'module' => array( 'css/style.css' ),
24
+ ) );
25
+
26
+ self::enqueueScripts( array(
27
+ 'backend' => array( 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ) ),
28
+ 'module' => array( 'js/debug.js' => array( 'jquery' ) ),
29
+ ) );
30
+
31
+ $debug = array();
32
+ /** @var Lib\Base\Plugin $plugin */
33
+ foreach ( apply_filters( 'bookly_plugins', array() ) as $plugin ) {
34
+ foreach ( $plugin::getEntityClasses() as $entity_class ) {
35
+ $tableName = $entity_class::getTableName();
36
+ $debug[ $tableName ] = array(
37
+ 'fields' => null,
38
+ 'constraints' => null,
39
+ 'status' => null,
40
+ );
41
+ if ( self::_tableExists( $tableName ) ) {
42
+ $tableStructure = self::_getTableStructure( $tableName );
43
+ $tableConstraints = self::_getTableConstraints( $tableName );
44
+ $entitySchema = $entity_class::getSchema();
45
+ $entityConstraints = $entity_class::getConstraints();
46
+ $debug[ $tableName ]['status'] = self::TABLE_STATUS_OK;
47
+ $debug[ $tableName ]['fields'] = array();
48
+
49
+ // Comparing model schema with real DB schema
50
+ foreach ( $entitySchema as $field => $data ) {
51
+ if ( in_array( $field, $tableStructure ) ) {
52
+ $debug[ $tableName ]['fields'][ $field ] = 1;
53
+ } else {
54
+ $debug[ $tableName ]['fields'][ $field ] = 0;
55
+ $debug[ $tableName ]['status'] = self::TABLE_STATUS_WARNING;
56
+ }
57
+ }
58
+
59
+ // Comparing model constraints with real DB constraints
60
+ foreach ( $entityConstraints as $constraint ) {
61
+ $key = $constraint['column_name'] . $constraint['referenced_table_name'] . $constraint['referenced_column_name'];
62
+ $debug[ $tableName ]['constraints'][ $key ] = $constraint;
63
+ if ( array_key_exists ( $key, $tableConstraints ) ) {
64
+ $debug[ $tableName ]['constraints'][ $key ]['status'] = 1;
65
+ } else {
66
+ $debug[ $tableName ]['constraints'][ $key ]['status'] = 0;
67
+ $debug[ $tableName ]['status'] = self::TABLE_STATUS_WARNING;
68
+ }
69
+ }
70
+
71
+ } else {
72
+ $debug[ $tableName ]['status'] = self::TABLE_STATUS_ERROR;
73
+ }
74
+ }
75
+ }
76
+
77
+ $import_status = self::parameter( 'status' );
78
+ self::renderTemplate( 'index', compact( 'debug', 'import_status' ) );
79
+ }
80
+
81
+ /**
82
+ * Get table structure
83
+ *
84
+ * @param string $tableName
85
+ * @return array
86
+ */
87
+ protected static function _getTableStructure( $tableName )
88
+ {
89
+ global $wpdb;
90
+
91
+ $tableStructure = array();
92
+ $results = $wpdb->get_results( 'DESCRIBE `' . $tableName . '`;' );
93
+ if ( $results ) {
94
+ foreach ( $results as $row ) {
95
+ $tableStructure[] = $row->Field;
96
+ }
97
+ }
98
+
99
+ return $tableStructure;
100
+ }
101
+
102
+ /**
103
+ * Get table constraints
104
+ *
105
+ * @param string $tableName
106
+ * @return array
107
+ */
108
+ protected static function _getTableConstraints( $tableName )
109
+ {
110
+ global $wpdb;
111
+
112
+ $tableConstraints = array();
113
+ $results = $wpdb->get_results(
114
+ 'SELECT
115
+ COLUMN_NAME,
116
+ CONSTRAINT_NAME,
117
+ REFERENCED_COLUMN_NAME,
118
+ REFERENCED_TABLE_NAME
119
+ FROM information_schema.KEY_COLUMN_USAGE
120
+ WHERE
121
+ TABLE_NAME = "' . $tableName . '"
122
+ AND CONSTRAINT_SCHEMA = SCHEMA()
123
+ AND CONSTRAINT_NAME <> "PRIMARY";'
124
+ );
125
+ if ( $results ) {
126
+ foreach ( $results as $row ) {
127
+ $constraint = array(
128
+ 'column_name' => $row->COLUMN_NAME,
129
+ 'referenced_table_name' => $row->REFERENCED_COLUMN_NAME,
130
+ 'referenced_column_name' => $row->REFERENCED_TABLE_NAME,
131
+ );
132
+ $key = $row->COLUMN_NAME . $row->REFERENCED_TABLE_NAME . $row->REFERENCED_COLUMN_NAME;
133
+ $tableConstraints[ $key ] = $constraint;
134
+ }
135
+ }
136
+
137
+ return $tableConstraints;
138
+ }
139
+
140
+ /**
141
+ * Verifying if table exists
142
+ *
143
+ * @param string $tableName
144
+ * @return int
145
+ */
146
+ protected static function _tableExists( $tableName )
147
+ {
148
+ global $wpdb;
149
+
150
+ return $wpdb->query( 'SHOW TABLES LIKE "' . $tableName . '"' );
151
+ }
152
+ }
backend/modules/debug/templates/index.php CHANGED
@@ -1,4 +1,6 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
2
  <div id="bookly-tbs" class="wrap">
3
  <div class="bookly-tbs-body">
4
  <div class="page-header text-right clearfix">
@@ -15,7 +17,7 @@
15
  <div class="panel-group" id="data-management">
16
  <div class="bookly-data-button">
17
  <form action="<?php echo admin_url( 'admin-ajax.php?action=bookly_export_data' ) ?>" method="POST">
18
- <?php \BooklyLite\Lib\Utils\Common::csrf() ?>
19
  <button id="bookly-export" type="submit" class="btn btn-lg btn-success">
20
  <span class="ladda-label">Export data</span>
21
  </button>
@@ -23,7 +25,7 @@
23
  </div>
24
  <div class="bookly-data-button">
25
  <form id="bookly_import" action="<?php echo admin_url( 'admin-ajax.php?action=bookly_import_data' ) ?>" method="POST" enctype="multipart/form-data">
26
- <?php \BooklyLite\Lib\Utils\Common::csrf() ?>
27
  <div id="bookly-import" class="btn btn-lg btn-primary btn-file">
28
  <span class="ladda-label">Import data</span>
29
  <input type="file" id="bookly_import_file" name="import">
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Inputs;
3
+ ?>
4
  <div id="bookly-tbs" class="wrap">
5
  <div class="bookly-tbs-body">
6
  <div class="page-header text-right clearfix">
17
  <div class="panel-group" id="data-management">
18
  <div class="bookly-data-button">
19
  <form action="<?php echo admin_url( 'admin-ajax.php?action=bookly_export_data' ) ?>" method="POST">
20
+ <?php Inputs::renderCsrf() ?>
21
  <button id="bookly-export" type="submit" class="btn btn-lg btn-success">
22
  <span class="ladda-label">Export data</span>
23
  </button>
25
  </div>
26
  <div class="bookly-data-button">
27
  <form id="bookly_import" action="<?php echo admin_url( 'admin-ajax.php?action=bookly_import_data' ) ?>" method="POST" enctype="multipart/form-data">
28
+ <?php Inputs::renderCsrf() ?>
29
  <div id="bookly-import" class="btn btn-lg btn-primary btn-file">
30
  <span class="ladda-label">Import data</span>
31
  <input type="file" id="bookly_import_file" name="import">
backend/modules/message/Controller.php DELETED
@@ -1,100 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Message;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Message
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- const page_slug = 'bookly-messages';
13
-
14
- /**
15
- * Default action
16
- */
17
- public function index()
18
- {
19
- $this->enqueueStyles( array(
20
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
21
- ) );
22
-
23
- $this->enqueueScripts( array(
24
- 'backend' => array(
25
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
26
- 'js/datatables.min.js' => array( 'jquery' ),
27
- ),
28
- 'module' => array( 'js/message.js' => array( 'jquery' ) ),
29
- ) );
30
-
31
- wp_localize_script( 'bookly-message.js', 'BooklyL10n', array(
32
- 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
33
- 'datatable' => array(
34
- 'zeroRecords' => __( 'No records.', 'bookly' ),
35
- 'processing' => __( 'Processing...', 'bookly' ),
36
- 'per_page' => __( 'messages', 'bookly' ),
37
- 'paginate' => array(
38
- 'first' => __( 'First', 'bookly' ),
39
- 'previous' => __( 'Previous', 'bookly' ),
40
- 'next' => __( 'Next', 'bookly' ),
41
- 'last' => __( 'Last', 'bookly' ),
42
- )
43
- )
44
- ) );
45
- $this->render( 'index' );
46
- }
47
-
48
- /**
49
- * Get messages
50
- */
51
- public function executeGetMessages()
52
- {
53
- $query = Lib\Entities\Message::query( 'm' );
54
- $total = $query->count();
55
-
56
- $query->select( 'm.created, m.subject, m.seen, m.body, m.message_id' )
57
- ->sortBy( 'm.seen, m.message_id' )->order( 'DESC' );
58
-
59
- $query->limit( $this->getParameter( 'length' ) )->offset( $this->getParameter( 'start' ) );
60
-
61
- $data = $query->fetchArray();
62
- foreach ( $data as &$row ) {
63
- $row['created'] = Lib\Utils\DateTime::formatDateTime( $row['created'] );
64
- }
65
-
66
- wp_send_json( array(
67
- 'draw' => ( int ) $this->getParameter( 'draw' ),
68
- 'recordsTotal' => $total,
69
- 'recordsFiltered' => count( $data ),
70
- 'data' => $data,
71
- ) );
72
- }
73
-
74
- /**
75
- * Mark all messages was read
76
- */
77
- public function executeMarkReadAllMessages()
78
- {
79
- $messages = Lib\Entities\Message::query( 'm' )->select( 'm.message_id' )->whereNot( 'm.seen', 1 )->fetchArray();
80
- $message_ids = array();
81
- foreach ( $messages as $message ) {
82
- $message_ids[] = $message['message_id'];
83
- }
84
-
85
- if ( $message_ids ) {
86
- Lib\API::seenMessages( $message_ids );
87
- }
88
- wp_send_json_success();
89
- }
90
-
91
- /**
92
- * Mark some massages was read
93
- */
94
- public function executeMarkReadMessages()
95
- {
96
- Lib\API::seenMessages( (array) $this->getParameter( 'message_ids' ) );
97
- wp_send_json_success();
98
- }
99
-
100
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/messages/Ajax.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Messages;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Page
8
+ * @package Bookly\Backend\Modules\Messages
9
+ */
10
+ class Ajax extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * Get messages
14
+ */
15
+ public static function getMessages()
16
+ {
17
+ $query = Lib\Entities\Message::query( 'm' );
18
+ $total = $query->count();
19
+
20
+ $query->select( 'm.created, m.subject, m.seen, m.body, m.message_id' )
21
+ ->sortBy( 'm.seen, m.message_id' )->order( 'DESC' );
22
+
23
+ $query->limit( self::parameter( 'length' ) )->offset( self::parameter( 'start' ) );
24
+
25
+ $data = $query->fetchArray();
26
+ foreach ( $data as &$row ) {
27
+ $row['created'] = Lib\Utils\DateTime::formatDateTime( $row['created'] );
28
+ }
29
+
30
+ wp_send_json( array(
31
+ 'draw' => ( int ) self::parameter( 'draw' ),
32
+ 'recordsTotal' => $total,
33
+ 'recordsFiltered' => count( $data ),
34
+ 'data' => $data,
35
+ ) );
36
+ }
37
+
38
+ /**
39
+ * Mark all messages was read
40
+ */
41
+ public static function markReadAllMessages()
42
+ {
43
+ $messages = Lib\Entities\Message::query( 'm' )->select( 'm.message_id' )->whereNot( 'm.seen', 1 )->fetchArray();
44
+ $message_ids = array();
45
+ foreach ( $messages as $message ) {
46
+ $message_ids[] = $message['message_id'];
47
+ }
48
+
49
+ if ( $message_ids ) {
50
+ Lib\API::seenMessages( $message_ids );
51
+ }
52
+ wp_send_json_success();
53
+ }
54
+
55
+ /**
56
+ * Mark some massages was read
57
+ */
58
+ public static function markReadMessages()
59
+ {
60
+ Lib\API::seenMessages( (array) self::parameter( 'message_ids' ) );
61
+ wp_send_json_success();
62
+ }
63
+ }
backend/modules/messages/Page.php ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Messages;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Page
8
+ * @package Bookly\Backend\Modules\Messages
9
+ */
10
+ class Page extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render page.
14
+ */
15
+ public static function render()
16
+ {
17
+ self::enqueueStyles( array(
18
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
19
+ ) );
20
+
21
+ self::enqueueScripts( array(
22
+ 'backend' => array(
23
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
24
+ 'js/datatables.min.js' => array( 'jquery' ),
25
+ ),
26
+ 'module' => array( 'js/message.js' => array( 'jquery' ) ),
27
+ ) );
28
+
29
+ wp_localize_script( 'bookly-message.js', 'BooklyL10n', array(
30
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
31
+ 'datatable' => array(
32
+ 'zeroRecords' => __( 'No records.', 'bookly' ),
33
+ 'processing' => __( 'Processing...', 'bookly' ),
34
+ 'per_page' => __( 'messages', 'bookly' ),
35
+ 'paginate' => array(
36
+ 'first' => __( 'First', 'bookly' ),
37
+ 'previous' => __( 'Previous', 'bookly' ),
38
+ 'next' => __( 'Next', 'bookly' ),
39
+ 'last' => __( 'Last', 'bookly' ),
40
+ )
41
+ )
42
+ ) );
43
+
44
+ self::renderTemplate( 'index' );
45
+ }
46
+
47
+ /**
48
+ * @return int
49
+ */
50
+ public static function getMessagesCount()
51
+ {
52
+ return Lib\Entities\Message::query()
53
+ ->where( 'seen', 0 )
54
+ ->count();
55
+ }
56
+
57
+ /**
58
+ * Show 'Messages' submenu with counter inside Bookly main menu
59
+ */
60
+ public static function addBooklyMenuItem()
61
+ {
62
+ $messages = __( 'Messages', 'bookly' );
63
+ $count = self::getMessagesCount();
64
+ if ( $count ) {
65
+ add_submenu_page( 'bookly-menu', $messages, sprintf( '%s <span class="update-plugins count-%d"><span class="update-count">%d</span></span>', $messages, $count, $count ), 'manage_options',
66
+ self::pageSlug(), function () { Page::render(); } );
67
+ } else {
68
+ add_submenu_page( 'bookly-menu', $messages, $messages, 'manage_options',
69
+ self::pageSlug(), function () { Page::render(); } );
70
+ }
71
+ }
72
+
73
+ }
backend/modules/{message → messages}/resources/js/message.js RENAMED
File without changes
backend/modules/{message → messages}/templates/index.php RENAMED
@@ -1,11 +1,13 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
2
  <div id="bookly-tbs" class="wrap">
3
  <div class="bookly-tbs-body">
4
  <div class="page-header text-right clearfix">
5
  <div class="bookly-page-title">
6
  <?php _e( 'Messages', 'bookly' ) ?>
7
  </div>
8
- <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
9
  </div>
10
  <div class="panel panel-default bookly-main">
11
  <div class="panel-body">
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components;
3
+ ?>
4
  <div id="bookly-tbs" class="wrap">
5
  <div class="bookly-tbs-body">
6
  <div class="page-header text-right clearfix">
7
  <div class="bookly-page-title">
8
  <?php _e( 'Messages', 'bookly' ) ?>
9
  </div>
10
+ <?php Components\Support\Buttons::render( $self::pageSlug() ) ?>
11
  </div>
12
  <div class="panel panel-default bookly-main">
13
  <div class="panel-body">
backend/modules/notifications/Ajax.php ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Notifications;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Ajax
8
+ * @package Bookly\Backend\Modules\Notifications
9
+ */
10
+ class Ajax extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * Get notifications data.
14
+ */
15
+ public static function getEmailNotificationsData()
16
+ {
17
+ $form = new Forms\Notifications( 'email' );
18
+
19
+ $bookly_email_sender_name = get_option( 'bookly_email_sender_name' ) == '' ?
20
+ get_option( 'blogname' ) : get_option( 'bookly_email_sender_name' );
21
+
22
+ $bookly_email_sender = get_option( 'bookly_email_sender' ) == '' ?
23
+ get_option( 'admin_email' ) : get_option( 'bookly_email_sender' );
24
+
25
+ $notifications = array();
26
+ foreach ( $form->getData() as $notification ) {
27
+ if ( Lib\Config::proActive() || in_array( $notification['type'], Lib\Entities\Notification::$bookly_notifications['email'] ) ) {
28
+ $name = Lib\Entities\Notification::getNameIfExists( $notification['type'] );
29
+ if ( $name !== false ) {
30
+ if ( in_array( $notification['type'], Lib\Entities\Notification::getCustomNotificationTypes() ) && $notification['subject'] != '' ) {
31
+ // In window Test Email Notification
32
+ // for custom notification, subject is name.
33
+ $name = $notification['subject'];
34
+ }
35
+ $notifications[] = array(
36
+ 'id' => $notification['id'],
37
+ 'name' => $name,
38
+ 'active' => $notification['active'],
39
+ );
40
+ }
41
+ }
42
+ }
43
+
44
+ $result = array(
45
+ 'notifications' => $notifications,
46
+ 'sender_email' => $bookly_email_sender,
47
+ 'sender_name' => $bookly_email_sender_name,
48
+ 'send_as' => get_option( 'bookly_email_send_as' ),
49
+ 'reply_to_customers' => get_option( 'bookly_email_reply_to_customers' ),
50
+ );
51
+
52
+ wp_send_json_success( $result );
53
+ }
54
+
55
+ /**
56
+ * Test email notifications.
57
+ */
58
+ public static function testEmailNotifications()
59
+ {
60
+ $to_email = self::parameter( 'to_email' );
61
+ $sender_name = self::parameter( 'sender_name' );
62
+ $sender_email = self::parameter( 'sender_email' );
63
+ $send_as = self::parameter( 'send_as' );
64
+ $notifications = self::parameter( 'notifications' );
65
+ $reply_to_customers = self::parameter( 'reply_to_customers' );
66
+
67
+ // Change 'Content-Type' and 'Reply-To' for test email notification.
68
+ add_filter( 'bookly_email_headers', function ( $headers ) use ( $sender_name, $sender_email, $send_as, $reply_to_customers ) {
69
+ $headers = array();
70
+ if ( $send_as == 'html' ) {
71
+ $headers[] = 'Content-Type: text/html; charset=utf-8';
72
+ } else {
73
+ $headers[] = 'Content-Type: text/plain; charset=utf-8';
74
+ }
75
+ $headers[] = 'From: ' . $sender_name . ' <' . $sender_email . '>';
76
+ if ( $reply_to_customers ) {
77
+ $headers[] = 'Reply-To: ' . $sender_name . ' <' . $sender_email . '>';
78
+ }
79
+
80
+ return $headers;
81
+ }, 10, 1 );
82
+
83
+ Lib\Notifications\Sender::sendTestEmailNotifications( $to_email, $notifications, $send_as );
84
+
85
+ wp_send_json_success();
86
+ }
87
+
88
+ /**
89
+ * Delete custom notification
90
+ */
91
+ public static function deleteCustomNotification()
92
+ {
93
+ $id = self::parameter( 'id' );
94
+ Lib\Entities\Notification::query()
95
+ ->delete()
96
+ ->where( 'id', $id )
97
+ ->whereIn( 'type', Lib\Entities\Notification::getCustomNotificationTypes() )
98
+ ->execute();
99
+
100
+ wp_send_json_success();
101
+ }
102
+ }
backend/modules/notifications/Components.php DELETED
@@ -1,357 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Notifications;
3
-
4
- use BooklyLite\Lib;
5
- use BooklyLite\Lib\Entities\Notification;
6
-
7
- /**
8
- * Class Components
9
- * @package BooklyLite\Backend\Modules\Notifications
10
- */
11
- class Components extends Lib\Base\Components
12
- {
13
- protected $css_prefix = 'bookly-js-codes-';
14
-
15
- /**
16
- * Codes for all notifications.
17
- *
18
- * @return array
19
- */
20
- private function getCommonCodes()
21
- {
22
- return array(
23
- array( 'code' => 'company_name', 'description' => __( 'name of company', 'bookly' ) ),
24
- array( 'code' => 'company_logo', 'description' => __( 'company logo', 'bookly' ) ),
25
- array( 'code' => 'company_address', 'description' => __( 'address of company', 'bookly' ) ),
26
- array( 'code' => 'company_phone', 'description' => __( 'company phone', 'bookly' ) ),
27
- array( 'code' => 'company_website', 'description' => __( 'company web-site address', 'bookly' ) ),
28
- );
29
- }
30
-
31
- /**
32
- * Render codes for notifications
33
- *
34
- * @param string $notification_type
35
- */
36
- public function renderCodes( $notification_type )
37
- {
38
- switch ( $notification_type ) {
39
- case Notification::TYPE_APPOINTMENT_START_TIME:
40
- case Notification::TYPE_CUSTOMER_APPOINTMENT_CREATED:
41
- case Notification::TYPE_LAST_CUSTOMER_APPOINTMENT:
42
- case Notification::TYPE_CUSTOMER_APPOINTMENT_STATUS_CHANGED:
43
- $this->renderCustomerAppointmentCodesForCN( $notification_type );
44
- break;
45
- case 'staff_agenda':
46
- case Notification::TYPE_STAFF_DAY_AGENDA:
47
- $this->renderStaffDayAgendaCodes();
48
- break;
49
- case 'client_birthday_greeting':
50
- case Notification::TYPE_CUSTOMER_BIRTHDAY:
51
- $this->renderBirthdayGreetingCodes();
52
- break;
53
- case 'client_new_wp_user':
54
- $this->renderNewWpUserCodes();
55
- break;
56
- case 'client_pending_appointment_cart':
57
- case 'client_approved_appointment_cart':
58
- $this->renderCompoundCodes();
59
- break;
60
- case 'staff_waiting_list':
61
- $this->renderWaitingListCodes();
62
- break;
63
- case 'client_pending_appointment':
64
- case 'staff_pending_appointment':
65
- case 'client_approved_appointment':
66
- case 'staff_approved_appointment':
67
- case 'client_cancelled_appointment':
68
- case 'staff_cancelled_appointment':
69
- case 'client_rejected_appointment':
70
- case 'staff_rejected_appointment':
71
- case 'client_waitlisted_appointment':
72
- case 'staff_waitlisted_appointment':
73
- case 'client_reminder':
74
- case 'client_reminder_1st':
75
- case 'client_reminder_2nd':
76
- case 'client_reminder_3rd':
77
- case 'client_follow_up':
78
- $this->renderBaseCodes();
79
- break;
80
- case 'staff_package_purchased':
81
- case 'client_package_purchased':
82
- case 'staff_package_deleted':
83
- case 'client_package_deleted':
84
- $this->renderPackageCodes( $notification_type );
85
- break;
86
- case 'client_pending_recurring_appointment':
87
- case 'staff_pending_recurring_appointment':
88
- case 'client_approved_recurring_appointment':
89
- case 'staff_approved_recurring_appointment':
90
- case 'client_cancelled_recurring_appointment':
91
- case 'staff_cancelled_recurring_appointment':
92
- case 'client_rejected_recurring_appointment':
93
- case 'staff_rejected_recurring_appointment':
94
- case 'client_waitlisted_recurring_appointment':
95
- case 'staff_waitlisted_recurring_appointment':
96
- $this->renderRecurringCodes();
97
- break;
98
- }
99
- }
100
-
101
- /**
102
- * Render codes for custom notifications with appointment(s)
103
- *
104
- * @param string $notification_type
105
- */
106
- private function renderCustomerAppointmentCodesForCN( $notification_type )
107
- {
108
- $codes = $this->getCommonCodes();
109
- $codes[] = array( 'code' => 'appointment_date', 'description' => __( 'date of appointment', 'bookly' ) );
110
- $codes[] = array( 'code' => 'appointment_end_date', 'description' => __( 'end date of appointment', 'bookly' ) );
111
- $codes[] = array( 'code' => 'appointment_end_time', 'description' => __( 'end time of appointment', 'bookly' ) );
112
- $codes[] = array( 'code' => 'appointment_notes', 'description' => __( 'customer notes for appointment', 'bookly' ) );
113
- $codes[] = array( 'code' => 'appointment_time', 'description' => __( 'time of appointment', 'bookly' ) );
114
- $codes[] = array( 'code' => 'approve_appointment_url', 'description' => esc_html__( 'URL of approve appointment link (to use inside <a> tag)', 'bookly' ) );
115
- $codes[] = array( 'code' => 'booking_number', 'description' => __( 'booking number', 'bookly' ) );
116
- $codes[] = array( 'code' => 'cancel_appointment', 'description' => __( 'cancel appointment link', 'bookly' ) );
117
- $codes[] = array( 'code' => 'cancel_appointment_confirm_url', 'description' => esc_html__( 'URL of cancel appointment link with confirmation (to use inside <a> tag)', 'bookly' ) );
118
- $codes[] = array( 'code' => 'cancel_appointment_url', 'description' => esc_html__( 'URL of cancel appointment link (to use inside <a> tag)', 'bookly' ) );
119
- $codes[] = array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) );
120
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
121
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
122
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
123
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
124
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
125
- $codes[] = array( 'code' => 'google_calendar_url', 'description' => esc_html__( 'URL for adding event to client\'s Google Calendar (to use inside <a> tag)', 'bookly' ) );
126
- $codes[] = array( 'code' => 'number_of_persons', 'description' => __( 'number of persons', 'bookly' ) );
127
- $codes[] = array( 'code' => 'payment_type', 'description' => __( 'payment type', 'bookly' ) );
128
- $codes[] = array( 'code' => 'reject_appointment_url', 'description' => esc_html__( 'URL of reject appointment link (to use inside <a> tag)', 'bookly' ) );
129
- $codes[] = array( 'code' => 'service_duration', 'description' => __( 'duration of service', 'bookly' ) );
130
- $codes[] = array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) );
131
- $codes[] = array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) );
132
- $codes[] = array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ) );
133
- $codes[] = array( 'code' => 'staff_email', 'description' => __( 'email of staff', 'bookly' ) );
134
- $codes[] = array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) );
135
- $codes[] = array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) );
136
- $codes[] = array( 'code' => 'staff_phone', 'description' => __( 'phone of staff', 'bookly' ) );
137
- $codes[] = array( 'code' => 'staff_photo', 'description' => __( 'photo of staff', 'bookly' ) );
138
- $codes[] = array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) );
139
-
140
- $codes = Lib\Proxy\Packages::prepareNotificationCodesList( $codes );
141
- $codes = Lib\Proxy\RecurringAppointments::prepareNotificationCodesList( $codes );
142
-
143
- Lib\Utils\Common::codes(
144
- Lib\Proxy\Shared::prepareNotificationCodesList( $codes, 'customer' ),
145
- array( 'type' => $notification_type )
146
- );
147
- }
148
-
149
- /**
150
- * Render codes for staff agenda.
151
- */
152
- private function renderStaffDayAgendaCodes()
153
- {
154
- $codes = $this->getCommonCodes();
155
- $codes[] = array( 'code' => 'agenda_date', 'description' => __( 'agenda date', 'bookly' ) );
156
- $codes[] = array( 'code' => 'next_day_agenda', 'description' => __( 'staff agenda for next day', 'bookly' ) );
157
- $codes[] = array( 'code' => 'staff_email', 'description' => __( 'email of staff', 'bookly' ) );
158
- $codes[] = array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) );
159
- $codes[] = array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) );
160
- $codes[] = array( 'code' => 'staff_phone', 'description' => __( 'phone of staff', 'bookly' ) );
161
- $codes[] = array( 'code' => 'staff_photo', 'description' => __( 'photo of staff', 'bookly' ) );
162
- $codes[] = array( 'code' => 'tomorrow_date', 'description' => __( 'date of next day', 'bookly' ) );
163
-
164
- Lib\Utils\Common::codes(
165
- $codes,
166
- array( 'type' => Notification::TYPE_STAFF_DAY_AGENDA )
167
- );
168
- }
169
-
170
- /**
171
- * Render codes for Greeting notifications
172
- */
173
- private function renderBirthdayGreetingCodes()
174
- {
175
- $codes = $this->getCommonCodes();
176
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
177
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
178
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
179
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
180
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
181
-
182
- Lib\Utils\Common::codes(
183
- $codes,
184
- array( 'type' => Notification::TYPE_CUSTOMER_BIRTHDAY )
185
- );
186
- }
187
-
188
- /**
189
- * Render codes for new WordPress users.
190
- */
191
- private function renderNewWpUserCodes()
192
- {
193
- $codes = $this->getCommonCodes();
194
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
195
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
196
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
197
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
198
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
199
- $codes[] = array( 'code' => 'new_password', 'description' => __( 'customer new password', 'bookly' ) );
200
- $codes[] = array( 'code' => 'new_username', 'description' => __( 'customer new username', 'bookly' ) );
201
- $codes[] = array( 'code' => 'site_address', 'description' => __( 'site address', 'bookly' ) );
202
-
203
- Lib\Utils\Common::codes( $codes );
204
- }
205
-
206
- /**
207
- * Render codes for compound (cart) notifications.
208
- */
209
- private function renderCompoundCodes()
210
- {
211
- $codes = $this->getCommonCodes();
212
- $codes[] = array( 'code' => 'cart_info', 'description' => __( 'cart information', 'bookly' ) );
213
- $codes[] = array( 'code' => 'cart_info_c', 'description' => __( 'cart information with cancel', 'bookly' ) );
214
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
215
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
216
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
217
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
218
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
219
- $codes[] = array( 'code' => 'payment_type', 'description' => __( 'payment type', 'bookly' ) );
220
- $codes[] = array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) );
221
-
222
- Lib\Utils\Common::codes( Lib\Proxy\Shared::prepareCartNotificationShortCodes( $codes ) );
223
- }
224
-
225
- /**
226
- * Render base codes
227
- */
228
- private function renderBaseCodes()
229
- {
230
- $codes = $this->getCommonCodes();
231
- $codes[] = array( 'code' => 'appointment_date', 'description' => __( 'date of appointment', 'bookly' ) );
232
- $codes[] = array( 'code' => 'appointment_end_date', 'description' => __( 'end date of appointment', 'bookly' ) );
233
- $codes[] = array( 'code' => 'appointment_end_time', 'description' => __( 'end time of appointment', 'bookly' ) );
234
- $codes[] = array( 'code' => 'appointment_notes', 'description' => __( 'customer notes for appointment', 'bookly' ) );
235
- $codes[] = array( 'code' => 'appointment_time', 'description' => __( 'time of appointment', 'bookly' ) );
236
- $codes[] = array( 'code' => 'approve_appointment_url', 'description' => esc_html__( 'URL of approve appointment link (to use inside <a> tag)', 'bookly' ) );
237
- $codes[] = array( 'code' => 'booking_number', 'description' => __( 'booking number', 'bookly' ) );
238
- $codes[] = array( 'code' => 'cancel_appointment', 'description' => __( 'cancel appointment link', 'bookly' ) );
239
- $codes[] = array( 'code' => 'cancel_appointment_confirm_url', 'description' => esc_html__( 'URL of cancel appointment link with confirmation (to use inside <a> tag)', 'bookly' ) );
240
- $codes[] = array( 'code' => 'cancel_appointment_url', 'description' => esc_html__( 'URL of cancel appointment link (to use inside <a> tag)', 'bookly' ) );
241
- $codes[] = array( 'code' => 'cancellation_reason', 'description' => __( 'reason you mentioned while deleting appointment', 'bookly' ) );
242
- $codes[] = array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) );
243
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
244
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
245
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
246
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
247
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
248
- $codes[] = array( 'code' => 'google_calendar_url', 'description' => esc_html__( 'URL for adding event to client\'s Google Calendar (to use inside <a> tag)', 'bookly' ) );
249
- $codes[] = array( 'code' => 'number_of_persons', 'description' => __( 'number of persons', 'bookly' ) );
250
- $codes[] = array( 'code' => 'payment_type', 'description' => __( 'payment type', 'bookly' ) );
251
- $codes[] = array( 'code' => 'reject_appointment_url', 'description' => esc_html__( 'URL of reject appointment link (to use inside <a> tag)', 'bookly' ) );
252
- $codes[] = array( 'code' => 'service_duration', 'description' => __( 'duration of service', 'bookly' ) );
253
- $codes[] = array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) );
254
- $codes[] = array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) );
255
- $codes[] = array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ) );
256
- $codes[] = array( 'code' => 'staff_email', 'description' => __( 'email of staff', 'bookly' ) );
257
- $codes[] = array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) );
258
- $codes[] = array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) );
259
- $codes[] = array( 'code' => 'staff_phone', 'description' => __( 'phone of staff', 'bookly' ) );
260
- $codes[] = array( 'code' => 'staff_photo', 'description' => __( 'photo of staff', 'bookly' ) );
261
- $codes[] = array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) );
262
-
263
- Lib\Utils\Common::codes(
264
- Lib\Proxy\Shared::prepareNotificationCodesList( $codes, 'customer' )
265
- );
266
- }
267
-
268
- /**
269
- * Render codes notifications about package appointments
270
- *
271
- * @param string $notification_type
272
- */
273
- private function renderPackageCodes( $notification_type )
274
- {
275
- $codes = $this->getCommonCodes();
276
- $codes[] = array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) );
277
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
278
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
279
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
280
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
281
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
282
- $codes[] = array( 'code' => 'service_duration', 'description' => __( 'duration of service', 'bookly' ) );
283
- $codes[] = array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) );
284
- $codes[] = array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) );
285
- $codes[] = array( 'code' => 'staff_email', 'description' => __( 'email of staff', 'bookly' ) );
286
- $codes[] = array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) );
287
- $codes[] = array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) );
288
- $codes[] = array( 'code' => 'staff_phone', 'description' => __( 'phone of staff', 'bookly' ) );
289
- $codes[] = array( 'code' => 'staff_photo', 'description' => __( 'photo of staff', 'bookly' ) );
290
-
291
- $codes = Lib\Proxy\Packages::prepareNotificationCodesList( $codes, '', $notification_type );
292
-
293
- Lib\Utils\Common::codes( $codes );
294
- }
295
-
296
- /**
297
- * Render codes notifications wor appointments in waiting list
298
- */
299
- private function renderWaitingListCodes()
300
- {
301
- $codes = $this->getCommonCodes();
302
- $codes[] = array( 'code' => 'appointment_date', 'description' => __( 'date of appointment', 'bookly' ) );
303
- $codes[] = array( 'code' => 'appointment_end_date', 'description' => __( 'end date of appointment', 'bookly' ) );
304
- $codes[] = array( 'code' => 'appointment_end_time', 'description' => __( 'end time of appointment', 'bookly' ) );
305
- $codes[] = array( 'code' => 'appointment_time', 'description' => __( 'time of appointment', 'bookly' ) );
306
- $codes[] = array( 'code' => 'appointment_waiting_list', 'description' => __( 'waiting list of appointment', 'bookly-waiting-list' ) );
307
- $codes[] = array( 'code' => 'booking_number', 'description' => __( 'booking number', 'bookly' ) );
308
- $codes[] = array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) );
309
- $codes[] = array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) );
310
- $codes[] = array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) );
311
- $codes[] = array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ) );
312
- $codes[] = array( 'code' => 'service_duration', 'description' => __( 'duration of service', 'bookly' ) );
313
- $codes[] = array( 'code' => 'staff_email', 'description' => __( 'email of staff', 'bookly' ) );
314
- $codes[] = array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) );
315
- $codes[] = array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) );
316
- $codes[] = array( 'code' => 'staff_phone', 'description' => __( 'phone of staff', 'bookly' ) );
317
- $codes[] = array( 'code' => 'staff_photo', 'description' => __( 'photo of staff', 'bookly' ) );
318
-
319
- $codes = Lib\Proxy\WaitingList::prepareNotificationCodesList( $codes );
320
-
321
- Lib\Utils\Common::codes( Lib\Proxy\Shared::prepareNotificationCodesList( $codes, 'appointment' ) );
322
- }
323
-
324
- private function renderRecurringCodes()
325
- {
326
- $codes = $this->getCommonCodes();
327
- $codes[] = array( 'code' => 'appointment_date', 'description' => __( 'date of appointment', 'bookly' ) );
328
- $codes[] = array( 'code' => 'appointment_end_date','description' => __( 'end date of appointment', 'bookly' ) );
329
- $codes[] = array( 'code' => 'appointment_end_time','description' => __( 'end time of appointment', 'bookly' ) );
330
- $codes[] = array( 'code' => 'appointment_time', 'description' => __( 'time of appointment', 'bookly' ) );
331
- $codes[] = array( 'code' => 'booking_number', 'description' => __( 'booking number', 'bookly' ) );
332
- $codes[] = array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) );
333
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
334
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
335
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
336
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
337
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
338
- $codes[] = array( 'code' => 'google_calendar_url', 'description' => esc_html__( 'URL for adding event to client\'s Google Calendar (to use inside <a> tag)', 'bookly' ) );
339
- $codes[] = array( 'code' => 'number_of_persons', 'description' => __( 'number of persons', 'bookly' ) );
340
- $codes[] = array( 'code' => 'payment_type', 'description' => __( 'payment type', 'bookly' ) );
341
- $codes[] = array( 'code' => 'service_duration', 'description' => __( 'duration of service', 'bookly' ) );
342
- $codes[] = array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) );
343
- $codes[] = array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) );
344
- $codes[] = array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ) );
345
- $codes[] = array( 'code' => 'staff_email', 'description' => __( 'email of staff', 'bookly' ) );
346
- $codes[] = array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) );
347
- $codes[] = array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) );
348
- $codes[] = array( 'code' => 'staff_phone', 'description' => __( 'phone of staff', 'bookly' ) );
349
- $codes[] = array( 'code' => 'staff_photo', 'description' => __( 'photo of staff', 'bookly' ) );
350
- $codes[] = array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) );
351
-
352
- $codes = Lib\Proxy\RecurringAppointments::prepareNotificationCodesList( $codes );
353
-
354
- Lib\Utils\Common::codes( Lib\Proxy\Shared::prepareNotificationCodesList( $codes, 'customer' ) );
355
- }
356
-
357
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/notifications/Controller.php DELETED
@@ -1,133 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Notifications;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Notifications
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- const page_slug = 'bookly-notifications';
13
-
14
- public function index()
15
- {
16
- $this->enqueueStyles( array(
17
- 'frontend' => array( 'css/ladda.min.css' ),
18
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
19
- ) );
20
-
21
- $this->enqueueScripts( array(
22
- 'backend' => array(
23
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
24
- 'js/angular.min.js',
25
- 'js/help.js' => array( 'jquery' ),
26
- 'js/alert.js' => array( 'jquery' ),
27
- ),
28
- 'module' => array(
29
- 'js/notification.js' => array( 'jquery' ),
30
- 'js/ng-app.js' => array( 'jquery', 'bookly-angular.min.js' ),
31
- ),
32
- 'frontend' => array(
33
- 'js/spin.min.js' => array( 'jquery' ),
34
- 'js/ladda.min.js' => array( 'jquery' ),
35
- )
36
- ) );
37
- $cron_reminder = (array) get_option( 'bookly_cron_reminder_times' );
38
- $form = new Forms\Notifications( 'email', Components::getInstance() );
39
- $alert = array( 'success' => array() );
40
- // Save action.
41
- if ( ! empty ( $_POST ) ) {
42
- if ( $this->csrfTokenValid() ) {
43
- $form->bind( $this->getPostParameters() );
44
- $form->save();
45
- $alert['success'][] = __( 'Settings saved.', 'bookly' );
46
- update_option( 'bookly_email_send_as', $this->getParameter( 'bookly_email_send_as' ) );
47
- update_option( 'bookly_email_reply_to_customers', $this->getParameter( 'bookly_email_reply_to_customers' ) );
48
- update_option( 'bookly_email_sender', $this->getParameter( 'bookly_email_sender' ) );
49
- update_option( 'bookly_email_sender_name', $this->getParameter( 'bookly_email_sender_name' ) );
50
- update_option( 'bookly_ntf_processing_interval', (int) $this->getParameter( 'bookly_ntf_processing_interval' ) );
51
- foreach ( array( 'staff_agenda', 'client_follow_up', 'client_reminder', 'client_birthday_greeting' ) as $type ) {
52
- $cron_reminder[ $type ] = $this->getParameter( $type . '_cron_hour' );
53
- }
54
- foreach ( array( 'client_reminder_1st', 'client_reminder_2nd', 'client_reminder_3rd', ) as $type ) {
55
- $cron_reminder[ $type ] = $this->getParameter( $type . '_cron_before_hour' );
56
- }
57
- update_option( 'bookly_cron_reminder_times', $cron_reminder );
58
- }
59
- }
60
- $cron_uri = plugins_url( 'lib/utils/send_notifications_cron.php', Lib\Plugin::getMainFile() );
61
- wp_localize_script( 'bookly-alert.js', 'BooklyL10n', array(
62
- 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
63
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
64
- 'alert' => $alert,
65
- 'sent_successfully' => __( 'Sent successfully.', 'bookly' ),
66
- 'limitations' => __( '<b class="h4">This function is not available in the Lite version of Bookly.</b><br><br>To get access to all Bookly features, lifetime free updates and 24/7 support, please upgrade to the Standard version of Bookly.<br>For more information visit', 'bookly' ) . ' <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
67
- ) );
68
- $statuses = Lib\Entities\CustomerAppointment::getStatuses();
69
- foreach ( range( 1, 23 ) as $hours ) {
70
- $bookly_ntf_processing_interval_values[] = array( $hours, Lib\Utils\DateTime::secondsToInterval( $hours * HOUR_IN_SECONDS ) );
71
- }
72
- $this->render( 'index', compact( 'form', 'cron_uri', 'cron_reminder', 'statuses', 'bookly_ntf_processing_interval_values' ) );
73
- }
74
-
75
- public function executeGetEmailNotificationsData()
76
- {
77
- $form = new Forms\Notifications( 'email', Components::getInstance() );
78
-
79
- $bookly_email_sender_name = get_option( 'bookly_email_sender_name' ) == '' ?
80
- get_option( 'blogname' ) : get_option( 'bookly_email_sender_name' );
81
-
82
- $bookly_email_sender = get_option( 'bookly_email_sender' ) == '' ?
83
- get_option( 'admin_email' ) : get_option( 'bookly_email_sender' );
84
-
85
- $notifications = array();
86
- foreach ( $form->getData() as $notification ) {
87
- $name = Lib\Entities\Notification::getName( $notification['type'] );
88
- if ( in_array( $notification['type'], Lib\Entities\Notification::getCustomNotificationTypes() ) && $notification['subject'] != '' ) {
89
- // In window Test Email Notification
90
- // for custom notification, subject is name.
91
- $name = $notification['subject'];
92
- }
93
- $notifications[] = array(
94
- 'type' => $notification['type'],
95
- 'name' => $name,
96
- 'active' => $notification['active'],
97
- );
98
- }
99
-
100
- $result = array(
101
- 'notifications' => $notifications,
102
- 'sender_email' => $bookly_email_sender,
103
- 'sender_name' => $bookly_email_sender_name,
104
- 'send_as' => get_option( 'bookly_email_send_as' ),
105
- 'reply_to_customers' => get_option( 'bookly_email_reply_to_customers' ),
106
- );
107
-
108
- wp_send_json_success( $result );
109
- }
110
-
111
- public function executeTestEmailNotifications(){}
112
-
113
- /**
114
- * Create new custom notification
115
- */
116
- public function executeCreateCustomNotification(){}
117
-
118
- /**
119
- * Delete custom notification
120
- */
121
- public function executeDeleteCustomNotification()
122
- {
123
- $id = $this->getParameter( 'id' );
124
- Lib\Entities\Notification::query()
125
- ->delete()
126
- ->where( 'id', $id )
127
- ->whereIN( 'type', Lib\Entities\Notification::getCustomNotificationTypes() )
128
- ->execute();
129
-
130
- wp_send_json_success();
131
- }
132
-
133
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/notifications/Page.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Notifications;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Page
8
+ * @package Bookly\Backend\Modules\Notifications
9
+ */
10
+ class Page extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render page.
14
+ */
15
+ public static function render()
16
+ {
17
+ self::enqueueStyles( array(
18
+ 'frontend' => array( 'css/ladda.min.css' ),
19
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', ),
20
+ ) );
21
+
22
+ self::enqueueScripts( array(
23
+ 'backend' => array(
24
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
25
+ 'js/angular.min.js',
26
+ 'js/help.js' => array( 'jquery' ),
27
+ 'js/alert.js' => array( 'jquery' ),
28
+ ),
29
+ 'module' => array(
30
+ 'js/notification.js' => array( 'jquery' ),
31
+ 'js/ng-app.js' => array( 'jquery', 'bookly-angular.min.js' ),
32
+ ),
33
+ 'frontend' => array(
34
+ 'js/spin.min.js' => array( 'jquery' ),
35
+ 'js/ladda.min.js' => array( 'jquery' ),
36
+ )
37
+ ) );
38
+ $cron_reminder = (array) get_option( 'bookly_cron_reminder_times' );
39
+ $form = new Forms\Notifications( 'email' );
40
+ $alert = array( 'success' => array() );
41
+ $current_notification_id = null;
42
+ // Save action.
43
+ if ( ! empty ( $_POST ) ) {
44
+ if ( self::csrfTokenValid() ) {
45
+ $form->bind( self::postParameters() );
46
+ $form->save();
47
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
48
+ update_option( 'bookly_email_send_as', self::parameter( 'bookly_email_send_as' ) );
49
+ update_option( 'bookly_email_reply_to_customers', self::parameter( 'bookly_email_reply_to_customers' ) );
50
+ update_option( 'bookly_email_sender', self::parameter( 'bookly_email_sender' ) );
51
+ update_option( 'bookly_email_sender_name', self::parameter( 'bookly_email_sender_name' ) );
52
+ update_option( 'bookly_ntf_processing_interval', (int) self::parameter( 'bookly_ntf_processing_interval' ) );
53
+ foreach ( array( 'staff_agenda', 'client_follow_up', 'client_reminder', 'client_birthday_greeting' ) as $type ) {
54
+ $cron_reminder[ $type ] = self::parameter( $type . '_cron_hour' );
55
+ }
56
+ foreach ( array( 'client_reminder_1st', 'client_reminder_2nd', 'client_reminder_3rd', ) as $type ) {
57
+ $cron_reminder[ $type ] = self::parameter( $type . '_cron_before_hour' );
58
+ }
59
+ update_option( 'bookly_cron_reminder_times', $cron_reminder );
60
+ $current_notification_id = self::parameter( 'new_notification_id' );
61
+ }
62
+ }
63
+ wp_localize_script( 'bookly-alert.js', 'BooklyL10n', array(
64
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
65
+ 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
66
+ 'alert' => $alert,
67
+ 'current_notification_id' => $current_notification_id,
68
+ 'sent_successfully' => __( 'Sent successfully.', 'bookly' ),
69
+ ) );
70
+ foreach ( range( 1, 23 ) as $hours ) {
71
+ $bookly_ntf_processing_interval_values[] = array( $hours, Lib\Utils\DateTime::secondsToInterval( $hours * HOUR_IN_SECONDS ) );
72
+ }
73
+ self::renderTemplate( 'index', compact( 'form', 'cron_reminder', 'bookly_ntf_processing_interval_values' ) );
74
+ }
75
+ }
backend/modules/notifications/forms/Notifications.php CHANGED
@@ -1,11 +1,13 @@
1
  <?php
2
- namespace BooklyLite\Backend\Modules\Notifications\Forms;
3
 
4
- use BooklyLite\Lib;
 
 
5
 
6
  /**
7
  * Class Notifications
8
- * @package BooklyLite\Backend\Modules\Notifications\Forms
9
  */
10
  class Notifications extends Lib\Base\Form
11
  {
@@ -37,36 +39,35 @@ class Notifications extends Lib\Base\Form
37
 
38
  public $gateway;
39
 
40
- /** @var \BooklyLite\Backend\Modules\Notifications\Components|\BooklyLite\Backend\Modules\Sms\Components|Lib\Base\Components */
41
  protected $codes;
42
 
43
  /**
44
  * Notifications constructor.
45
  *
46
- * @param string $gateway
47
- * @param \BooklyLite\Backend\Modules\Notifications\Components|\BooklyLite\Backend\Modules\Sms\Components $components
48
  */
49
- public function __construct( $gateway = 'email', Lib\Base\Components $components )
50
  {
51
  /*
52
  * make Visual Mode as default (instead of Text Mode)
53
  * allowed: tinymce - Visual Mode, html - Text Mode, test - no one Mode selected
54
  */
55
- add_filter( 'wp_default_editor', create_function( '', 'return \'tinymce\';' ) );
56
- $this->types = Lib\Proxy\Shared::prepareNotificationTypes( $this->types );
57
  $this->gateway = $gateway;
58
  if ( ! Lib\Config::combinedNotificationsEnabled() ) {
59
  $this->types['combined'] = array();
60
  }
61
  $this->types['custom'] = Lib\Entities\Notification::getCustomNotificationTypes();
62
- $this->codes = $components;
63
- $this->setFields( array( 'id', 'active', 'type', 'subject', 'message', 'to_customer', 'to_staff', 'to_admin', 'attach_ics', 'settings' ) );
64
  $this->load();
65
  }
66
 
67
- public function bind( array $_post = array(), array $files = array() )
68
  {
69
- $this->data = $_post['notification'];
70
  }
71
 
72
  /**
@@ -105,7 +106,7 @@ class Notifications extends Lib\Base\Form
105
  $notifications = array();
106
  foreach ( $this->types[ $group ] as $type ) {
107
  foreach ( $this->data as $notification ) {
108
- if ( $notification['type'] == $type ) {
109
  $notifications[] = $notification;
110
  }
111
  }
@@ -156,21 +157,14 @@ class Notifications extends Lib\Base\Form
156
  esc_textarea( $value )
157
  );
158
  } else {
159
- $settings = array(
160
- 'textarea_name' => $name,
161
- 'media_buttons' => false,
162
- 'editor_height' => 384,
163
- 'tinymce' => array(
164
- 'theme_advanced_buttons1' => 'formatselect,|,bold,italic,underline,|,'.
165
- 'bullist,blockquote,|,justifyleft,justifycenter'.
166
- ',justifyright,justifyfull,|,link,unlink,|'.
167
- ',spellchecker,wp_fullscreen,wp_adv'
168
- )
169
  );
170
-
171
- echo '<div class="form-group"><label>' . __( 'Message', 'bookly' ) . '</label>';
172
- wp_editor( $value, $attr_id, $settings );
173
- echo '</div>';
174
  }
175
  }
176
 
@@ -222,6 +216,17 @@ class Notifications extends Lib\Base\Form
222
  'client_reminder_2nd',
223
  'client_reminder_3rd',
224
  'client_follow_up',
 
 
 
 
 
 
 
 
 
 
 
225
  ) ) ) {
226
  $id = $notification['id'];
227
  $name = sprintf( 'notification[%d][attach_ics]', $id );
@@ -238,6 +243,18 @@ class Notifications extends Lib\Base\Form
238
  }
239
  }
240
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  /**
242
  * Render sending time.
243
  *
@@ -260,7 +277,7 @@ class Notifications extends Lib\Base\Form
260
  '<option value="%s" %s>%s</option>',
261
  $hour,
262
  selected( $cron_reminder[ $type ], $hour, false ),
263
- sprintf( __( '%s before', 'bookly' ), \BooklyLite\Lib\Utils\DateTime::secondsToInterval( $hour * HOUR_IN_SECONDS ) )
264
  );
265
  }, array_merge( range( 1, 24 ), range( 48, 336, 24 ) ) ) );
266
  } else {
@@ -292,7 +309,7 @@ class Notifications extends Lib\Base\Form
292
  */
293
  public function renderCodes( $notification_type )
294
  {
295
- $this->codes->renderCodes( $notification_type );
296
  }
297
 
298
  }
1
  <?php
2
+ namespace Bookly\Backend\Modules\Notifications\Forms;
3
 
4
+ use Bookly\Lib;
5
+ use Bookly\Backend\Modules\Notifications\Proxy;
6
+ use Bookly\Backend\Modules\Notifications\Lib\Codes;
7
 
8
  /**
9
  * Class Notifications
10
+ * @package Bookly\Backend\Modules\Notifications\Forms
11
  */
12
  class Notifications extends Lib\Base\Form
13
  {
39
 
40
  public $gateway;
41
 
42
+ /** @var Codes */
43
  protected $codes;
44
 
45
  /**
46
  * Notifications constructor.
47
  *
48
+ * @param string $gateway
 
49
  */
50
+ public function __construct( $gateway = 'email' )
51
  {
52
  /*
53
  * make Visual Mode as default (instead of Text Mode)
54
  * allowed: tinymce - Visual Mode, html - Text Mode, test - no one Mode selected
55
  */
56
+ add_filter( 'wp_default_editor', function() { return 'tinymce'; } );
57
+ $this->types = Proxy\Shared::prepareNotificationTypes( $this->types );
58
  $this->gateway = $gateway;
59
  if ( ! Lib\Config::combinedNotificationsEnabled() ) {
60
  $this->types['combined'] = array();
61
  }
62
  $this->types['custom'] = Lib\Entities\Notification::getCustomNotificationTypes();
63
+ $this->codes = new Codes( $gateway );
64
+ $this->setFields( array( 'id', 'active', 'type', 'subject', 'message', 'to_customer', 'to_staff', 'to_admin', 'attach_ics', 'attach_invoice', 'settings' ) );
65
  $this->load();
66
  }
67
 
68
+ public function bind( array $params = array(), array $files = array() )
69
  {
70
+ $this->data = $params['notification'];
71
  }
72
 
73
  /**
106
  $notifications = array();
107
  foreach ( $this->types[ $group ] as $type ) {
108
  foreach ( $this->data as $notification ) {
109
+ if ( $notification['type'] == $type && ( Lib\Config::proActive() || in_array( $notification['type'], Lib\Entities\Notification::$bookly_notifications[ $notification['gateway'] ] ) ) ) {
110
  $notifications[] = $notification;
111
  }
112
  }
157
  esc_textarea( $value )
158
  );
159
  } else {
160
+ printf(
161
+ '<div class="form-group">
162
+ <input type="hidden" name="%1$s" value="%2$s" class="bookly-js-message-input"/>
163
+ <div class="bookly-js-tinymce-message"></div>
164
+ </div>',
165
+ $name,
166
+ esc_attr( $value )
 
 
 
167
  );
 
 
 
 
168
  }
169
  }
170
 
216
  'client_reminder_2nd',
217
  'client_reminder_3rd',
218
  'client_follow_up',
219
+ // Recurring.
220
+ 'client_pending_recurring_appointment',
221
+ 'staff_pending_recurring_appointment',
222
+ 'client_approved_recurring_appointment',
223
+ 'staff_approved_recurring_appointment',
224
+ 'client_cancelled_recurring_appointment',
225
+ 'staff_cancelled_recurring_appointment',
226
+ 'client_rejected_recurring_appointment',
227
+ 'staff_rejected_recurring_appointment',
228
+ 'client_waitlisted_recurring_appointment',
229
+ 'staff_waitlisted_recurring_appointment',
230
  ) ) ) {
231
  $id = $notification['id'];
232
  $name = sprintf( 'notification[%d][attach_ics]', $id );
243
  }
244
  }
245
 
246
+ /**
247
+ * Render attach invoice file.
248
+ *
249
+ * @param array $notification
250
+ */
251
+ public function renderAttachInvoice( array $notification )
252
+ {
253
+ if ( in_array( $notification['type'], array( 'client_pending_appointment', 'client_approved_appointment', 'client_follow_up', ) ) ) {
254
+ Proxy\Invoices::renderAttach( $notification );
255
+ }
256
+ }
257
+
258
  /**
259
  * Render sending time.
260
  *
277
  '<option value="%s" %s>%s</option>',
278
  $hour,
279
  selected( $cron_reminder[ $type ], $hour, false ),
280
+ sprintf( __( '%s before', 'bookly' ), Lib\Utils\DateTime::secondsToInterval( $hour * HOUR_IN_SECONDS ) )
281
  );
282
  }, array_merge( range( 1, 24 ), range( 48, 336, 24 ) ) ) );
283
  } else {
309
  */
310
  public function renderCodes( $notification_type )
311
  {
312
+ $this->codes->render( $notification_type );
313
  }
314
 
315
  }
backend/modules/notifications/lib/Codes.php ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Notifications\Lib;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Lib\Entities\Notification;
6
+ use Bookly\Backend\Modules\Notifications\Proxy;
7
+
8
+ /**
9
+ * Class Codes
10
+ * @package Bookly\Backend\Modules\Notifications\Lib
11
+ */
12
+ class Codes
13
+ {
14
+ /** @var string */
15
+ protected $type;
16
+
17
+ /** @var array */
18
+ protected $codes;
19
+
20
+ /**
21
+ * Constructor.
22
+ *
23
+ * @param string $type
24
+ */
25
+ public function __construct( $type = 'email' )
26
+ {
27
+ $this->type = $type;
28
+ $this->codes = array(
29
+ 'appointment' => array(
30
+ 'appointment_date' => __( 'date of appointment', 'bookly' ),
31
+ 'appointment_end_date' => __( 'end date of appointment', 'bookly' ),
32
+ 'appointment_end_time' => __( 'end time of appointment', 'bookly' ),
33
+ 'appointment_notes' => __( 'customer notes for appointment', 'bookly' ),
34
+ 'appointment_time' => __( 'time of appointment', 'bookly' ),
35
+ 'booking_number' => __( 'booking number', 'bookly' ),
36
+ ),
37
+ 'cart' => array(
38
+ 'cart_info' => __( 'cart information', 'bookly' ),
39
+ 'cart_info_c' => __( 'cart information with cancel', 'bookly' ),
40
+ ),
41
+ 'category' => array(
42
+ 'category_name' => __( 'name of category', 'bookly' ),
43
+ ),
44
+ 'company' => array(
45
+ 'company_address' => __( 'address of company', 'bookly' ),
46
+ 'company_name' => __( 'name of company', 'bookly' ),
47
+ 'company_phone' => __( 'company phone', 'bookly' ),
48
+ 'company_website' => __( 'company web-site address', 'bookly' ),
49
+ ),
50
+ 'customer' => array(
51
+ 'client_address' => __( 'address of client', 'bookly' ),
52
+ 'client_email' => __( 'email of client', 'bookly' ),
53
+ 'client_first_name' => __( 'first name of client', 'bookly' ),
54
+ 'client_last_name' => __( 'last name of client', 'bookly' ),
55
+ 'client_name' => __( 'full name of client', 'bookly' ),
56
+ 'client_phone' => __( 'phone of client', 'bookly' ),
57
+ ),
58
+ 'customer_timezone' => array(
59
+ 'client_timezone' => __( 'time zone of client', 'bookly' ),
60
+ ),
61
+ 'customer_appointment' => array(
62
+ 'approve_appointment_url' => __( 'URL of approve appointment link (to use inside <a> tag)', 'bookly' ),
63
+ 'cancel_appointment_confirm_url' => __( 'URL of cancel appointment link with confirmation (to use inside <a> tag)', 'bookly' ),
64
+ 'cancel_appointment_url' => __( 'URL of cancel appointment link (to use inside <a> tag)', 'bookly' ),
65
+ 'cancellation_reason' => __( 'reason you mentioned while deleting appointment', 'bookly' ),
66
+ 'google_calendar_url' => __( 'URL for adding event to client\'s Google Calendar (to use inside <a> tag)', 'bookly' ),
67
+ 'number_of_persons' => __( 'number of persons', 'bookly' ),
68
+ 'reject_appointment_url' => __( 'URL of reject appointment link (to use inside <a> tag)', 'bookly' ),
69
+ ),
70
+ 'payment' => array(
71
+ 'payment_type' => __( 'payment type', 'bookly' ),
72
+ 'total_price' => __( 'total price of booking (sum of all cart items after applying coupon)' ),
73
+ ),
74
+ 'service' => array(
75
+ 'service_duration' => __( 'duration of service', 'bookly' ),
76
+ 'service_info' => __( 'info of service', 'bookly' ),
77
+ 'service_name' => __( 'name of service', 'bookly' ),
78
+ 'service_price' => __( 'price of service', 'bookly' ),
79
+ ),
80
+ 'staff' => array(
81
+ 'staff_email' => __( 'email of staff', 'bookly' ),
82
+ 'staff_info' => __( 'info of staff', 'bookly' ),
83
+ 'staff_name' => __( 'name of staff', 'bookly' ),
84
+ 'staff_phone' => __( 'phone of staff', 'bookly' ),
85
+ ),
86
+ 'staff_agenda' => array(
87
+ 'agenda_date' => __( 'agenda date', 'bookly' ),
88
+ 'next_day_agenda' => __( 'staff agenda for next day', 'bookly' ),
89
+ 'tomorrow_date' => __( 'date of next day', 'bookly' ),
90
+ ),
91
+ 'user_credentials' => array(
92
+ 'new_password' => __( 'customer new password', 'bookly' ),
93
+ 'new_username' => __( 'customer new username', 'bookly' ),
94
+ 'site_address' => __( 'site address', 'bookly' ),
95
+ ),
96
+ 'rating' => array(),
97
+ );
98
+
99
+ if ( $type == 'email' ) {
100
+ // Only email.
101
+ $this->codes['company']['company_logo'] = __( 'company logo', 'bookly' );
102
+ $this->codes['customer_appointment']['cancel_appointment'] = __( 'cancel appointment link', 'bookly' );
103
+ $this->codes['staff']['staff_photo'] = __( 'photo of staff', 'bookly' );
104
+ }
105
+
106
+ // Add codes from add-ons.
107
+ $this->codes = Proxy\Shared::prepareNotificationCodes( $this->codes, $type );
108
+ }
109
+
110
+ /**
111
+ * Render codes for given notification type.
112
+ *
113
+ * @param $notification_type
114
+ */
115
+ public function render( $notification_type )
116
+ {
117
+ $codes = $this->_build( $notification_type );
118
+
119
+ ksort( $codes );
120
+
121
+ $tbody = '';
122
+ foreach ( $codes as $code => $description ) {
123
+ $tbody .= sprintf(
124
+ '<tr><td><input value="{%s}" readonly="readonly" onclick="this.select()" /> - %s</td></tr>',
125
+ $code,
126
+ esc_html( $description )
127
+ );
128
+ }
129
+
130
+ printf(
131
+ '<table class="bookly-codes bookly-js-codes-%s"><tbody>%s</tbody></table>',
132
+ $notification_type,
133
+ $tbody
134
+ );
135
+ }
136
+
137
+ /**
138
+ * Build array of codes for given notification type.
139
+ *
140
+ * @param $notification_type
141
+ * @return array
142
+ */
143
+ private function _build( $notification_type )
144
+ {
145
+ $codes = array();
146
+
147
+ switch ( $notification_type ) {
148
+ case Notification::TYPE_APPOINTMENT_START_TIME:
149
+ case Notification::TYPE_CUSTOMER_APPOINTMENT_CREATED:
150
+ case Notification::TYPE_LAST_CUSTOMER_APPOINTMENT:
151
+ case Notification::TYPE_CUSTOMER_APPOINTMENT_STATUS_CHANGED:
152
+ $codes = array_merge(
153
+ $this->codes['appointment'],
154
+ $this->codes['category'],
155
+ $this->codes['service'],
156
+ $this->codes['customer_appointment'],
157
+ $this->codes['customer'],
158
+ $this->codes['customer_timezone'],
159
+ $this->codes['staff'],
160
+ $this->codes['payment'],
161
+ $this->codes['company']
162
+ );
163
+ if ( Lib\Config::invoicesActive() &&
164
+ in_array( $notification_type, array(
165
+ Notification::TYPE_CUSTOMER_APPOINTMENT_CREATED,
166
+ Notification::TYPE_CUSTOMER_APPOINTMENT_STATUS_CHANGED,
167
+ ) )
168
+ ) {
169
+ $codes = array_merge( $codes, $this->codes['invoice'] );
170
+ }
171
+ break;
172
+ case 'staff_agenda':
173
+ case Notification::TYPE_STAFF_DAY_AGENDA:
174
+ $codes = array_merge(
175
+ $this->codes['staff'],
176
+ $this->codes['staff_agenda'],
177
+ $this->codes['company']
178
+ );
179
+ break;
180
+ case 'client_birthday_greeting':
181
+ case Notification::TYPE_CUSTOMER_BIRTHDAY:
182
+ $codes = array_merge(
183
+ $this->codes['customer'],
184
+ $this->codes['company']
185
+ );
186
+ break;
187
+ case 'client_new_wp_user':
188
+ $codes = array_merge(
189
+ $this->codes['customer'],
190
+ $this->codes['user_credentials'],
191
+ $this->codes['company']
192
+ );
193
+ break;
194
+ case 'client_pending_appointment_cart':
195
+ case 'client_approved_appointment_cart':
196
+ $codes = array_merge(
197
+ $this->codes['cart'],
198
+ $this->codes['customer'],
199
+ $this->codes['customer_timezone'],
200
+ $this->codes['payment'],
201
+ $this->codes['company']
202
+ );
203
+ break;
204
+ case 'client_pending_appointment':
205
+ case 'staff_pending_appointment':
206
+ case 'client_approved_appointment':
207
+ case 'staff_approved_appointment':
208
+ case 'client_cancelled_appointment':
209
+ case 'staff_cancelled_appointment':
210
+ case 'client_rejected_appointment':
211
+ case 'staff_rejected_appointment':
212
+ case 'client_waitlisted_appointment':
213
+ case 'staff_waitlisted_appointment':
214
+ case 'client_reminder':
215
+ case 'client_reminder_1st':
216
+ case 'client_reminder_2nd':
217
+ case 'client_reminder_3rd':
218
+ $codes = array_merge(
219
+ $this->codes['appointment'],
220
+ $this->codes['category'],
221
+ $this->codes['service'],
222
+ $this->codes['customer_appointment'],
223
+ $this->codes['staff'],
224
+ $this->codes['customer'],
225
+ $this->codes['customer_timezone'],
226
+ $this->codes['payment'],
227
+ $this->codes['company']
228
+ );
229
+ if ( Lib\Config::invoicesActive() &&
230
+ in_array( $notification_type, array(
231
+ 'client_pending_appointment',
232
+ 'client_approved_appointment'
233
+ ) )
234
+ ) {
235
+ $codes = array_merge( $codes, $this->codes['invoice'] );
236
+ }
237
+ break;
238
+ case 'client_follow_up':
239
+ $codes = array_merge(
240
+ $this->codes['appointment'],
241
+ $this->codes['category'],
242
+ $this->codes['service'],
243
+ $this->codes['customer_appointment'],
244
+ $this->codes['staff'],
245
+ $this->codes['customer'],
246
+ $this->codes['customer_timezone'],
247
+ $this->codes['payment'],
248
+ $this->codes['company'],
249
+ $this->codes['rating'],
250
+ isset( $this->codes['invoice'] ) ? $this->codes['invoice'] : array()
251
+ );
252
+ break;
253
+ default:
254
+ $codes = Proxy\Shared::buildNotificationCodesList( $codes, $notification_type, $this->codes );
255
+ }
256
+
257
+ return $codes;
258
+ }
259
+
260
+ /**
261
+ * @param array $groups
262
+ * @param bool $echo
263
+ * @return string
264
+ */
265
+ public function renderGroups( array $groups, $echo = true )
266
+ {
267
+ $codes = array();
268
+ foreach ( $groups as $group ) {
269
+ if ( array_key_exists( $group, $this->codes ) ) {
270
+ $codes = array_merge( $codes, $this->codes[ $group ] );
271
+ }
272
+ }
273
+
274
+ ksort( $codes );
275
+
276
+ $tbody = '';
277
+ foreach ( $codes as $code => $description ) {
278
+ $tbody .= sprintf(
279
+ '<tr><td><input value="{%s}" readonly="readonly" onclick="this.select()" /> - %s</td></tr>',
280
+ $code,
281
+ esc_html( $description )
282
+ );
283
+ }
284
+
285
+ $result = sprintf(
286
+ '<table class="bookly-codes"><tbody>%s</tbody></table>',
287
+ $tbody
288
+ );
289
+
290
+ if ( $echo ) {
291
+ echo $result;
292
+ }
293
+
294
+ return $result;
295
+ }
296
+ }
backend/modules/notifications/proxy/Invoices.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Notifications\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Shared
8
+ * @package Bookly\Backend\Modules\Notifications\Proxy
9
+ *
10
+ * @method static void renderAttach( array $notification ) Render checkbox for attaching invoice to notifications.
11
+ */
12
+ abstract class Invoices extends Lib\Base\Proxy
13
+ {
14
+ }
backend/modules/notifications/proxy/Pro.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Notifications\Proxy;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Backend\Modules\Notifications\Forms;
6
+
7
+ /**
8
+ * Class Pro
9
+ * @package Bookly\Backend\Modules\Notifications\Proxy
10
+ *
11
+ * @method static void renderCustomEmailNotifications( Forms\Notifications $form ) Render custom email notifications.
12
+ */
13
+ abstract class Pro extends Lib\Base\Proxy
14
+ {
15
+ }
backend/modules/notifications/proxy/Shared.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Notifications\Proxy;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Backend\Modules\Notifications\Forms;
6
+
7
+ /**
8
+ * Class Shared
9
+ * @package Bookly\Backend\Modules\Notifications\Proxy
10
+ *
11
+ * @method static array buildNotificationCodesList( array $codes, string $notification_type, array $codes_data ) Build array of codes to be displayed in notification template.
12
+ * @method static array prepareNotificationCodes( array $codes, string $type ) Alter codes for displaying in notification templates.
13
+ * @method static array prepareNotificationTypes( array $types ) Prepare notification types.
14
+ * @method static void renderEmailNotifications( Forms\Notifications $form ) Render email notification(s).
15
+ */
16
+ abstract class Shared extends Lib\Base\Proxy
17
+ {
18
+
19
+ }
backend/modules/notifications/resources/js/ng-app.js CHANGED
@@ -17,7 +17,7 @@
17
  dataType : 'json',
18
  success : function(response) {
19
  if (response.success) {
20
- ds.sender_name = response.data.sender_name;
21
  ds.sender_email = response.data.sender_email;
22
  ds.reply_to_customers = response.data.reply_to_customers;
23
  ds.send_as = response.data.send_as;
@@ -97,7 +97,7 @@
97
  };
98
  angular.forEach($scope.dataSource.notifications, function(notification) {
99
  if (notification.active == '1') {
100
- data.notifications.push(notification.type);
101
  }
102
  });
103
  jQuery.ajax({
17
  dataType : 'json',
18
  success : function(response) {
19
  if (response.success) {
20
+ ds.sender_name = response.data.sender_name;
21
  ds.sender_email = response.data.sender_email;
22
  ds.reply_to_customers = response.data.reply_to_customers;
23
  ds.send_as = response.data.send_as;
97
  };
98
  angular.forEach($scope.dataSource.notifications, function(notification) {
99
  if (notification.active == '1') {
100
+ data.notifications.push(notification.id);
101
  }
102
  });
103
  jQuery.ajax({
backend/modules/notifications/resources/js/notification.js CHANGED
@@ -1,5 +1,9 @@
1
  jQuery(function($) {
2
 
 
 
 
 
3
  Ladda.bind( 'button[type=submit]' );
4
 
5
  // menu fix for WP 3.8.1
@@ -12,6 +16,43 @@ jQuery(function($) {
12
  $(this).parents('.panel-heading').next().collapse(this.checked ? 'show' : 'hide');
13
  });
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  $('[data-toggle="popover"]').popover({
16
  html: true,
17
  placement: 'top',
@@ -19,22 +60,110 @@ jQuery(function($) {
19
  template: '<div class="popover bookly-font-xs" style="width: 220px" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
20
  });
21
 
22
- $("#bookly-js-new-notification").on('click', function () {
23
- booklyAlert({error: [BooklyL10n.limitations]});
24
- $(this).prop('disabled', true);
25
- });
 
 
 
 
 
 
26
 
27
- booklyAlert(BooklyL10n.alert);
 
 
28
 
29
- $(':checkbox').on('change', function () {
30
- if ($(this).prop('checked')) {
31
- booklyAlert({error: [BooklyL10n.limitations]});
32
- $(this).prop('checked', false).prop('readonly', true);
33
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  });
35
 
36
- $('.bookly-test-email-notifications').on('click',function () {
37
- booklyAlert({error: [BooklyL10n.limitations]});
38
- $(this).prop('disabled', true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  });
 
 
40
  });
1
  jQuery(function($) {
2
 
3
+ var $custom_notifications = $('#bookly-js-custom-notifications'),
4
+ $tiny_mce_container = $('#bookly-js-tinymce-container'),
5
+ $container = $('.bookly-main');
6
+
7
  Ladda.bind( 'button[type=submit]' );
8
 
9
  // menu fix for WP 3.8.1
16
  $(this).parents('.panel-heading').next().collapse(this.checked ? 'show' : 'hide');
17
  });
18
 
19
+ $container.on('show.bs.collapse', '.panel', function () {
20
+ var $panel = $(this),
21
+ $old_panel = $('#bookly-js-tinymce-container').closest('.panel'),
22
+ message_id = $panel.find('.panel-collapse').attr('id');
23
+
24
+ $container.find('.panel .collapse.in').collapse('hide');
25
+ if ($old_panel.hasClass('bookly-js-collapse')) {
26
+ $old_panel.find('.bookly-js-message-input').val(tinymce.get('bookly-js-tinymce-area').getContent());
27
+ $old_panel.find('#bookly-js-tinymce-area-tmce').click();
28
+ }
29
+ tinymce.remove("#bookly-js-tinymce-area");
30
+ $tiny_mce_container.detach().appendTo('#' + message_id + ' .bookly-js-tinymce-message');
31
+ tinymce.init(tinyMCEPreInit.mceInit['bookly-js-tinymce-area']);
32
+ tinymce.get('bookly-js-tinymce-area').setContent($panel.find('.bookly-js-message-input').val());
33
+ });
34
+
35
+ $container.on('shown.bs.collapse', '.panel', function () {
36
+ var $panel = $(this),
37
+ $subject = $panel.find('input[id$="subject"]');
38
+
39
+ $subject.focus();
40
+ $('html, body').animate({
41
+ scrollTop: Math.max($panel.offset().top - 40, 0)
42
+ }, 1000);
43
+ });
44
+
45
+ $('#bookly-save').on('click', function (e) {
46
+ e.preventDefault();
47
+ var $panel = $('#bookly-js-tinymce-container').closest('.panel');
48
+
49
+ if ($panel.hasClass('bookly-js-collapse')) {
50
+ $panel.find('.bookly-js-message-input').val(tinymce.get('bookly-js-tinymce-area').getContent());
51
+ }
52
+
53
+ $('#bookly-save').closest('form').submit();
54
+ });
55
+
56
  $('[data-toggle="popover"]').popover({
57
  html: true,
58
  placement: 'top',
60
  template: '<div class="popover bookly-font-xs" style="width: 220px" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
61
  });
62
 
63
+ $custom_notifications
64
+ .on('change', "select[name$='[type]']", function () {
65
+ var $panel = $(this).closest('.panel'),
66
+ $settings = $panel.find('.bookly-js-settings'),
67
+ $attach = $panel.find('.bookly-js-attach'),
68
+ value = $(this).find(':selected').val(),
69
+ set = $(this).find(':selected').data('set'),
70
+ to = $(this).find(':selected').data('to'),
71
+ showAttach = $(this).find(':selected').data('attach-show')||[]
72
+ ;
73
 
74
+ $panel.find('table.bookly-codes').each(function () {
75
+ $(this).toggle($(this).hasClass('bookly-js-codes-' + value));
76
+ });
77
 
78
+ $.each(['customer', 'staff', 'admin'], function (index, value) {
79
+ $panel.find("[name$='[to_" + value + "]']").closest('.checkbox-inline').toggle(to.indexOf(value) != -1);
80
+ });
81
+
82
+ $attach.hide();
83
+ $.each(showAttach, function (index, value) {
84
+ $('.bookly-js-' + value, $panel).show();
85
+ });
86
+
87
+ $settings.each(function () {
88
+ $(this).toggle($(this).hasClass('bookly-js-' + set));
89
+ });
90
+
91
+ switch (set) {
92
+ case 'after_event':
93
+ var $set = $panel.find('.bookly-js-' + set);
94
+ $set.find('.bookly-js-to').toggle(value == 'ca_status_changed');
95
+ $set.find('.bookly-js-with').toggle(value != 'ca_status_changed');
96
+ break;
97
+ }
98
+ })
99
+ .on('click', '.bookly-js-delete', function () {
100
+ if (confirm(BooklyL10n.are_you_sure)) {
101
+ var $button = $(this),
102
+ id = $button.data('notification_id'),
103
+ ladda = Ladda.create(this);
104
+ ladda.start();
105
+ $.ajax({
106
+ url: ajaxurl,
107
+ type: 'POST',
108
+ data: {
109
+ action: 'bookly_delete_custom_notification',
110
+ id: id,
111
+ csrf_token: BooklyL10n.csrf_token
112
+ },
113
+ dataType: 'json',
114
+ success: function (response) {
115
+ if (response.success) {
116
+ var $panel = $button.closest('.panel');
117
+ if ($panel.find('#bookly-js-tinymce-container').length != 0) {
118
+ $panel.find('#bookly-js-tinymce-area-tmce').click();
119
+ tinymce.remove("#bookly-js-tinymce-area");
120
+ $tiny_mce_container.detach().appendTo('#bookly-js-tinymce-wrap');
121
+ tinymce.init(tinyMCEPreInit.mceInit['bookly-js-tinymce-area']);
122
+ tinymce.get('bookly-js-tinymce-area').setContent($panel.find('.bookly-js-message-input').val());
123
+ }
124
+ $panel.remove();
125
+ ladda.stop();
126
+ }
127
+ }
128
+ });
129
+ }
130
+ })
131
+ .find("select[name$='[type]']").trigger('change');
132
+
133
+ $('button[type=reset]').on('click', function () {
134
+ setTimeout(function () {
135
+ $("select[name$='[type]']", $custom_notifications).trigger('change');
136
+ }, 0);
137
  });
138
 
139
+ $("#bookly-js-new-notification").on('click', function () {
140
+ var ladda = Ladda.create(this);
141
+ ladda.start();
142
+ $.ajax({
143
+ url : ajaxurl,
144
+ type: 'POST',
145
+ data: {
146
+ action : 'bookly_pro_create_custom_notification',
147
+ render : true,
148
+ csrf_token: BooklyL10n.csrf_token
149
+ },
150
+ dataType: 'json',
151
+ success: function (response) {
152
+ if (response.success) {
153
+ $custom_notifications.append(response.data.html);
154
+ var $subject = $custom_notifications.find('[name="notification[' + response.data.id + '][subject]"]'),
155
+ $panel = $subject.closest('.panel-collapse');
156
+
157
+ $panel.collapse('show');
158
+ $panel.find("select[name$='[type]']").trigger('change');
159
+ $subject.focus();
160
+ }
161
+ },
162
+ complete: function () {
163
+ ladda.stop();
164
+ }
165
+ });
166
  });
167
+
168
+ booklyAlert(BooklyL10n.alert);
169
  });
backend/modules/notifications/templates/_custom_notification.php DELETED
@@ -1 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
backend/modules/notifications/templates/_test_email_notifications_modal.php CHANGED
@@ -1,4 +1,7 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
 
2
  <div ng-controller=testEmailNotificationsDialogCtrl>
3
  <div id=bookly-test-email-notifications-dialog class="modal fade" tabindex=-1 role="dialog">
4
  <div class="modal-dialog">
@@ -83,14 +86,14 @@
83
  </div>
84
  </div>
85
  <div class="modal-footer">
86
- <?php \BooklyLite\Lib\Utils\Common::csrf() ?>
87
- <?php \BooklyLite\Lib\Utils\Common::submitButton( '', '', __( 'Send', 'bookly' ) ) ?>
88
  </div>
89
  </form>
90
  </div>
91
- </div><!-- /.modal-content -->
92
- </div><!-- /.modal-dialog -->
93
- </div><!-- /.modal -->
94
 
95
  <div class="modal fade" id="bookly-modal" tabindex="-1" role="dialog">
96
  <div class="modal-dialog" role="document">
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
  <div ng-controller=testEmailNotificationsDialogCtrl>
6
  <div id=bookly-test-email-notifications-dialog class="modal fade" tabindex=-1 role="dialog">
7
  <div class="modal-dialog">
86
  </div>
87
  </div>
88
  <div class="modal-footer">
89
+ <?php Inputs::renderCsrf() ?>
90
+ <?php Buttons::renderSubmit( null, null, __( 'Send', 'bookly' ) ) ?>
91
  </div>
92
  </form>
93
  </div>
94
+ </div>
95
+ </div>
96
+ </div>
97
 
98
  <div class="modal fade" id="bookly-modal" tabindex="-1" role="dialog">
99
  <div class="modal-dialog" role="document">
backend/modules/notifications/templates/index.php CHANGED
@@ -1,15 +1,19 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Entities\Notification;
3
- use BooklyLite\Lib\Utils\Common;
4
- use BooklyLite\Lib\Proxy;
5
- use BooklyLite\Backend\Modules\Support;
 
 
 
 
6
 
7
  $bookly_email_sender_name = get_option( 'bookly_email_sender_name' ) == '' ?
8
  get_option( 'blogname' ) : get_option( 'bookly_email_sender_name' );
9
  $bookly_email_sender = get_option( 'bookly_email_sender' ) == '' ?
10
  get_option( 'admin_email' ) : get_option( 'bookly_email_sender' );
11
 
12
- /** @var BooklyLite\Backend\Modules\Notifications\Forms\Notifications $form */
13
  ?>
14
  <div id="bookly-tbs" class="wrap">
15
  <div class="bookly-tbs-body" ng-app="notifications">
@@ -17,9 +21,9 @@ $bookly_email_sender = get_option( 'bookly_email_sender' ) == '' ?
17
  <div class="bookly-page-title">
18
  <?php _e( 'Email Notifications', 'bookly' ) ?>
19
  </div>
20
- <?php Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
21
  </div>
22
- <form method="post" action="<?php echo Common::escAdminUrl( $this::page_slug ) ?>">
23
  <div class="panel panel-default bookly-main" ng-controller="emailNotifications">
24
  <div class="panel-body">
25
  <div class="row">
@@ -36,12 +40,12 @@ $bookly_email_sender = get_option( 'bookly_email_sender' ) == '' ?
36
  </div>
37
  </div>
38
  <div class="col-md-6">
39
- <?php Common::optionToggle( 'bookly_email_send_as', __( 'Send emails as', 'bookly' ), __( 'HTML allows formatting, colors, fonts, positioning, etc. With Text you must use Text mode of rich-text editors below. On some servers only text emails are sent successfully.', 'bookly' ),
40
  array( array( 'html', __( 'HTML', 'bookly' ) ), array( 'text', __( 'Text', 'bookly' ) ) )
41
  ) ?>
42
  </div>
43
  <div class="col-md-6">
44
- <?php Common::optionToggle( 'bookly_email_reply_to_customers', __( 'Reply directly to customers', 'bookly' ), __( 'If this option is enabled then the email address of the customer is used as a sender email address for notifications sent to staff members and administrators.', 'bookly' ) ) ?>
45
  </div>
46
  </div>
47
 
@@ -70,6 +74,7 @@ $bookly_email_sender = get_option( 'bookly_email_sender' ) == '' ?
70
  <?php $form->renderSubject( $id ) ?>
71
  <?php $form->renderEditor( $id ) ?>
72
  <?php $form->renderAttachIcs( $notification ) ?>
 
73
  <?php $form->renderCopy( $notification ) ?>
74
  <div class="form-group">
75
  <label><?php _e( 'Codes', 'bookly' ) ?></label>
@@ -82,7 +87,7 @@ $bookly_email_sender = get_option( 'bookly_email_sender' ) == '' ?
82
  <?php endforeach ?>
83
  </div>
84
 
85
- <?php if ( $form->types['combined'] ) : ?>
86
 
87
  <h4 class="bookly-block-head bookly-color-gray"><?php _e( 'Combined', 'bookly' ) ?></h4>
88
 
@@ -122,49 +127,52 @@ $bookly_email_sender = get_option( 'bookly_email_sender' ) == '' ?
122
 
123
  <?php Proxy\Shared::renderEmailNotifications( $form ) ?>
124
 
125
- <h4 class="bookly-block-head bookly-color-gray"><?php _e( 'Custom', 'bookly' ) ?></h4>
126
-
127
- <div class="panel-group bookly-margin-vertical-xlg" id="bookly-js-custom-notifications">
128
- <?php foreach ( $form->getNotifications( 'custom' ) as $notification ) :
129
- $this->render( '_custom_notification', compact( 'form', 'notification', 'statuses' ) );
130
- endforeach ?>
131
- </div>
132
- <div class="row">
133
- <div class="col-sm-12">
134
- <div class="form-group">
135
- <button id="bookly-js-new-notification" type="button" class="btn btn-xlg btn-block btn-success-outline">
136
- <span class="ladda-label"><i class="dashicons dashicons-plus-alt"></i>
137
- <?php _e( 'New Notification', 'bookly' ) ?>
138
- </span>
139
- </button>
140
- </div>
 
141
  </div>
142
  </div>
143
 
 
144
  <div class="alert alert-info">
145
  <div class="row">
146
  <div class="col-md-10">
147
  <?php if ( is_multisite() ) : ?>
148
- <p><?php printf( __( 'To send scheduled notifications please refer to <a href="%1$s">Bookly Multisite</a> add-on <a href="%2$s">message</a>.', 'bookly' ), 'http://codecanyon.net/item/bookly-multisite-addon/13903524?ref=ladela', network_admin_url( 'admin.php?page=bookly-multisite-network' ) ) ?></p>
149
  <?php else : ?>
150
  <p><?php _e( 'To send scheduled notifications please execute the following command hourly with your cron:', 'bookly' ) ?></p><br/>
151
- <code class="bookly-text-wrap">wget -q -O - <?php echo $cron_uri ?></code>
152
  <?php endif ?>
153
  </div>
154
  <div class="col-md-2">
155
- <?php Common::optionToggle( 'bookly_ntf_processing_interval', __( 'Notification period', 'bookly' ), __( 'Set period of time when system will attempt to deliver notification to user. Notification will be discarded after period expiration.', 'bookly' ), $bookly_ntf_processing_interval_values ) ?>
156
  </div>
157
  </div>
158
  </div>
 
159
  </div>
160
 
161
  <div class="panel-footer">
162
- <?php Common::csrf() ?>
163
- <?php Common::submitButton() ?>
164
- <?php Common::resetButton() ?>
165
 
166
  <div class="pull-left">
167
- <button type="button" class="btn btn-default bookly-test-email-notifications btn-lg">
168
  <?php _e( 'Test Email Notifications', 'bookly' ) ?>
169
  </button>
170
  </div>
@@ -173,24 +181,5 @@ $bookly_email_sender = get_option( 'bookly_email_sender' ) == '' ?
173
  </form>
174
 
175
  <?php include '_test_email_notifications_modal.php' ?>
176
-
177
- <div class="modal fade" tabindex="-1" role="dialog" id="bookly-js-continue-confirm">
178
- <div class="modal-dialog modal-lg" role="document">
179
- <div class="modal-content">
180
- <div class="modal-header">
181
- <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
182
- <div class="modal-title h4"><?php _e( 'Are you sure?', 'bookly' ) ?></div>
183
- </div>
184
- <div class="modal-body">
185
- <p><?php _e( 'When creating a new notification, the page will be reloaded, and all unsaved changes will be lost.', 'bookly' ) ?></p>
186
- </div>
187
- <div class="modal-footer">
188
- <?php Common::customButton( null, 'btn-lg btn-success bookly-js-save', __( 'Save changes', 'bookly' ) ) ?>
189
- <?php Common::customButton( null, 'btn-lg btn-danger bookly-js-continue', __( 'Continue without saving', 'bookly' ) ) ?>
190
- <?php Common::customButton( null, 'btn-lg btn-default', __( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
191
- </div>
192
- </div>
193
- </div>
194
- </div>
195
  </div>
196
  </div>
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
+ use Bookly\Backend\Components\Settings\Selects;
5
+ use Bookly\Backend\Components\Support;
6
+ use Bookly\Backend\Modules\Notifications\Proxy;
7
+ use Bookly\Lib\Entities\Notification;
8
+ use Bookly\Lib\Utils\Common;
9
+ use Bookly\Lib\Config;
10
 
11
  $bookly_email_sender_name = get_option( 'bookly_email_sender_name' ) == '' ?
12
  get_option( 'blogname' ) : get_option( 'bookly_email_sender_name' );
13
  $bookly_email_sender = get_option( 'bookly_email_sender' ) == '' ?
14
  get_option( 'admin_email' ) : get_option( 'bookly_email_sender' );
15
 
16
+ /** @var Bookly\Backend\Modules\Notifications\Forms\Notifications $form */
17
  ?>
18
  <div id="bookly-tbs" class="wrap">
19
  <div class="bookly-tbs-body" ng-app="notifications">
21
  <div class="bookly-page-title">
22
  <?php _e( 'Email Notifications', 'bookly' ) ?>
23
  </div>
24
+ <?php Support\Buttons::render( $self::pageSlug() ) ?>
25
  </div>
26
+ <form method="post" action="<?php echo Common::escAdminUrl( $self::pageSlug() ) ?>">
27
  <div class="panel panel-default bookly-main" ng-controller="emailNotifications">
28
  <div class="panel-body">
29
  <div class="row">
40
  </div>
41
  </div>
42
  <div class="col-md-6">
43
+ <?php Selects::renderSingle( 'bookly_email_send_as', __( 'Send emails as', 'bookly' ), __( 'HTML allows formatting, colors, fonts, positioning, etc. With Text you must use Text mode of rich-text editors below. On some servers only text emails are sent successfully.', 'bookly' ),
44
  array( array( 'html', __( 'HTML', 'bookly' ) ), array( 'text', __( 'Text', 'bookly' ) ) )
45
  ) ?>
46
  </div>
47
  <div class="col-md-6">
48
+ <?php Selects::renderSingle( 'bookly_email_reply_to_customers', __( 'Reply directly to customers', 'bookly' ), __( 'If this option is enabled then the email address of the customer is used as a sender email address for notifications sent to staff members and administrators.', 'bookly' ) ) ?>
49
  </div>
50
  </div>
51
 
74
  <?php $form->renderSubject( $id ) ?>
75
  <?php $form->renderEditor( $id ) ?>
76
  <?php $form->renderAttachIcs( $notification ) ?>
77
+ <?php $form->renderAttachInvoice( $notification ) ?>
78
  <?php $form->renderCopy( $notification ) ?>
79
  <div class="form-group">
80
  <label><?php _e( 'Codes', 'bookly' ) ?></label>
87
  <?php endforeach ?>
88
  </div>
89
 
90
+ <?php if ( Config::proActive() && $form->types['combined'] ) : ?>
91
 
92
  <h4 class="bookly-block-head bookly-color-gray"><?php _e( 'Combined', 'bookly' ) ?></h4>
93
 
127
 
128
  <?php Proxy\Shared::renderEmailNotifications( $form ) ?>
129
 
130
+ <?php Proxy\Pro::renderCustomEmailNotifications( $form ) ?>
131
+
132
+ <div id="bookly-js-tinymce-wrap" class="collapse">
133
+ <div id="bookly-js-tinymce-container">
134
+ <?php
135
+ $settings = array(
136
+ 'media_buttons' => false,
137
+ 'editor_height' => 384,
138
+ 'tinymce' => array(
139
+ 'theme_advanced_buttons1' => 'formatselect,|,bold,italic,underline,|,'.
140
+ 'bullist,blockquote,|,justifyleft,justifycenter'.
141
+ ',justifyright,justifyfull,|,link,unlink,|'.
142
+ ',spellchecker,wp_fullscreen,wp_adv'
143
+ )
144
+ );
145
+ wp_editor( '', 'bookly-js-tinymce-area', $settings );
146
+ ?>
147
  </div>
148
  </div>
149
 
150
+ <?php if ( Config::proActive() ) : ?>
151
  <div class="alert alert-info">
152
  <div class="row">
153
  <div class="col-md-10">
154
  <?php if ( is_multisite() ) : ?>
155
+ <p><?php printf( __( 'To send scheduled notifications please refer to <a href="%1$s">Bookly Multisite</a> add-on <a href="%2$s">message</a>.', 'bookly' ), Common::prepareUrlReferrers( 'http://codecanyon.net/item/bookly-multisite-addon/13903524?ref=ladela', 'cron_setup' ), network_admin_url( 'admin.php?page=bookly-multisite-network' ) ) ?></p>
156
  <?php else : ?>
157
  <p><?php _e( 'To send scheduled notifications please execute the following command hourly with your cron:', 'bookly' ) ?></p><br/>
158
+ <code class="bookly-text-wrap">wget -q -O - <?php echo site_url( 'wp-cron.php' ) ?></code>
159
  <?php endif ?>
160
  </div>
161
  <div class="col-md-2">
162
+ <?php Selects::renderSingle( 'bookly_ntf_processing_interval', __( 'Notification period', 'bookly' ), __( 'Set period of time when system will attempt to deliver notification to user. Notification will be discarded after period expiration.', 'bookly' ), $bookly_ntf_processing_interval_values ) ?>
163
  </div>
164
  </div>
165
  </div>
166
+ <?php endif ?>
167
  </div>
168
 
169
  <div class="panel-footer">
170
+ <?php Inputs::renderCsrf() ?>
171
+ <?php Buttons::renderSubmit() ?>
172
+ <?php Buttons::renderReset() ?>
173
 
174
  <div class="pull-left">
175
+ <button type="button" class="btn btn-default bookly-test-email-notifications btn-lg" ng-click="showTestEmailNotificationDialog(); $event.stopPropagation();">
176
  <?php _e( 'Test Email Notifications', 'bookly' ) ?>
177
  </button>
178
  </div>
181
  </form>
182
 
183
  <?php include '_test_email_notifications_modal.php' ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  </div>
185
  </div>
backend/modules/payments/Ajax.php ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Payments;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Ajax
8
+ * @package Bookly\Backend\Modules\Payments
9
+ */
10
+ class Ajax extends Lib\Base\Ajax
11
+ {
12
+
13
+ /**
14
+ * Get payments.
15
+ */
16
+ public static function getPayments()
17
+ {
18
+ $columns = self::parameter( 'columns' );
19
+ $order = self::parameter( 'order' );
20
+ $filter = self::parameter( 'filter' );
21
+
22
+ $query = Lib\Entities\Payment::query( 'p' )
23
+ ->select( 'p.id, p.created, p.type, p.paid, p.total, p.status, p.details, c.full_name customer, st.full_name provider, s.title service, a.start_date' )
24
+ ->leftJoin( 'CustomerAppointment', 'ca', 'ca.payment_id = p.id' )
25
+ ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
26
+ ->leftJoin( 'Appointment', 'a', 'a.id = ca.appointment_id' )
27
+ ->leftJoin( 'Service', 's', 's.id = COALESCE(ca.compound_service_id, a.service_id)' )
28
+ ->leftJoin( 'Staff', 'st', 'st.id = a.staff_id' )
29
+ ->groupBy( 'p.id' );
30
+
31
+ // Filters.
32
+ list ( $start, $end ) = explode( ' - ', $filter['created'], 2 );
33
+ $end = date( 'Y-m-d', strtotime( '+1 day', strtotime( $end ) ) );
34
+
35
+ $query->whereBetween( 'p.created', $start, $end );
36
+
37
+ if ( $filter['id'] != '' ) {
38
+ $query->where( 'p.id', $filter['id'] );
39
+ }
40
+
41
+ if ( $filter['type'] != '' ) {
42
+ $query->where( 'p.type', $filter['type'] );
43
+ }
44
+
45
+ if ( $filter['staff'] != '' ) {
46
+ $query->where( 'st.id', $filter['staff'] );
47
+ }
48
+
49
+ if ( $filter['service'] != '' ) {
50
+ $query->where( 's.id', $filter['service'] );
51
+ }
52
+
53
+ if ( $filter['status'] != '' ) {
54
+ $query->where( 'p.status', $filter['status'] );
55
+ }
56
+
57
+ if ( $filter['customer'] != '' ) {
58
+ $query->where( 'ca.customer_id', $filter['customer'] );
59
+ }
60
+
61
+ foreach ( $order as $sort_by ) {
62
+ $query->sortBy( $columns[ $sort_by['column'] ]['data'] )
63
+ ->order( $sort_by['dir'] == 'desc' ? Lib\Query::ORDER_DESCENDING : Lib\Query::ORDER_ASCENDING );
64
+ }
65
+
66
+ $payments = $query->fetchArray();
67
+
68
+ $data = array();
69
+ $total = 0;
70
+ foreach ( $payments as $payment ) {
71
+ $details = json_decode( $payment['details'], true );
72
+ $multiple = count( $details['items'] ) > 1
73
+ ? ' <span class="glyphicon glyphicon-shopping-cart" title="' . esc_attr( __( 'See details for more items', 'bookly' ) ) . '"></span>'
74
+ : '';
75
+
76
+ $paid_title = Lib\Utils\Price::format( $payment['paid'] );
77
+ if ( $payment['paid'] != $payment['total'] ) {
78
+ $paid_title = sprintf( __( '%s of %s', 'bookly' ), $paid_title, Lib\Utils\Price::format( $payment['total'] ) );
79
+ }
80
+
81
+ $data[] = array(
82
+ 'id' => $payment['id'],
83
+ 'created' => Lib\Utils\DateTime::formatDateTime( $payment['created'] ),
84
+ 'type' => Lib\Entities\Payment::typeToString( $payment['type'] ),
85
+ 'customer' => $payment['customer'] ?: $details['customer'],
86
+ 'provider' => ( $payment['provider'] ?: $details['items'][0]['staff_name'] ) . $multiple,
87
+ 'service' => ( $payment['service'] ?: $details['items'][0]['service_name'] ) . $multiple,
88
+ 'start_date' => ( $payment['start_date']
89
+ ? Lib\Utils\DateTime::formatDateTime( $payment['start_date'] )
90
+ : ( $details['items'][0]['appointment_date'] ? Lib\Utils\DateTime::formatDateTime( $details['items'][0]['appointment_date'] ) . $multiple : __( 'N/A', 'bookly' ) . $multiple ) ),
91
+ 'paid' => $paid_title,
92
+ 'status' => Lib\Entities\Payment::statusToString( $payment['status'] ),
93
+ );
94
+
95
+ $total += $payment['paid'];
96
+ }
97
+
98
+ wp_send_json( array(
99
+ 'draw' => ( int ) self::parameter( 'draw' ),
100
+ 'recordsTotal' => count( $data ),
101
+ 'recordsFiltered' => count( $data ),
102
+ 'data' => $data,
103
+ 'total' => Lib\Utils\Price::format( $total ),
104
+ ) );
105
+ }
106
+
107
+ /**
108
+ * Delete payments.
109
+ */
110
+ public static function deletePayments()
111
+ {
112
+ $payment_ids = array_map( 'intval', self::parameter( 'data', array() ) );
113
+ Lib\Entities\Payment::query()->delete()->whereIn( 'id', $payment_ids )->execute();
114
+ wp_send_json_success();
115
+ }
116
+
117
+ }
backend/modules/payments/Components.php DELETED
@@ -1,34 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Payments;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Components
8
- * @package BooklyLite\Backend\Modules\Payments
9
- */
10
- class Components extends Lib\Base\Components
11
- {
12
- /**
13
- * Render payment details dialog.
14
- * @throws \Exception
15
- */
16
- public function renderPaymentDetailsDialog()
17
- {
18
- $this->enqueueStyles( array(
19
- 'frontend' => array( 'css/ladda.min.css', ),
20
- ) );
21
-
22
- $this->enqueueScripts( array(
23
- 'backend' => array( 'js/angular.min.js' => array( 'jquery' ), ),
24
- 'frontend' => array(
25
- 'js/spin.min.js' => array( 'jquery' ),
26
- 'js/ladda.min.js' => array( 'jquery' ),
27
- ),
28
- 'module' => array( 'js/ng-payment_details_dialog.js' => array( 'bookly-angular.min.js' ), ),
29
- ) );
30
-
31
- $this->render( '_payment_details_dialog' );
32
- }
33
-
34
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/payments/Controller.php DELETED
@@ -1,257 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Payments;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Payments
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- const page_slug = 'bookly-payments';
13
-
14
- /**
15
- * @return array
16
- */
17
- protected function getPermissions()
18
- {
19
- return array(
20
- 'executeGetPaymentDetails' => 'user',
21
- 'executeCompletePayment' => 'user',
22
- );
23
- }
24
-
25
- public function index()
26
- {
27
- /** @var \WP_Locale $wp_locale */
28
- global $wp_locale;
29
-
30
- $this->enqueueStyles( array(
31
- 'frontend' => array( 'css/ladda.min.css', ),
32
- 'backend' => array(
33
- 'css/select2.min.css',
34
- 'bootstrap/css/bootstrap-theme.min.css' => array( 'bookly-select2.min.css' ),
35
- 'css/daterangepicker.css',
36
- ),
37
- ) );
38
-
39
- $this->enqueueScripts( array(
40
- 'backend' => array(
41
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
42
- 'js/datatables.min.js' => array( 'jquery' ),
43
- 'js/moment.min.js',
44
- 'js/daterangepicker.js' => array( 'jquery' ),
45
- 'js/select2.full.min.js' => array( 'jquery' ),
46
- ),
47
- 'frontend' => array(
48
- 'js/spin.min.js' => array( 'jquery' ),
49
- 'js/ladda.min.js' => array( 'jquery' ),
50
- ),
51
- 'module' => array( 'js/payments.js' => array( 'bookly-datatables.min.js', 'bookly-ng-payment_details_dialog.js' ) ),
52
- ) );
53
-
54
- wp_localize_script( 'bookly-daterangepicker.js', 'BooklyL10n', array(
55
- 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
56
- 'today' => __( 'Today', 'bookly' ),
57
- 'yesterday' => __( 'Yesterday', 'bookly' ),
58
- 'last_7' => __( 'Last 7 Days', 'bookly' ),
59
- 'last_30' => __( 'Last 30 Days', 'bookly' ),
60
- 'this_month' => __( 'This Month', 'bookly' ),
61
- 'last_month' => __( 'Last Month', 'bookly' ),
62
- 'custom_range' => __( 'Custom Range', 'bookly' ),
63
- 'apply' => __( 'Apply', 'bookly' ),
64
- 'cancel' => __( 'Cancel', 'bookly' ),
65
- 'to' => __( 'To', 'bookly' ),
66
- 'from' => __( 'From', 'bookly' ),
67
- 'calendar' => array(
68
- 'longMonths' => array_values( $wp_locale->month ),
69
- 'shortMonths' => array_values( $wp_locale->month_abbrev ),
70
- 'longDays' => array_values( $wp_locale->weekday ),
71
- 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
72
- ),
73
- 'startOfWeek' => (int) get_option( 'start_of_week' ),
74
- 'mjsDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
75
- 'zeroRecords' => __( 'No payments for selected period and criteria.', 'bookly' ),
76
- 'processing' => __( 'Processing...', 'bookly' ),
77
- 'details' => __( 'Details', 'bookly' ),
78
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
79
- 'no_result_found' => __( 'No result found', 'bookly' )
80
- ) );
81
-
82
- $types = array(
83
- Lib\Entities\Payment::TYPE_LOCAL,
84
- Lib\Entities\Payment::TYPE_2CHECKOUT,
85
- Lib\Entities\Payment::TYPE_PAYPAL,
86
- Lib\Entities\Payment::TYPE_AUTHORIZENET,
87
- Lib\Entities\Payment::TYPE_STRIPE,
88
- Lib\Entities\Payment::TYPE_PAYULATAM,
89
- Lib\Entities\Payment::TYPE_PAYSON,
90
- Lib\Entities\Payment::TYPE_MOLLIE,
91
- Lib\Entities\Payment::TYPE_COUPON,
92
- Lib\Entities\Payment::TYPE_WOOCOMMERCE,
93
- );
94
- $providers = Lib\Entities\Staff::query()->select( 'id, full_name' )->sortBy( 'full_name' )->fetchArray();
95
- $services = Lib\Entities\Service::query()->select( 'id, title' )->sortBy( 'title' )->fetchArray();
96
-
97
- $this->render( 'index', compact( 'types', 'providers', 'services' ) );
98
- }
99
-
100
- /**
101
- * Get payments.
102
- */
103
- public function executeGetPayments()
104
- {
105
- $columns = $this->getParameter( 'columns' );
106
- $order = $this->getParameter( 'order' );
107
- $filter = $this->getParameter( 'filter' );
108
-
109
- $query = Lib\Entities\Payment::query( 'p' )
110
- ->select( 'p.id, p.created, p.type, p.paid, p.total, p.status, p.details, c.full_name customer, st.full_name provider, s.title service, a.start_date' )
111
- ->leftJoin( 'CustomerAppointment', 'ca', 'ca.payment_id = p.id' )
112
- ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
113
- ->leftJoin( 'Appointment', 'a', 'a.id = ca.appointment_id' )
114
- ->leftJoin( 'Service', 's', 's.id = COALESCE(ca.compound_service_id, a.service_id)' )
115
- ->leftJoin( 'Staff', 'st', 'st.id = a.staff_id' )
116
- ->groupBy( 'p.id' );
117
-
118
- // Filters.
119
- list ( $start, $end ) = explode( ' - ', $filter['created'], 2 );
120
- $end = date( 'Y-m-d', strtotime( '+1 day', strtotime( $end ) ) );
121
-
122
- $query->whereBetween( 'p.created', $start, $end );
123
-
124
- if ( $filter['type'] != '' ) {
125
- $query->where( 'p.type', $filter['type'] );
126
- }
127
-
128
- if ( $filter['staff'] != '' ) {
129
- $query->where( 'st.id', $filter['staff'] );
130
- }
131
-
132
- if ( $filter['service'] != '' ) {
133
- $query->where( 's.id', $filter['service'] );
134
- }
135
-
136
- foreach ( $order as $sort_by ) {
137
- $query->sortBy( $columns[ $sort_by['column'] ]['data'] )
138
- ->order( $sort_by['dir'] == 'desc' ? Lib\Query::ORDER_DESCENDING : Lib\Query::ORDER_ASCENDING );
139
- }
140
-
141
- $payments = $query->fetchArray();
142
-
143
- $data = array();
144
- $total = 0;
145
- foreach ( $payments as $payment ) {
146
- $details = json_decode( $payment['details'], true );
147
- $multiple = count( $details['items'] ) > 1
148
- ? ' <span class="glyphicon glyphicon-shopping-cart" title="' . esc_attr( __( 'See details for more items', 'bookly' ) ) . '"></span>'
149
- : '' ;
150
-
151
- $paid_title = Lib\Utils\Price::format( $payment['paid'] );
152
- if ( $payment['paid'] != $payment['total'] ) {
153
- $paid_title = sprintf( __( '%s of %s', 'bookly' ), $paid_title, Lib\Utils\Price::format( $payment['total'] ) );
154
- }
155
-
156
- $data[] = array(
157
- 'id' => $payment['id'],
158
- 'created' => Lib\Utils\DateTime::formatDateTime( $payment['created'] ),
159
- 'type' => Lib\Entities\Payment::typeToString( $payment['type'] ),
160
- 'customer' => $payment['customer'] ?: $details['customer'],
161
- 'provider' => ( $payment['provider'] ?: $details['items'][0]['staff_name'] ) . $multiple,
162
- 'service' => ( $payment['service'] ?: $details['items'][0]['service_name'] ) . $multiple,
163
- 'start_date' => ( $payment['start_date']
164
- ? Lib\Utils\DateTime::formatDateTime( $payment['start_date'] )
165
- : Lib\Utils\DateTime::formatDateTime( $details['items'][0]['appointment_date'] ) ) . $multiple,
166
- 'paid' => $paid_title,
167
- 'status' => Lib\Entities\Payment::statusToString( $payment['status'] ),
168
-
169
- );
170
-
171
- $total += $payment['paid'];
172
- }
173
-
174
- wp_send_json( array(
175
- 'draw' => ( int ) $this->getParameter( 'draw' ),
176
- 'recordsTotal' => count( $data ),
177
- 'recordsFiltered' => count( $data ),
178
- 'data' => $data,
179
- 'total' => Lib\Utils\Price::format( $total ),
180
- ) );
181
- }
182
-
183
- /**
184
- * Get payment details.
185
- *
186
- * @throws \Exception
187
- */
188
- public function executeGetPaymentDetails()
189
- {
190
- $data = array();
191
- $payment = Lib\Entities\Payment::query( 'p' )
192
- ->select( 'p.total,
193
- p.status,
194
- p.created AS created,
195
- p.type,
196
- p.details,
197
- p.paid,
198
- c.full_name AS customer' )
199
- ->leftJoin( 'CustomerAppointment', 'ca', 'ca.payment_id = p.id' )
200
- ->leftJoin( 'Customer', 'c', 'c.id = ca.customer_id' )
201
- ->where( 'p.id', $this->getParameter( 'payment_id' ) )
202
- ->fetchRow();
203
- if ( $payment ) {
204
- $details = json_decode( $payment['details'], true );
205
- $data = array(
206
- 'payment' => array(
207
- 'status' => $payment['status'],
208
- 'type' => $payment['type'],
209
- 'coupon' => $details['coupon'],
210
- 'created' => $payment['created'],
211
- 'customer' => empty ( $payment['customer'] ) ? $details['customer'] : $payment['customer'],
212
- 'total' => $payment['total'],
213
- 'paid' => $payment['paid'],
214
- ),
215
- 'items' => $details['items'],
216
- 'deposit_enabled' => Lib\Config::depositPaymentsEnabled()
217
- );
218
- }
219
-
220
- wp_send_json_success( array( 'html' => $this->render( 'details', $data, false ) ) );
221
- }
222
-
223
- /**
224
- * Delete payments.
225
- */
226
- public function executeDeletePayments()
227
- {
228
- $payment_ids = array_map( 'intval', $this->getParameter( 'data', array() ) );
229
- Lib\Entities\Payment::query()->delete()->whereIn( 'id', $payment_ids )->execute();
230
- wp_send_json_success();
231
- }
232
-
233
- /**
234
- * Complete payment.
235
- */
236
- public function executeCompletePayment()
237
- {
238
- $payment = Lib\Entities\Payment::find( $this->getParameter( 'payment_id' ) );
239
- $payment
240
- ->setPaid( $payment->getTotal() )
241
- ->setStatus( Lib\Entities\Payment::STATUS_COMPLETED )
242
- ->save();
243
-
244
- $payment_title = Lib\Utils\Price::format( $payment->getPaid() );
245
- if ( $payment->getPaid() != $payment->getTotal() ) {
246
- $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Price::format( $payment->getTotal() ) );
247
- }
248
- $payment_title .= sprintf(
249
- ' %s <span%s>%s</span>',
250
- Lib\Entities\Payment::typeToString( $payment->getType() ),
251
- $payment->getStatus() == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
252
- Lib\Entities\Payment::statusToString( $payment->getStatus() )
253
- );
254
-
255
- wp_send_json_success( array( 'payment_title' => $payment_title ) );
256
- }
257
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/payments/Page.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Payments;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Page
8
+ * @package Bookly\Backend\Modules\Payments
9
+ */
10
+ class Page extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render page.
14
+ */
15
+ public static function render()
16
+ {
17
+ /** @var \WP_Locale $wp_locale */
18
+ global $wp_locale;
19
+
20
+ self::enqueueStyles( array(
21
+ 'frontend' => array( 'css/ladda.min.css', ),
22
+ 'backend' => array(
23
+ 'css/select2.min.css',
24
+ 'bootstrap/css/bootstrap-theme.min.css' => array( 'bookly-select2.min.css' ),
25
+ 'css/daterangepicker.css',
26
+ ),
27
+ ) );
28
+
29
+ self::enqueueScripts( array(
30
+ 'backend' => array(
31
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
32
+ 'js/datatables.min.js' => array( 'jquery' ),
33
+ 'js/moment.min.js',
34
+ 'js/daterangepicker.js' => array( 'jquery' ),
35
+ 'js/select2.full.min.js' => array( 'jquery' ),
36
+ ),
37
+ 'frontend' => array(
38
+ 'js/spin.min.js' => array( 'jquery' ),
39
+ 'js/ladda.min.js' => array( 'jquery' ),
40
+ ),
41
+ 'module' => array( 'js/payments.js' => array( 'bookly-datatables.min.js', 'bookly-ng-payment_details.js' ) ),
42
+ ) );
43
+
44
+ wp_localize_script( 'bookly-daterangepicker.js', 'BooklyL10n', array(
45
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
46
+ 'today' => __( 'Today', 'bookly' ),
47
+ 'yesterday' => __( 'Yesterday', 'bookly' ),
48
+ 'last_7' => __( 'Last 7 Days', 'bookly' ),
49
+ 'last_30' => __( 'Last 30 Days', 'bookly' ),
50
+ 'this_month' => __( 'This Month', 'bookly' ),
51
+ 'last_month' => __( 'Last Month', 'bookly' ),
52
+ 'custom_range' => __( 'Custom Range', 'bookly' ),
53
+ 'apply' => __( 'Apply', 'bookly' ),
54
+ 'cancel' => __( 'Cancel', 'bookly' ),
55
+ 'to' => __( 'To', 'bookly' ),
56
+ 'from' => __( 'From', 'bookly' ),
57
+ 'calendar' => array(
58
+ 'longMonths' => array_values( $wp_locale->month ),
59
+ 'shortMonths' => array_values( $wp_locale->month_abbrev ),
60
+ 'longDays' => array_values( $wp_locale->weekday ),
61
+ 'shortDays' => array_values( $wp_locale->weekday_abbrev ),
62
+ ),
63
+ 'startOfWeek' => (int) get_option( 'start_of_week' ),
64
+ 'mjsDateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_MOMENT_JS ),
65
+ 'zeroRecords' => __( 'No payments for selected period and criteria.', 'bookly' ),
66
+ 'processing' => __( 'Processing...', 'bookly' ),
67
+ 'details' => __( 'Details', 'bookly' ),
68
+ 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
69
+ 'no_result_found' => __( 'No result found', 'bookly' ),
70
+ 'invoice' => array(
71
+ 'enabled' => (int) Lib\Config::invoicesActive(),
72
+ 'button' => __( 'Invoice', 'bookly' ),
73
+ ),
74
+ ) );
75
+
76
+ $types = array(
77
+ Lib\Entities\Payment::TYPE_LOCAL,
78
+ Lib\Entities\Payment::TYPE_2CHECKOUT,
79
+ Lib\Entities\Payment::TYPE_PAYPAL,
80
+ Lib\Entities\Payment::TYPE_AUTHORIZENET,
81
+ Lib\Entities\Payment::TYPE_STRIPE,
82
+ Lib\Entities\Payment::TYPE_PAYUBIZ,
83
+ Lib\Entities\Payment::TYPE_PAYULATAM,
84
+ Lib\Entities\Payment::TYPE_PAYSON,
85
+ Lib\Entities\Payment::TYPE_MOLLIE,
86
+ Lib\Entities\Payment::TYPE_COUPON,
87
+ Lib\Entities\Payment::TYPE_WOOCOMMERCE,
88
+ );
89
+
90
+ $providers = Lib\Entities\Staff::query()->select( 'id, full_name' )->sortBy( 'full_name' )->fetchArray();
91
+ $services = Lib\Entities\Service::query()->select( 'id, title' )->sortBy( 'title' )->fetchArray();
92
+ $customers = Lib\Entities\Customer::query( 'c' )->select( 'c.id, c.full_name, c.first_name, c.last_name' )->fetchArray();
93
+
94
+ self::renderTemplate( 'index', compact( 'types', 'providers', 'services', 'customers' ) );
95
+ }
96
+ }
backend/modules/payments/proxy/Invoices.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Payments\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Invoices
8
+ * @package Bookly\Backend\Modules\Notifications\Proxy
9
+ *
10
+ * @method static void renderDownloadButton() Render button for downloading invoice(s).
11
+ */
12
+ abstract class Invoices extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/payments/proxy/Pro.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Payments\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Modules\Notifications\Proxy
9
+ *
10
+ * @method static void renderManualAdjustmentButton() Render manual adjustment button.
11
+ * @method static void renderManualAdjustmentForm( array $show ) Render manual adjustment form.
12
+ */
13
+ abstract class Pro extends Lib\Base\Proxy
14
+ {
15
+ }
backend/modules/payments/proxy/Shared.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Payments\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Shared
8
+ * @package Bookly\Backend\Modules\Payments\Proxy
9
+ *
10
+ * @method static bool paymentSpecificPriceExists( string $gateway ) Check whether specific price exists for given gateway.
11
+ */
12
+ abstract class Shared extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/payments/resources/js/ng-payment_details_dialog.js DELETED
@@ -1,69 +0,0 @@
1
- ;(function() {
2
-
3
- angular.module('paymentDetailsDialog', []).directive('paymentDetailsDialog', function() {
4
- return {
5
- restrict: 'A',
6
- replace: true,
7
- scope: {
8
- callback: '&paymentDetailsDialog'
9
- },
10
- templateUrl: 'bookly-payment-details-dialog.tpl',
11
- // The linking function will add behavior to the template.
12
- link: function (scope, element, attrs) {
13
- var $body = element.find('.modal-body'),
14
- spinner = $body.html();
15
-
16
- element
17
- .on('show.bs.modal', function (e, payment_id) {
18
- if (payment_id === undefined) {
19
- payment_id = e.relatedTarget.getAttribute('data-payment_id');
20
- }
21
- jQuery.ajax({
22
- url: ajaxurl,
23
- data: {action: 'bookly_get_payment_details', payment_id: payment_id, csrf_token : BooklyL10n.csrf_token},
24
- dataType: 'json',
25
- success: function (response) {
26
- if (response.success) {
27
- $body.html(response.data.html);
28
- $body.find('#bookly-complete-payment').on('click',function () {
29
- var ladda = Ladda.create(this);
30
- ladda.start();
31
- jQuery.ajax({
32
- url: ajaxurl,
33
- data: {action: 'bookly_complete_payment', payment_id: payment_id, csrf_token : BooklyL10n.csrf_token},
34
- dataType: 'json',
35
- type: 'POST',
36
- success: function (response) {
37
- if (response.success) {
38
- element.trigger('show.bs.modal', [payment_id]);
39
- if (scope.callback) {
40
- scope.$apply(function ($scope) {
41
- $scope.callback({
42
- payment_id : payment_id,
43
- payment_title : response.data.payment_title
44
- });
45
- });
46
- }
47
- // Reload DataTable.
48
- var $table = jQuery(e.relatedTarget).closest('table.dataTable');
49
- if ($table.length) {
50
- $table.DataTable().ajax.reload();
51
- }
52
- }
53
- }
54
- });
55
- });
56
- }
57
- }
58
- });
59
- })
60
- .on('hidden.bs.modal', function () {
61
- $body.html(spinner);
62
- if ((jQuery("#bookly-appointment-dialog").data('bs.modal') || {isShown: false}).isShown) {
63
- jQuery('body').addClass('modal-open');
64
- }
65
- });
66
- }
67
- }
68
- });
69
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/payments/resources/js/payments.js CHANGED
@@ -3,12 +3,16 @@ jQuery(function($) {
3
  var
4
  $payments_list = $('#bookly-payments-list'),
5
  $check_all_button = $('#bookly-check-all'),
 
6
  $date_filter = $('#bookly-filter-date'),
7
  $type_filter = $('#bookly-filter-type'),
 
8
  $staff_filter = $('#bookly-filter-staff'),
9
  $service_filter = $('#bookly-filter-service'),
 
10
  $payment_total = $('#bookly-payment-total'),
11
- $delete_button = $('#bookly-delete')
 
12
  ;
13
  $('.bookly-js-select')
14
  .val(null)
@@ -23,6 +27,42 @@ jQuery(function($) {
23
  noResults: function() { return BooklyL10n.no_result_found; }
24
  }
25
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  /**
27
  * Init DataTables.
28
  */
@@ -42,10 +82,13 @@ jQuery(function($) {
42
  action: 'bookly_get_payments',
43
  csrf_token: BooklyL10n.csrf_token,
44
  filter: {
45
- created: $date_filter.data('date'),
46
- type: $type_filter.val(),
47
- staff: $staff_filter.val(),
48
- service: $service_filter.val()
 
 
 
49
  }
50
  } );
51
  },
@@ -55,35 +98,10 @@ jQuery(function($) {
55
  return json.data;
56
  }
57
  },
58
- columns: [
59
- { data: 'created' },
60
- { data: 'type' },
61
- { data: 'customer', render: $.fn.dataTable.render.text() },
62
- { data: 'provider' },
63
- { data: 'service' },
64
- { data: 'start_date' },
65
- { data: 'paid' },
66
- { data: 'status' },
67
- {
68
- responsivePriority: 1,
69
- orderable: false,
70
- searchable: false,
71
- render: function ( data, type, row, meta ) {
72
- return '<button type="button" class="btn btn-default" data-toggle="modal" data-target="#bookly-payment-details-modal" data-payment_id="' + row.id + '"><i class="glyphicon glyphicon-list-alt"></i> ' + BooklyL10n.details + '</a>';
73
- }
74
- },
75
- {
76
- responsivePriority: 1,
77
- orderable: false,
78
- searchable: false,
79
- render: function ( data, type, row, meta ) {
80
- return '<input type="checkbox" value="' + row.id + '">';
81
- }
82
- }
83
- ],
84
  language: {
85
  zeroRecords: BooklyL10n.zeroRecords,
86
- processing: BooklyL10n.processing
87
  }
88
  });
89
 
@@ -100,6 +118,7 @@ jQuery(function($) {
100
  $payments_list.on('change', 'tbody input:checkbox', function () {
101
  $check_all_button.prop('checked', $payments_list.find('tbody input:not(:checked)').length == 0);
102
  });
 
103
  /**
104
  * Init date range picker.
105
  */
@@ -145,11 +164,13 @@ jQuery(function($) {
145
  }
146
  );
147
 
148
-
149
  $date_filter.on('apply.daterangepicker', function () { dt.ajax.reload(); });
150
  $type_filter.on('change', function () { dt.ajax.reload(); });
 
151
  $staff_filter.on('change', function () { dt.ajax.reload(); });
152
  $service_filter.on('change', function () { dt.ajax.reload(); });
 
153
 
154
  /**
155
  * Delete payments.
@@ -185,6 +206,20 @@ jQuery(function($) {
185
  });
186
  }
187
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  });
189
 
190
  (function() {
3
  var
4
  $payments_list = $('#bookly-payments-list'),
5
  $check_all_button = $('#bookly-check-all'),
6
+ $id_filter = $('#bookly-filter-id'),
7
  $date_filter = $('#bookly-filter-date'),
8
  $type_filter = $('#bookly-filter-type'),
9
+ $customer_filter = $('#bookly-filter-customer'),
10
  $staff_filter = $('#bookly-filter-staff'),
11
  $service_filter = $('#bookly-filter-service'),
12
+ $status_filter = $('#bookly-filter-status'),
13
  $payment_total = $('#bookly-payment-total'),
14
+ $delete_button = $('#bookly-delete'),
15
+ $download_invoice = $('#bookly-download-invoices')
16
  ;
17
  $('.bookly-js-select')
18
  .val(null)
27
  noResults: function() { return BooklyL10n.no_result_found; }
28
  }
29
  });
30
+
31
+ /**
32
+ * Init Columns.
33
+ */
34
+ var columns = [
35
+ { data: 'id' },
36
+ { data: 'created' },
37
+ { data: 'type' },
38
+ { data: 'customer', render: $.fn.dataTable.render.text() },
39
+ { data: 'provider' },
40
+ { data: 'service' },
41
+ { data: 'start_date' },
42
+ { data: 'paid' },
43
+ { data: 'status' },
44
+ {
45
+ responsivePriority: 1,
46
+ orderable: false,
47
+ searchable: false,
48
+ render: function ( data, type, row, meta ) {
49
+ var buttons = '';
50
+ if (BooklyL10n.invoice.enabled) {
51
+ buttons += '<button type="button" class="btn btn-default bookly-margin-right-md" data-action="view-invoice" data-payment_id="' + row.id + '"><i class="dashicons dashicons-media-text"></i> ' + BooklyL10n.invoice.button + '</a>';
52
+ }
53
+ return buttons + '<button type="button" class="btn btn-default" data-toggle="modal" data-target="#bookly-payment-details-modal" data-payment_id="' + row.id + '"><i class="glyphicon glyphicon-list-alt"></i> ' + BooklyL10n.details + '</a>';
54
+ }
55
+ },
56
+ {
57
+ responsivePriority: 1,
58
+ orderable: false,
59
+ searchable: false,
60
+ render: function ( data, type, row, meta ) {
61
+ return '<input type="checkbox" value="' + row.id + '">';
62
+ }
63
+ }
64
+ ];
65
+
66
  /**
67
  * Init DataTables.
68
  */
82
  action: 'bookly_get_payments',
83
  csrf_token: BooklyL10n.csrf_token,
84
  filter: {
85
+ id : $id_filter.val(),
86
+ created : $date_filter.data('date'),
87
+ type : $type_filter.val(),
88
+ customer: $customer_filter.val(),
89
+ staff : $staff_filter.val(),
90
+ service : $service_filter.val(),
91
+ status : $status_filter.val()
92
  }
93
  } );
94
  },
98
  return json.data;
99
  }
100
  },
101
+ columns: columns,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  language: {
103
  zeroRecords: BooklyL10n.zeroRecords,
104
+ processing: BooklyL10n.processing
105
  }
106
  });
107
 
118
  $payments_list.on('change', 'tbody input:checkbox', function () {
119
  $check_all_button.prop('checked', $payments_list.find('tbody input:not(:checked)').length == 0);
120
  });
121
+
122
  /**
123
  * Init date range picker.
124
  */
164
  }
165
  );
166
 
167
+ $id_filter.on('keyup', function () { dt.ajax.reload(); });
168
  $date_filter.on('apply.daterangepicker', function () { dt.ajax.reload(); });
169
  $type_filter.on('change', function () { dt.ajax.reload(); });
170
+ $customer_filter.on('change', function () { dt.ajax.reload(); });
171
  $staff_filter.on('change', function () { dt.ajax.reload(); });
172
  $service_filter.on('change', function () { dt.ajax.reload(); });
173
+ $status_filter.on('change', function () { dt.ajax.reload(); });
174
 
175
  /**
176
  * Delete payments.
206
  });
207
  }
208
  });
209
+
210
+ $payments_list.on('click', '[data-action=view-invoice]', function () {
211
+ window.location = $download_invoice.data('action') + '&invoices=' + $(this).data('payment_id');
212
+ });
213
+
214
+ $download_invoice.on('click', function () {
215
+ var invoices = [];
216
+ $payments_list.find('tbody input:checked').each(function () {
217
+ invoices.push(this.value);
218
+ });
219
+ if (invoices.length) {
220
+ window.location = $(this).data('action') + '&invoices=' + invoices.join(',');
221
+ }
222
+ });
223
  });
224
 
225
  (function() {
backend/modules/payments/templates/details.php DELETED
@@ -1,137 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Utils\Price;
3
- use BooklyLite\Lib\Utils\DateTime;
4
- use BooklyLite\Lib\Entities;
5
- use BooklyLite\Lib\Proxy;
6
-
7
- $subtotal = 0;
8
- $subtotal_deposit = 0;
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>
29
- </table>
30
- </div>
31
-
32
- <div class="table-responsive">
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 ( $deposit_enabled ): ?>
40
- <th class="text-right"><?php _e( 'Deposit', 'bookly' ) ?></th>
41
- <?php endif ?>
42
- <th class="text-right"><?php _e( 'Price', 'bookly' ) ?></th>
43
- </tr>
44
- </thead>
45
- <tbody>
46
- <?php foreach ( $items as $item ) :
47
- $extras_price = 0; ?>
48
- <tr>
49
- <td>
50
- <?php if ( $item['number_of_persons'] > 1 ) echo $item['number_of_persons'] . '&nbsp;&times;&nbsp;' ?><?php echo esc_html( $item['service_name'] ) ?>
51
- <?php if ( ! empty ( $item['extras'] ) ) : ?>
52
- <ul class="bookly-list list-dots">
53
- <?php foreach ( $item['extras'] as $extra ) : ?>
54
- <li><?php if ( $extra['quantity'] > 1 ) echo $extra['quantity'] . '&nbsp;&times;&nbsp;' ?><?php echo esc_html( $extra['title'] ) ?></li>
55
- <?php $extras_price += $extra['price'] * $extra['quantity'] ?>
56
- <?php endforeach ?>
57
- </ul>
58
- <?php endif ?>
59
- </td>
60
- <td><?php echo DateTime::formatDateTime( $item['appointment_date'] ) ?></td>
61
- <td><?php echo esc_html( $item['staff_name'] ) ?></td>
62
- <?php $deposit = Proxy\DepositPayments::prepareAmount( $item['number_of_persons'] * ( $item['service_price'] + $extras_price ), $item['deposit'], $item['number_of_persons'] ) ?>
63
- <?php if ( $deposit_enabled ) : ?>
64
- <td class="text-right"><?php echo Proxy\DepositPayments::formatDeposit( $deposit, $item['deposit'] ) ?></td>
65
- <?php endif ?>
66
- <td class="text-right">
67
- <?php $service_price = Price::format( $item['service_price'] ) ?>
68
- <?php if ( $item['number_of_persons'] > 1 ) $service_price = $item['number_of_persons'] . '&nbsp;&times;&nbsp' . $service_price ?>
69
- <?php echo $service_price ?>
70
- <ul class="bookly-list">
71
- <?php foreach ( $item['extras'] as $extra ) : ?>
72
- <li>
73
- <?php printf( '%s%s%s',
74
- ( $item['number_of_persons'] > 1 ) ? $item['number_of_persons'] . '&nbsp;&times;&nbsp;' : '',
75
- ( $extra['quantity'] > 1 ) ? $extra['quantity'] . '&nbsp;&times;&nbsp;' : '',
76
- Price::format( $extra['price'] )
77
- ) ?>
78
- </li>
79
- <?php $subtotal += $item['number_of_persons'] * $extra['price'] * $extra['quantity'] ?>
80
- <?php endforeach ?>
81
- </ul>
82
- </td>
83
- </tr>
84
- <?php $subtotal += $item['number_of_persons'] * $item['service_price'] ?>
85
- <?php $subtotal_deposit += $deposit ?>
86
- <?php endforeach ?>
87
- </tbody>
88
- <tfoot>
89
- <tr>
90
- <th rowspan="3" style="border-left-color: white; border-bottom-color: white;"></th>
91
- <th colspan="2"><?php _e( 'Subtotal', 'bookly' ) ?></th>
92
- <?php if ( $deposit_enabled ) : ?>
93
- <th class="text-right"><?php echo Price::format( $subtotal_deposit ) ?></th>
94
- <?php endif ?>
95
- <th class="text-right"><?php echo Price::format( $subtotal ) ?></th>
96
- </tr>
97
- <tr>
98
- <th colspan="<?php echo 2 + (int) $deposit_enabled ?>">
99
- <?php _e( 'Discount', 'bookly' ) ?>
100
- <?php if ( $payment['coupon'] ) : ?><div><small>(<?php echo $payment['coupon']['code'] ?>)</small></div><?php endif ?>
101
- </th>
102
- <th class="text-right">
103
- <?php if ( $payment['coupon'] ) : ?>
104
- <?php if ( $payment['coupon']['discount'] ) : ?>
105
- <div>-<?php echo $payment['coupon']['discount'] ?>%</div>
106
- <?php endif ?>
107
- <?php if ( $payment['coupon']['deduction'] ) : ?>
108
- <div><?php echo Price::format( -$payment['coupon']['deduction'] ) ?></div>
109
- <?php endif ?>
110
- <?php else : ?>
111
- <?php echo Price::format( 0 ) ?>
112
- <?php endif ?>
113
- </th>
114
- </tr>
115
- <tr>
116
- <th colspan="<?php echo 2 + (int) $deposit_enabled ?>"><?php _e( 'Total', 'bookly' ) ?></th>
117
- <th class="text-right"><?php echo Price::format( $payment['total'] ) ?></th>
118
- </tr>
119
- <?php if ( $payment['total'] != $payment['paid'] ) : ?>
120
- <tr>
121
- <td rowspan="2" style="border-left-color:#fff;border-bottom-color:#fff;"></td>
122
- <td colspan="<?php echo 2 + (int) $deposit_enabled ?>"><i><?php _e( 'Paid', 'bookly' ) ?></i></td>
123
- <td class="text-right"><i><?php echo Price::format( $payment['paid'] ) ?></i></td>
124
- </tr>
125
- <tr>
126
- <td colspan="<?php echo 2 + (int) $deposit_enabled ?>"><i><?php _e( 'Due', 'bookly' ) ?></i></td>
127
- <td class="text-right"><i><?php echo Price::format( $payment['total'] - $payment['paid'] ) ?></i></td>
128
- </tr>
129
- <tr>
130
- <td style="border-left-color:#fff;border-bottom-color:#fff;"></td>
131
- <td colspan="<?php echo 3 + (int) $deposit_enabled ?>" class="text-right"><button type="button" class="btn btn-success ladda-button" id="bookly-complete-payment" data-spinner-size="40" data-style="zoom-in"><i><?php _e( 'Complete payment', 'bookly' ) ?></i></button></td>
132
- </tr>
133
- <?php endif ?>
134
- </tfoot>
135
- </table>
136
- </div>
137
- <?php endif ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/payments/templates/index.php CHANGED
@@ -1,37 +1,55 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
 
 
2
  <div id="bookly-tbs" class="wrap">
3
  <div class="bookly-tbs-body">
4
  <div class="page-header text-right clearfix">
5
  <div class="bookly-page-title">
6
  <?php _e( 'Payments', 'bookly' ) ?>
7
  </div>
8
- <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
9
  </div>
10
  <div class="panel panel-default bookly-main">
11
  <div class="panel-body">
12
  <div class="row">
 
 
 
 
 
13
  <div class="col-md-4 col-lg-3">
14
  <div class="bookly-margin-bottom-lg bookly-relative">
15
  <button type="button" class="btn btn-block btn-default" id="bookly-filter-date" data-date="<?php echo date( 'Y-m-d', strtotime( '-30 day' ) ) ?> - <?php echo date( 'Y-m-d' ) ?>">
16
  <i class="dashicons dashicons-calendar-alt"></i>
17
  <span>
18
- <?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( '-30 days' ) ?> - <?php echo \BooklyLite\Lib\Utils\DateTime::formatDate( 'today' ) ?>
19
  </span>
20
  </button>
21
  </div>
22
  </div>
23
- <div class="col-md-2 col-lg-2">
24
  <div class="form-group">
25
  <select id="bookly-filter-type" class="form-control bookly-js-select" data-placeholder="<?php esc_attr_e( 'Type', 'bookly' ) ?>">
26
  <?php foreach ( $types as $type ) : ?>
27
  <option value="<?php echo esc_attr( $type ) ?>">
28
- <?php echo \BooklyLite\Lib\Entities\Payment::typeToString( $type ) ?>
29
  </option>
30
  <?php endforeach ?>
31
  </select>
32
  </div>
33
  </div>
34
- <div class="col-md-3 col-lg-2">
 
 
 
 
 
 
 
 
 
35
  <div class="form-group">
36
  <select id="bookly-filter-staff" class="form-control bookly-js-select" data-placeholder="<?php esc_attr_e( 'Provider', 'bookly' ) ?>">
37
  <?php foreach ( $providers as $provider ) : ?>
@@ -40,7 +58,7 @@
40
  </select>
41
  </div>
42
  </div>
43
- <div class="col-md-3 col-lg-2">
44
  <div class="form-group">
45
  <select id="bookly-filter-service" class="form-control bookly-js-select" data-placeholder="<?php esc_attr_e( 'Service', 'bookly' ) ?>">
46
  <?php foreach ( $services as $service ) : ?>
@@ -49,11 +67,21 @@
49
  </select>
50
  </div>
51
  </div>
 
 
 
 
 
 
 
 
 
52
  </div>
53
 
54
  <table id="bookly-payments-list" class="table table-striped" width="100%">
55
  <thead>
56
  <tr>
 
57
  <th><?php _e( 'Date', 'bookly' ) ?></th>
58
  <th><?php _e( 'Type', 'bookly' ) ?></th>
59
  <th><?php _e( 'Customer', 'bookly' ) ?></th>
@@ -68,20 +96,21 @@
68
  </thead>
69
  <tfoot>
70
  <tr>
71
- <th colspan="6"><div class="pull-right"><?php _e( 'Total', 'bookly' ) ?>:</div></th>
72
  <th colspan="4"><span id="bookly-payment-total"></span></th>
73
  </tr>
74
  </tfoot>
75
  </table>
76
  <div class="text-right bookly-margin-top-lg">
77
- <?php \BooklyLite\Lib\Utils\Common::deleteButton() ?>
 
78
  </div>
79
  </div>
80
  </div>
81
 
82
  <div ng-app="paymentDetails" ng-controller="paymentDetailsCtrl">
83
  <div payment-details-dialog></div>
84
- <?php \BooklyLite\Backend\Modules\Payments\Components::getInstance()->renderPaymentDetailsDialog() ?>
85
  </div>
86
  </div>
87
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Lib\Entities\Payment;
3
+ use Bookly\Backend\Components;
4
+ use Bookly\Backend\Modules\Payments\Proxy;
5
+ ?>
6
  <div id="bookly-tbs" class="wrap">
7
  <div class="bookly-tbs-body">
8
  <div class="page-header text-right clearfix">
9
  <div class="bookly-page-title">
10
  <?php _e( 'Payments', 'bookly' ) ?>
11
  </div>
12
+ <?php Components\Support\Buttons::render( $self::pageSlug() ) ?>
13
  </div>
14
  <div class="panel panel-default bookly-main">
15
  <div class="panel-body">
16
  <div class="row">
17
+ <div class="col-md-1 col-lg-1">
18
+ <div class="form-group">
19
+ <input class="form-control" type="text" id="bookly-filter-id" placeholder="<?php esc_attr_e( 'No.', 'bookly' ) ?>" />
20
+ </div>
21
+ </div>
22
  <div class="col-md-4 col-lg-3">
23
  <div class="bookly-margin-bottom-lg bookly-relative">
24
  <button type="button" class="btn btn-block btn-default" id="bookly-filter-date" data-date="<?php echo date( 'Y-m-d', strtotime( '-30 day' ) ) ?> - <?php echo date( 'Y-m-d' ) ?>">
25
  <i class="dashicons dashicons-calendar-alt"></i>
26
  <span>
27
+ <?php echo Bookly\Lib\Utils\DateTime::formatDate( '-30 days' ) ?> - <?php echo Bookly\Lib\Utils\DateTime::formatDate( 'today' ) ?>
28
  </span>
29
  </button>
30
  </div>
31
  </div>
32
+ <div class="col-md-1 col-lg-1">
33
  <div class="form-group">
34
  <select id="bookly-filter-type" class="form-control bookly-js-select" data-placeholder="<?php esc_attr_e( 'Type', 'bookly' ) ?>">
35
  <?php foreach ( $types as $type ) : ?>
36
  <option value="<?php echo esc_attr( $type ) ?>">
37
+ <?php echo Payment::typeToString( $type ) ?>
38
  </option>
39
  <?php endforeach ?>
40
  </select>
41
  </div>
42
  </div>
43
+ <div class="col-md-1 col-lg-2">
44
+ <div class="form-group">
45
+ <select class="form-control bookly-js-select" id="bookly-filter-customer" data-placeholder="<?php esc_attr_e( 'Customer', 'bookly' ) ?>">
46
+ <?php foreach ( $customers as $customer ) : ?>
47
+ <option value="<?php echo $customer['id'] ?>"><?php echo esc_html( $customer['full_name'] ) ?></option>
48
+ <?php endforeach ?>
49
+ </select>
50
+ </div>
51
+ </div>
52
+ <div class="col-md-1 col-lg-2">
53
  <div class="form-group">
54
  <select id="bookly-filter-staff" class="form-control bookly-js-select" data-placeholder="<?php esc_attr_e( 'Provider', 'bookly' ) ?>">
55
  <?php foreach ( $providers as $provider ) : ?>
58
  </select>
59
  </div>
60
  </div>
61
+ <div class="col-md-1 col-lg-2">
62
  <div class="form-group">
63
  <select id="bookly-filter-service" class="form-control bookly-js-select" data-placeholder="<?php esc_attr_e( 'Service', 'bookly' ) ?>">
64
  <?php foreach ( $services as $service ) : ?>
67
  </select>
68
  </div>
69
  </div>
70
+ <div class="col-md-1 col-lg-1">
71
+ <div class="form-group">
72
+ <select id="bookly-filter-status" class="form-control bookly-js-select" data-placeholder="<?php esc_attr_e( 'Status', 'bookly' ) ?>">
73
+ <option value="<?php echo Payment::STATUS_COMPLETED ?>"><?php echo Payment::statusToString( Payment::STATUS_COMPLETED ) ?></option>
74
+ <option value="<?php echo Payment::STATUS_PENDING ?>"><?php echo Payment::statusToString( Payment::STATUS_PENDING ) ?></option>
75
+ <option value="<?php echo Payment::STATUS_REJECTED ?>"><?php echo Payment::statusToString( Payment::STATUS_REJECTED ) ?></option>
76
+ </select>
77
+ </div>
78
+ </div>
79
  </div>
80
 
81
  <table id="bookly-payments-list" class="table table-striped" width="100%">
82
  <thead>
83
  <tr>
84
+ <th><?php _e( 'No.', 'bookly' ) ?></th>
85
  <th><?php _e( 'Date', 'bookly' ) ?></th>
86
  <th><?php _e( 'Type', 'bookly' ) ?></th>
87
  <th><?php _e( 'Customer', 'bookly' ) ?></th>
96
  </thead>
97
  <tfoot>
98
  <tr>
99
+ <th colspan="7"><div class="pull-right"><?php _e( 'Total', 'bookly' ) ?>:</div></th>
100
  <th colspan="4"><span id="bookly-payment-total"></span></th>
101
  </tr>
102
  </tfoot>
103
  </table>
104
  <div class="text-right bookly-margin-top-lg">
105
+ <?php Proxy\Invoices::renderDownloadButton() ?>
106
+ <?php Components\Controls\Buttons::renderDelete() ?>
107
  </div>
108
  </div>
109
  </div>
110
 
111
  <div ng-app="paymentDetails" ng-controller="paymentDetailsCtrl">
112
  <div payment-details-dialog></div>
113
+ <?php Components\Dialogs\Payment\Dialog::render() ?>
114
  </div>
115
  </div>
116
  </div>
backend/modules/services/Ajax.php ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Services;
3
+
4
+ use Bookly\Backend\Components\Notices\Limitation;
5
+ use Bookly\Lib;
6
+
7
+ /**
8
+ * Class Ajax
9
+ * @package Bookly\Backend\Modules\Services
10
+ */
11
+ class Ajax extends Page
12
+ {
13
+ /**
14
+ * Get category services
15
+ */
16
+ public static function getCategoryServices()
17
+ {
18
+ wp_send_json_success( self::renderTemplate( '_list', self::_getCaSeStCollections(), false ) );
19
+ }
20
+
21
+ /**
22
+ * Add category.
23
+ */
24
+ public static function addCategory()
25
+ {
26
+ $html = '';
27
+ if ( ! empty ( $_POST ) && self::csrfTokenValid() ) {
28
+ $form = new Forms\Category();
29
+ $form->bind( self::postParameters() );
30
+ if ( $category = $form->save() ) {
31
+ $html = self::renderTemplate( '_category_item', array( 'category' => $category->getFields() ), false );
32
+ }
33
+ }
34
+ wp_send_json_success( compact( 'html' ) );
35
+ }
36
+
37
+ /**
38
+ * Update category.
39
+ */
40
+ public static function updateCategory()
41
+ {
42
+ $form = new Forms\Category();
43
+ $form->bind( self::postParameters() );
44
+ $form->save();
45
+ }
46
+
47
+ /**
48
+ * Update category position.
49
+ */
50
+ public static function updateCategoryPosition()
51
+ {
52
+ $category_sorts = self::parameter( 'position' );
53
+ foreach ( $category_sorts as $position => $category_id ) {
54
+ $category_sort = new Lib\Entities\Category();
55
+ $category_sort->load( $category_id );
56
+ $category_sort->setPosition( $position );
57
+ $category_sort->save();
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Update services position.
63
+ */
64
+ public static function updateServicesPosition()
65
+ {
66
+ $services_sorts = self::parameter( 'position' );
67
+ foreach ( $services_sorts as $position => $service_id ) {
68
+ $services_sort = new Lib\Entities\Service();
69
+ $services_sort->load( $service_id );
70
+ $services_sort->setPosition( $position );
71
+ $services_sort->save();
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Delete category.
77
+ */
78
+ public static function deleteCategory()
79
+ {
80
+ $category = new Lib\Entities\Category();
81
+ $category->setId( self::parameter( 'id', 0 ) );
82
+ $category->delete();
83
+ }
84
+
85
+ /**
86
+ * Add service.
87
+ */
88
+ public static function addService()
89
+ {
90
+ ! Lib\Config::proActive() &&
91
+ get_option( 'bookly_updated_from_legacy_version' ) != 'lite' &&
92
+ Lib\Entities\Service::query()->count() > 4 &&
93
+ wp_send_json_error( array( 'message' => Limitation::getHtml() ) );
94
+
95
+ $form = new Forms\Service();
96
+ $form->bind( self::postParameters() );
97
+ $form->getObject()->setDuration( Lib\Config::getTimeSlotLength() );
98
+ $service = $form->save();
99
+ $data = self::_getCaSeStCollections( $service->getCategoryId() );
100
+
101
+ Proxy\Shared::serviceCreated( $service, self::postParameters() );
102
+
103
+ wp_send_json_success( array( 'html' => self::renderTemplate( '_list', $data, false ), 'service_id' => $service->getId() ) );
104
+ }
105
+
106
+ /**
107
+ * 'Safely' remove services (report if there are future appointments)
108
+ */
109
+ public static function removeServices()
110
+ {
111
+ $service_ids = self::parameter( 'service_ids', array() );
112
+ if ( self::parameter( 'force_delete', false ) ) {
113
+ if ( is_array( $service_ids ) && ! empty ( $service_ids ) ) {
114
+ foreach ( $service_ids as $service_id ) {
115
+ Proxy\Shared::serviceDeleted( $service_id );
116
+ }
117
+ Lib\Entities\Service::query( 's' )->delete()->whereIn( 's.id', $service_ids )->execute();
118
+ }
119
+ } else {
120
+ /** @var Lib\Entities\Appointment $appointment */
121
+ $appointment = Lib\Entities\Appointment::query( 'a' )
122
+ ->whereIn( 'a.service_id', $service_ids )
123
+ ->whereGt( 'a.start_date', current_time( 'mysql' ) )
124
+ ->sortBy( 'a.start_date' )
125
+ ->order( 'DESC' )
126
+ ->limit( '1' )
127
+ ->findOne();
128
+
129
+ if ( $appointment ) {
130
+ $last_month = date_create( $appointment->getStartDate() )->modify( 'last day of' )->format( 'Y-m-d' );
131
+ $action = 'show_modal';
132
+ $filter_url = sprintf( '%s#service=%d&range=%s-%s',
133
+ Lib\Utils\Common::escAdminUrl( \Bookly\Backend\Modules\Appointments\Page::pageSlug() ),
134
+ $appointment->getServiceId(),
135
+ date_create( current_time( 'mysql' ) )->format( 'Y-m-d' ),
136
+ $last_month );
137
+ wp_send_json_error( compact( 'action', 'filter_url' ) );
138
+ } else {
139
+ $action = 'confirm';
140
+ wp_send_json_error( compact( 'action' ) );
141
+ }
142
+ }
143
+
144
+ wp_send_json_success();
145
+ }
146
+
147
+ /**
148
+ * Update service parameters and assign staff
149
+ */
150
+ public static function updateService()
151
+ {
152
+ $form = new Forms\Service();
153
+ $form->bind( self::postParameters() );
154
+ $service = $form->save();
155
+
156
+ $staff_ids = self::parameter( 'staff_ids', array() );
157
+ if ( empty ( $staff_ids ) ) {
158
+ Lib\Entities\StaffService::query()->delete()->where( 'service_id', $service->getId() )->execute();
159
+ } else {
160
+ Lib\Entities\StaffService::query()->delete()->where( 'service_id', $service->getId() )->whereNotIn( 'staff_id', $staff_ids )->execute();
161
+ if ( $service->getType() == Lib\Entities\Service::TYPE_SIMPLE ) {
162
+ if ( self::parameter( 'update_staff', false ) ) {
163
+ Lib\Entities\StaffService::query()
164
+ ->update()
165
+ ->set( 'price', self::parameter( 'price' ) )
166
+ ->set( 'capacity_min', $service->getCapacityMin() )
167
+ ->set( 'capacity_max', $service->getCapacityMax() )
168
+ ->where( 'service_id', self::parameter( 'id' ) )
169
+ ->execute();
170
+ }
171
+ // Create records for newly linked staff.
172
+ $existing_staff_ids = array();
173
+ $res = Lib\Entities\StaffService::query()
174
+ ->select( 'staff_id' )
175
+ ->where( 'service_id', $service->getId() )
176
+ ->fetchArray();
177
+ foreach ( $res as $staff ) {
178
+ $existing_staff_ids[] = $staff['staff_id'];
179
+ }
180
+ foreach ( $staff_ids as $staff_id ) {
181
+ if ( ! in_array( $staff_id, $existing_staff_ids ) ) {
182
+ $staff_service = new Lib\Entities\StaffService();
183
+ $staff_service->setStaffId( $staff_id )
184
+ ->setServiceId( $service->getId() )
185
+ ->setPrice( $service->getPrice() )
186
+ ->setCapacityMin( $service->getCapacityMin() )
187
+ ->setCapacityMax( $service->getCapacityMax() )
188
+ ->save();
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ // Update services in addons.
195
+ $alert = Proxy\Shared::updateService( array( 'success' => array( __( 'Settings saved.', 'bookly' ) ) ), $service, self::postParameters() );
196
+
197
+ $price = Lib\Utils\Price::format( $service->getPrice() );
198
+ $nice_duration = Lib\Utils\DateTime::secondsToInterval( $service->getDuration() );
199
+ $title = $service->getTitle();
200
+ $colors = array_fill( 0, 3, $service->getColor() );
201
+
202
+ wp_send_json_success( Proxy\Shared::prepareUpdateServiceResponse( compact( 'title', 'price', 'colors', 'nice_duration', 'alert' ), $service, self::postParameters() ) );
203
+ }
204
+ }
backend/modules/services/Controller.php DELETED
@@ -1,332 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Services;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Services
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- const page_slug = 'bookly-services';
13
-
14
- /**
15
- * Index page.
16
- */
17
- public function index()
18
- {
19
- wp_enqueue_media();
20
- $this->enqueueStyles( array(
21
- 'wp' => array( 'wp-color-picker' ),
22
- 'frontend' => array( 'css/ladda.min.css' ),
23
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css' ),
24
- ) );
25
-
26
- $this->enqueueScripts( array(
27
- 'wp' => array( 'wp-color-picker' ),
28
- 'backend' => array(
29
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
30
- 'js/help.js' => array( 'jquery' ),
31
- 'js/alert.js' => array( 'jquery' ),
32
- 'js/range_tools.js' => array( 'jquery' ),
33
- ),
34
- 'module' => array( 'js/service.js' => array( 'jquery-ui-sortable', 'jquery' ) ),
35
- 'frontend' => array(
36
- 'js/spin.min.js' => array( 'jquery' ),
37
- 'js/ladda.min.js' => array( 'bookly-spin.min.js', 'jquery' ),
38
- )
39
- ) );
40
-
41
- $data = $this->getCaSeStSpCollections();
42
- $staff = array();
43
- foreach ( $data['staff_collection'] as $employee ) {
44
- $staff[ $employee['id'] ] = $employee['full_name'];
45
- }
46
-
47
- wp_localize_script( 'bookly-service.js', 'BooklyL10n', array(
48
- 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
49
- 'capacity_error' => __( 'Min capacity should not be greater than max capacity.', 'bookly' ),
50
- 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
51
- 'service_special_day' => Lib\Config::specialDaysEnabled() && Lib\Config::specialDaysEnabled(),
52
- 'reorder' => esc_attr__( 'Reorder', 'bookly' ),
53
- 'staff' => $staff,
54
- 'limitations' => __( '<b class="h4">This function is not available in the Lite version of Bookly.</b><br><br>To get access to all Bookly features, lifetime free updates and 24/7 support, please upgrade to the Standard version of Bookly.<br>For more information visit', 'bookly' ) . ' <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
55
- ) );
56
-
57
- // Allow add-ons to enqueue their assets.
58
- Lib\Proxy\Shared::enqueueAssetsForServices();
59
-
60
- $this->render( 'index', $data );
61
- }
62
-
63
- /**
64
- *
65
- */
66
- public function executeGetCategoryServices()
67
- {
68
- wp_send_json_success( $this->render( '_list', $this->getCaSeStSpCollections(), false ) );
69
- }
70
-
71
- /**
72
- *
73
- */
74
- public function executeAddCategory()
75
- {
76
- $html = '';
77
- if ( ! empty ( $_POST ) ) {
78
- if ( $this->csrfTokenValid() ) {
79
- $form = new Forms\Category();
80
- $form->bind( $this->getPostParameters() );
81
- if ( $category = $form->save() ) {
82
- $html = $this->render( '_category_item', array( 'category' => $category->getFields() ), false );
83
- }
84
- }
85
- }
86
- wp_send_json_success( compact( 'html' ) );
87
- }
88
-
89
- /**
90
- * Update category.
91
- */
92
- public function executeUpdateCategory()
93
- {
94
- $form = new Forms\Category();
95
- $form->bind( $this->getPostParameters() );
96
- $form->save();
97
- }
98
-
99
- /**
100
- * Update category position.
101
- */
102
- public function executeUpdateCategoryPosition()
103
- {
104
- $category_sorts = $this->getParameter( 'position' );
105
- foreach ( $category_sorts as $position => $category_id ) {
106
- $category_sort = new Lib\Entities\Category();
107
- $category_sort->load( $category_id );
108
- $category_sort->setPosition( $position );
109
- $category_sort->save();
110
- }
111
- }
112
-
113
- /**
114
- * Update services position.
115
- */
116
- public function executeUpdateServicesPosition()
117
- {
118
- $services_sorts = $this->getParameter( 'position' );
119
- foreach ( $services_sorts as $position => $service_ids ) {
120
- $services_sort = new Lib\Entities\Service();
121
- $services_sort->load( $service_ids );
122
- $services_sort->setPosition( $position );
123
- $services_sort->save();
124
- }
125
- }
126
-
127
- /**
128
- * Reorder staff preferences for service
129
- */
130
- public function executeUpdateServiceStaffPreferenceOrders()
131
- {
132
- $service_id = $this->getParameter( 'service_id' );
133
- $positions = (array) $this->getParameter( 'positions' );
134
- /** @var Lib\Entities\StaffPreferenceOrder[] $staff_preferences */
135
- $staff_preferences = Lib\Entities\StaffPreferenceOrder::query()
136
- ->where( 'service_id', $service_id )
137
- ->indexBy( 'staff_id' )
138
- ->find();
139
- foreach ( $positions as $position => $staff_id ) {
140
- if ( array_key_exists( $staff_id, $staff_preferences ) ) {
141
- $staff_preferences[ $staff_id ]->setPosition( $position )->save();
142
- } else {
143
- $preference = new Lib\Entities\StaffPreferenceOrder();
144
- $preference
145
- ->setServiceId( $service_id )
146
- ->setStaffId( $staff_id )
147
- ->setPosition( $position )
148
- ->save();
149
- }
150
- }
151
-
152
- wp_send_json_success();
153
- }
154
-
155
- /**
156
- * Delete category.
157
- */
158
- public function executeDeleteCategory()
159
- {
160
- $category = new Lib\Entities\Category();
161
- $category->setId( $this->getParameter( 'id', 0 ) );
162
- $category->delete();
163
- }
164
-
165
- public function executeAddService()
166
- {
167
- $form = new Forms\Service();
168
- $form->bind( $this->getPostParameters() );
169
- $form->getObject()->setDuration( Lib\Config::getTimeSlotLength() );
170
- $service = $form->save();
171
- $data = $this->getCaSeStSpCollections( $service->getCategoryId() );
172
- Lib\Proxy\Shared::serviceCreated( $service, $this->getPostParameters() );
173
- wp_send_json_success( array( 'html' => $this->render( '_list', $data, false ), 'service_id' => $service->getId() ) );
174
- }
175
-
176
- public function executeRemoveServices()
177
- {
178
- $service_ids = $this->getParameter( 'service_ids', array() );
179
- if ( is_array( $service_ids ) && ! empty ( $service_ids ) ) {
180
- foreach ( $service_ids as $service_id ) {
181
- Lib\Proxy\Shared::serviceDeleted( $service_id );
182
- }
183
- Lib\Entities\Service::query( 's' )->delete()->whereIn( 's.id', $service_ids )->execute();
184
- }
185
- wp_send_json_success();
186
- }
187
-
188
- /**
189
- * Update service parameters and assign staff
190
- */
191
- public function executeUpdateService()
192
- {
193
- /** @var \wpdb $wpdb */
194
- global $wpdb;
195
-
196
- $form = new Forms\Service();
197
- $form->bind( $this->getPostParameters() );
198
- $service = $form->save();
199
-
200
- $staff_ids = $this->getParameter( 'staff_ids', array() );
201
- if ( empty ( $staff_ids ) ) {
202
- Lib\Entities\StaffService::query()->delete()->where( 'service_id', $service->getId() )->execute();
203
- } else {
204
- Lib\Entities\StaffService::query()->delete()->where( 'service_id', $service->getId() )->whereNotIn( 'staff_id', $staff_ids )->execute();
205
- if ( $service->getType() == Lib\Entities\Service::TYPE_SIMPLE ) {
206
- if ( $this->getParameter( 'update_staff', false ) ) {
207
- $data = array(
208
- 'price' => $this->getParameter( 'price' ),
209
- 'capacity_min' => 1,
210
- 'capacity_max' => 1,
211
- );
212
- $wpdb->update(
213
- Lib\Entities\StaffService::getTableName(),
214
- $data,
215
- array( 'service_id' => $this->getParameter( 'id' ) )
216
- );
217
- }
218
- // Create records for newly linked staff.
219
- $existing_staff_ids = array();
220
- $res = Lib\Entities\StaffService::query()
221
- ->select( 'staff_id' )
222
- ->where( 'service_id', $service->getId() )
223
- ->fetchArray();
224
- foreach ( $res as $staff ) {
225
- $existing_staff_ids[] = $staff['staff_id'];
226
- }
227
- foreach ( $staff_ids as $staff_id ) {
228
- if ( ! in_array( $staff_id, $existing_staff_ids ) ) {
229
- $staff_service = new Lib\Entities\StaffService();
230
- $staff_service->setStaffId( $staff_id )
231
- ->setServiceId( $service->getId() )
232
- ->setPrice( $service->getPrice() )
233
- ->setCapacityMin( 1 )
234
- ->setCapacityMax( 1 )
235
- ->save();
236
- }
237
- }
238
- }
239
- }
240
-
241
- // Update services in addons.
242
- $alert = Lib\Proxy\Shared::updateService( array( 'success' => array( __( 'Settings saved.', 'bookly' ) ) ), $service, $this->getPostParameters() );
243
-
244
- $price = Lib\Utils\Price::format( $service->getPrice() );
245
- $nice_duration = Lib\Utils\DateTime::secondsToInterval( $service->getDuration() );
246
- $title = $service->getTitle();
247
- $colors = array_fill( 0, 3, $service->getColor() );
248
- wp_send_json_success( Lib\Proxy\Shared::prepareUpdateServiceResponse( compact( 'title', 'price', 'colors', 'nice_duration', 'alert' ), $service, $this->getPostParameters() ) );
249
- }
250
-
251
- /**
252
- * Array for rendering service list.
253
- *
254
- * @param int $category_id
255
- * @return array
256
- */
257
- private function getCaSeStSpCollections( $category_id = 0 )
258
- {
259
- if ( ! $category_id ) {
260
- $category_id = $this->getParameter( 'category_id', 0 );
261
- }
262
-
263
- return array(
264
- 'service_collection' => $this->getServiceCollection( $category_id ),
265
- 'staff_collection' => $this->getStaffCollection(),
266
- 'category_collection' => $this->getCategoryCollection(),
267
- 'staff_preference' => array(
268
- Lib\Entities\Service::PREFERRED_ORDER => __( 'Specified order', 'bookly' ),
269
- Lib\Entities\Service::PREFERRED_LEAST_OCCUPIED => __( 'Least occupied that day', 'bookly' ),
270
- Lib\Entities\Service::PREFERRED_MOST_OCCUPIED => __( 'Most occupied that day', 'bookly' ),
271
- Lib\Entities\Service::PREFERRED_LEAST_EXPENSIVE => __( 'Least expensive', 'bookly' ),
272
- Lib\Entities\Service::PREFERRED_MOST_EXPENSIVE => __( 'Most expensive', 'bookly' ),
273
- ),
274
- );
275
- }
276
-
277
- /**
278
- * @return array
279
- */
280
- private function getCategoryCollection()
281
- {
282
- return Lib\Entities\Category::query()->sortBy( 'position' )->fetchArray();
283
- }
284
-
285
- /**
286
- * @return array
287
- */
288
- private function getStaffCollection()
289
- {
290
- return Lib\Entities\Staff::query()->fetchArray();
291
- }
292
-
293
- /**
294
- * @param int $id
295
- * @return array
296
- */
297
- private function getServiceCollection( $id = 0 )
298
- {
299
- $services = Lib\Entities\Service::query( 's' )
300
- ->select( 's.*, COUNT(staff.id) AS total_staff, GROUP_CONCAT(DISTINCT staff.id) AS staff_ids, GROUP_CONCAT(DISTINCT sp.staff_id ORDER BY sp.position ASC) AS pref_staff_ids' )
301
- ->leftJoin( 'StaffService', 'ss', 'ss.service_id = s.id' )
302
- ->leftJoin( 'StaffPreferenceOrder', 'sp', 'sp.service_id = s.id' )
303
- ->leftJoin( 'Staff', 'staff', 'staff.id = ss.staff_id' )
304
- ->whereRaw( 's.category_id = %d OR !%d', array( $id, $id ) )
305
- ->groupBy( 's.id' )
306
- ->indexBy( 'id' )
307
- ->sortBy( 's.position' );
308
- if ( ! Lib\Config::packagesActive() ) {
309
- $services->whereNot( 's.type', Lib\Entities\Service::TYPE_PACKAGE );
310
- }
311
- $result = $services->fetchArray();
312
- foreach ( $result as &$service ) {
313
- $service['sub_services'] = Lib\Entities\SubService::query()
314
- ->where( 'service_id', $service['id'] )
315
- ->sortBy( 'position' )
316
- ->fetchArray()
317
- ;
318
- $service['sub_services_count'] = array_sum( array_map( function ( $sub_service ) {
319
- return (int) ( $sub_service['type'] == Lib\Entities\SubService::TYPE_SERVICE );
320
- }, $service['sub_services'] ) );
321
- $service['colors'] = Lib\Proxy\Shared::prepareServiceColors( array_fill( 0, 3, $service['color'] ), $service['id'], $service['type'] );
322
- }
323
-
324
- return $result;
325
- }
326
-
327
- public function executeUpdateExtraPosition()
328
- {
329
- Lib\Proxy\ServiceExtras::reorder( $this->getParameter( 'position' ) );
330
- wp_send_json_success();
331
- }
332
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/services/Page.php ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Services;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Page
8
+ * @package Bookly\Backend\Modules\Services
9
+ */
10
+ class Page extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * Render page.
14
+ */
15
+ public static function render()
16
+ {
17
+ wp_enqueue_media();
18
+ self::enqueueStyles( array(
19
+ 'wp' => array( 'wp-color-picker' ),
20
+ 'frontend' => array( 'css/ladda.min.css' ),
21
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css' ),
22
+ ) );
23
+
24
+ self::enqueueScripts( array(
25
+ 'wp' => array( 'wp-color-picker' ),
26
+ 'backend' => array(
27
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
28
+ 'js/help.js' => array( 'jquery' ),
29
+ 'js/alert.js' => array( 'jquery' ),
30
+ 'js/range_tools.js' => array( 'jquery' ),
31
+ ),
32
+ 'module' => array( 'js/service.js' => array( 'jquery-ui-sortable', 'jquery' ) ),
33
+ 'frontend' => array(
34
+ 'js/spin.min.js' => array( 'jquery' ),
35
+ 'js/ladda.min.js' => array( 'bookly-spin.min.js', 'jquery' ),
36
+ )
37
+ ) );
38
+
39
+ $data = self::_getCaSeStCollections();
40
+ $staff = array();
41
+ foreach ( $data['staff_collection'] as $employee ) {
42
+ $staff[ $employee['id'] ] = $employee['full_name'];
43
+ }
44
+
45
+ wp_localize_script( 'bookly-service.js', 'BooklyL10n', array(
46
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
47
+ 'capacity_error' => __( 'Min capacity should not be greater than max capacity.', 'bookly' ),
48
+ 'are_you_sure' => __( 'Are you sure?', 'bookly' ),
49
+ 'service_special_day' => Lib\Config::specialDaysActive(),
50
+ 'reorder' => esc_attr__( 'Reorder', 'bookly' ),
51
+ 'staff' => $staff,
52
+ ) );
53
+
54
+ // Allow add-ons to enqueue their assets.
55
+ Proxy\Shared::enqueueAssetsForServices();
56
+
57
+ self::renderTemplate( 'index', $data );
58
+ }
59
+
60
+ /**
61
+ * Array for rendering service list.
62
+ *
63
+ * @param int $category_id
64
+ * @return array
65
+ */
66
+ protected static function _getCaSeStCollections( $category_id = 0 )
67
+ {
68
+ if ( ! $category_id ) {
69
+ $category_id = self::parameter( 'category_id', 0 );
70
+ }
71
+
72
+ return array(
73
+ 'service_collection' => self::_getServiceCollection( $category_id ),
74
+ 'staff_collection' => self::_getStaffCollection(),
75
+ 'category_collection' => self::_getCategoryCollection(),
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Get category collection.
81
+ *
82
+ * @return array
83
+ */
84
+ protected static function _getCategoryCollection()
85
+ {
86
+ return Lib\Entities\Category::query()->sortBy( 'position' )->fetchArray();
87
+ }
88
+
89
+ /**
90
+ * Get staff collection.
91
+ *
92
+ * @return array
93
+ */
94
+ protected static function _getStaffCollection()
95
+ {
96
+ return Lib\Entities\Staff::query()->fetchArray();
97
+ }
98
+
99
+ /**
100
+ * Get service collection.
101
+ *
102
+ * @param int $id
103
+ * @return array
104
+ */
105
+ protected static function _getServiceCollection( $id = 0 )
106
+ {
107
+ $services = Lib\Entities\Service::query( 's' )
108
+ ->select( 's.*, COUNT(staff.id) AS total_staff, GROUP_CONCAT(DISTINCT staff.id) AS staff_ids' )
109
+ ->leftJoin( 'StaffService', 'ss', 'ss.service_id = s.id' )
110
+ ->leftJoin( 'Staff', 'staff', 'staff.id = ss.staff_id' )
111
+ ->whereRaw( 's.category_id = %d OR !%d', array( $id, $id ) )
112
+ ->groupBy( 's.id' )
113
+ ->indexBy( 'id' )
114
+ ->sortBy( 's.position' );
115
+ if ( ! Lib\Config::packagesActive() ) {
116
+ $services->whereNot( 's.type', Lib\Entities\Service::TYPE_PACKAGE );
117
+ }
118
+ $result = $services->fetchArray();
119
+ foreach ( $result as &$service ) {
120
+ $service['sub_services'] = Lib\Entities\SubService::query()
121
+ ->where( 'service_id', $service['id'] )
122
+ ->sortBy( 'position' )
123
+ ->fetchArray()
124
+ ;
125
+ $service['sub_services_count'] = array_sum( array_map( function ( $sub_service ) {
126
+ return (int) ( $sub_service['type'] == Lib\Entities\SubService::TYPE_SERVICE );
127
+ }, $service['sub_services'] ) );
128
+ $service['colors'] = Proxy\Shared::prepareServiceColors( array_fill( 0, 3, $service['color'] ), $service['id'], $service['type'] );
129
+ }
130
+
131
+ return $result;
132
+ }
133
+ }
backend/modules/services/forms/Category.php CHANGED
@@ -1,13 +1,13 @@
1
  <?php
2
- namespace BooklyLite\Backend\Modules\Services\Forms;
3
 
4
- use BooklyLite\Lib;
5
 
6
  /**
7
  * Class Category
8
  * @method Lib\Entities\Category save()
9
  *
10
- * @package BooklyLite\Backend\Modules\Services\Forms
11
  */
12
  class Category extends Lib\Base\Form
13
  {
1
  <?php
2
+ namespace Bookly\Backend\Modules\Services\Forms;
3
 
4
+ use Bookly\Lib;
5
 
6
  /**
7
  * Class Category
8
  * @method Lib\Entities\Category save()
9
  *
10
+ * @package Bookly\Backend\Modules\Services\Forms
11
  */
12
  class Category extends Lib\Base\Form
13
  {
backend/modules/services/forms/Service.php CHANGED
@@ -1,13 +1,14 @@
1
  <?php
2
- namespace BooklyLite\Backend\Modules\Services\Forms;
3
 
4
- use BooklyLite\Lib;
 
5
 
6
  /**
7
  * Class Service
8
  * @method Lib\Entities\Service getObject
9
  *
10
- * @package BooklyLite\Backend\Modules\Services\Forms
11
  */
12
  class Service extends Lib\Base\Form
13
  {
@@ -28,6 +29,7 @@ class Service extends Lib\Base\Form
28
  'padding_right',
29
  'package_life_time',
30
  'package_size',
 
31
  'appointments_limit',
32
  'limit_period',
33
  'info',
@@ -40,6 +42,10 @@ class Service extends Lib\Base\Form
40
  'recurrence_frequencies',
41
  'visibility',
42
  'positions',
 
 
 
 
43
  );
44
 
45
  $this->setFields( $fields );
@@ -48,63 +54,40 @@ class Service extends Lib\Base\Form
48
  /**
49
  * Bind values to form.
50
  *
51
- * @param array $_post
52
  * @param array $files
53
  */
54
- public function bind( array $_post, array $files = array() )
55
  {
56
  // Field with NULL
57
- if ( array_key_exists( 'category_id', $_post ) && ! $_post['category_id'] ) {
58
- $_post['category_id'] = null;
59
  }
60
 
61
- parent::bind( $_post, $files );
62
  }
63
 
64
  /**
65
- * @return \BooklyLite\Lib\Entities\Service
66
  */
67
  public function save()
68
  {
69
  if ( $this->isNew() ) {
70
  // When adding new service - set its color randomly.
71
  $this->data['color'] = sprintf( '#%06X', mt_rand( 0, 0x64FFFF ) );
72
- }
73
-
74
- if ( $this->data['type'] == Lib\Entities\Service::TYPE_SIMPLE ) {
75
- Lib\Entities\SubService::query()->delete()->where( 'service_id', $this->data['id'] )->execute();
76
- }
77
-
78
- if ( $this->data['limit_period'] == 'off' || ! $this->data['appointments_limit'] ) {
79
- $this->data['appointments_limit'] = null;
80
- }
81
-
82
- $this->data = Lib\Proxy\Shared::prepareUpdateService( $this->data );
83
-
84
- /** @var Lib\Entities\Service $service */
85
- $service = parent::save();
86
-
87
- // Saving staff preferences for service
88
 
89
- /** @var Lib\Entities\StaffPreferenceOrder[] $staff_preferences */
90
- $staff_preferences = Lib\Entities\StaffPreferenceOrder::query()
91
- ->where( 'service_id', $service->getId() )
92
- ->indexBy( 'staff_id' )
93
- ->find();
94
- foreach ( (array) $this->data['positions'] as $position => $staff_id ) {
95
- if ( array_key_exists( $staff_id, $staff_preferences ) ) {
96
- $staff_preferences[ $staff_id ]->setPosition( $position )->save();
97
- } else {
98
- $preference = new Lib\Entities\StaffPreferenceOrder();
99
- $preference
100
- ->setServiceId( $service->getId() )
101
- ->setStaffId( $staff_id )
102
- ->setPosition( $position )
103
- ->save();
104
  }
 
 
105
  }
106
 
107
- return $service;
108
  }
109
 
110
  }
1
  <?php
2
+ namespace Bookly\Backend\Modules\Services\Forms;
3
 
4
+ use Bookly\Lib;
5
+ use Bookly\Backend\Modules\Services\Proxy;
6
 
7
  /**
8
  * Class Service
9
  * @method Lib\Entities\Service getObject
10
  *
11
+ * @package Bookly\Backend\Modules\Services\Forms
12
  */
13
  class Service extends Lib\Base\Form
14
  {
29
  'padding_right',
30
  'package_life_time',
31
  'package_size',
32
+ 'package_unassigned',
33
  'appointments_limit',
34
  'limit_period',
35
  'info',
42
  'recurrence_frequencies',
43
  'visibility',
44
  'positions',
45
+ 'taxes',
46
+ 'unit_duration',
47
+ 'units_min',
48
+ 'units_max',
49
  );
50
 
51
  $this->setFields( $fields );
54
  /**
55
  * Bind values to form.
56
  *
57
+ * @param array $params
58
  * @param array $files
59
  */
60
+ public function bind( array $params, array $files = array() )
61
  {
62
  // Field with NULL
63
+ if ( array_key_exists( 'category_id', $params ) && ! $params['category_id'] ) {
64
+ $params['category_id'] = null;
65
  }
66
 
67
+ parent::bind( $params, $files );
68
  }
69
 
70
  /**
71
+ * @return \Bookly\Lib\Entities\Service
72
  */
73
  public function save()
74
  {
75
  if ( $this->isNew() ) {
76
  // When adding new service - set its color randomly.
77
  $this->data['color'] = sprintf( '#%06X', mt_rand( 0, 0x64FFFF ) );
78
+ } else {
79
+ if ( $this->data['type'] == Lib\Entities\Service::TYPE_SIMPLE ) {
80
+ Lib\Entities\SubService::query()->delete()->where( 'service_id', $this->data['id'] )->execute();
81
+ }
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
+ if ( $this->data['limit_period'] == 'off' || ! $this->data['appointments_limit'] ) {
84
+ $this->data['appointments_limit'] = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  }
86
+
87
+ $this->data = Proxy\Shared::prepareUpdateService( $this->data );
88
  }
89
 
90
+ return parent::save();
91
  }
92
 
93
  }
backend/modules/services/proxy/CompoundServices.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Services\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CompoundServices
8
+ * @package Bookly\Backend\Modules\Services\Proxy
9
+ *
10
+ * @method static void renderSubForm( array $service, array $service_collection ) Render compound services sub-form.
11
+ */
12
+ abstract class CompoundServices extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/services/proxy/CustomDuration.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Services\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CustomerGroups
8
+ * @package Bookly\Backend\Modules\Services\Proxy
9
+ *
10
+ * @method static array prepareServiceDurationOptions( array $options, array $service ) Add "Custom" option to service duration select.
11
+ * @method static void renderServiceDurationFields( array $service ) Render services duration(units) fields.
12
+ * @method static void renderServiceDurationHelp() Render services duration help tip.
13
+ * @method static void renderServicePriceLabel( $service_id ) Render service price label.
14
+ */
15
+ abstract class CustomDuration extends Lib\Base\Proxy
16
+ {
17
+
18
+ }
backend/modules/services/proxy/CustomerGroups.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Services\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CustomerGroups
8
+ * @package Bookly\Backend\Modules\Services\Proxy
9
+ *
10
+ * @method static void renderSubForm( array $service ) Render customer groups sub-form.
11
+ * @method static void renderVisibilityOption( array $service ) Render services visibility option 'based on groups'.
12
+ */
13
+ abstract class CustomerGroups extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/modules/services/proxy/GroupBooking.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Services\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class GroupBooking
8
+ * @package Bookly\Backend\Modules\Services\Proxy
9
+ *
10
+ * @method static void renderCapacity( array $service ) Render Capacity (min & max) controls on service settings.
11
+ */
12
+ abstract class GroupBooking extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/services/proxy/Packages.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Services\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Packages
8
+ * @package Bookly\Backend\Modules\Services\Proxy
9
+ *
10
+ * @method static void renderSubForm( array $service, array $service_collection )
11
+ */
12
+ abstract class Packages extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/services/proxy/Pro.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Services\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ *
9
+ * @package Bookly\Backend\Components\Service\Proxy
10
+ *
11
+ * @method static void renderLimitAppointmentsPerCustomer( array $service ) Render limit appointments rules per customer.
12
+ * @method static void renderPadding( array $service ) Render padding settings for service.
13
+ * @method static void renderStaffPreference( array $service ) Render staff preference rules "any" is selected.
14
+ * @method static void renderVisibility( array $service ) Render visibility option for service.
15
+ */
16
+ abstract class Pro extends Lib\Base\Proxy
17
+ {
18
+
19
+ }
backend/modules/services/proxy/Shared.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Services\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Shared
8
+ * @package Bookly\Backend\Modules\Services\Proxy
9
+ *
10
+ * @method static void enqueueAssetsForServices() Enqueue assets for page Services.
11
+ * @method static array prepareServiceColors( array $colors, int $service_id, int $service_type ) Prepare colors for service.
12
+ * @method static array prepareUpdateService( array $data ) Prepare update service settings in add-ons.
13
+ * @method static array prepareUpdateServiceResponse( array $response, Lib\Entities\Service $service, array $_post ) Prepare response for updated service.
14
+ * @method static void renderAfterServiceList( array $service_collection ) Render content after services forms.
15
+ * @method static void renderServiceForm( array $service ) Render content in service form.
16
+ * @method static void renderServiceFormHead( array $service ) Render top content in service form.
17
+ * @method static array serviceCreated( Lib\Entities\Service $service, array $_post ) Service created.
18
+ * @method static void serviceDeleted( int $service_id ) Service deleted.
19
+ * @method static array updateService( array $alert, Lib\Entities\Service $service, array $_post ) Update service settings in add-ons.
20
+ */
21
+ abstract class Shared extends Lib\Base\Proxy
22
+ {
23
+
24
+ }
backend/modules/services/proxy/Taxes.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Services\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Taxes
8
+ * @package Bookly\Backend\Modules\Services\Proxy
9
+ *
10
+ * @method static void renderSubForm( array $service ) Render taxes sub-form.
11
+ */
12
+ abstract class Taxes extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/services/resources/js/service.js CHANGED
@@ -1,10 +1,13 @@
1
  jQuery(function($) {
2
- var $no_result = $('#bookly-services-wrapper .no-result');
3
  // Remember user choice in the modal dialog.
4
- var update_staff_choice = null,
 
5
  $new_category_popover = $('#bookly-new-category'),
6
- $new_category_form = $('#new-category-form'),
7
- $new_category_name = $('#bookly-category-name');
 
 
 
8
 
9
  $new_category_popover.popover({
10
  html: true,
@@ -140,20 +143,28 @@ jQuery(function($) {
140
  data['category_id'] = selected_category_id;
141
  }
142
  $.post(ajaxurl, data, function(response) {
143
- refreshList(response.data.html, response.data.service_id);
 
 
 
 
144
  ladda.stop();
145
  });
146
  })
147
  // On click on 'Delete' button.
148
  .on('click', '#bookly-delete', function(e) {
149
- if (confirm(BooklyL10n.are_you_sure)) {
150
- var ladda = rangeTools.ladda(this);
151
-
152
- var $for_delete = $('.service-checker:checked'),
153
- data = { action: 'bookly_remove_services', csrf_token : BooklyL10n.csrf_token },
154
- services = [],
155
- $panels = [];
156
-
 
 
 
 
157
  $for_delete.each(function(){
158
  var panel = $(this).parents('.bookly-js-collapse');
159
  $panels.push(panel);
@@ -167,9 +178,29 @@ jQuery(function($) {
167
  }
168
  });
169
  data['service_ids[]'] = services;
170
- $.post(ajaxurl, data, function(response) {
171
- if (response.success) {
172
- ladda.stop();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  $.each($panels.reverse(), function (index) {
174
  $(this).delay(500 * index).fadeOut(200, function () {
175
  $(this).remove();
@@ -177,8 +208,11 @@ jQuery(function($) {
177
  });
178
  $(document.body).trigger( 'service.deleted', [ services ] );
179
  }
 
180
  });
181
- }
 
 
182
  })
183
 
184
  .on('change', 'input.bookly-check-all-entities, input.bookly-js-check-entity', function () {
@@ -189,9 +223,9 @@ jQuery(function($) {
189
  $container.find('.bookly-check-all-entities').prop('checked', $container.find('.bookly-js-check-entity:not(:checked)').length == 0);
190
  }
191
  var $form = $(this).closest('.panel.bookly-js-collapse'),
192
- service_id = $form.data('service-id'),
193
  service_type = $form.find('.bookly-js-service-type input[name="type"]:checked').val(),
194
- staff_index = $(this).closest('li').index() + 1;
195
  if (service_type == 'simple' && !$(this).is(':checked')) {
196
  $('#services_list .panel.bookly-js-collapse').each(function () {
197
  if ($(this).find('.bookly-js-service-type input[name="type"]:checked').val() == 'package' && $(this).find('.bookly-js-package-sub-service option:selected').val() == service_id) {
@@ -207,23 +241,37 @@ jQuery(function($) {
207
  });
208
  }
209
  updateSelectorButton($container);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  });
211
 
212
  // Modal window events.
213
- var $modal = $('#bookly-update-service-settings');
214
- $modal
215
  .on('click', '.bookly-yes', function() {
216
- $modal.modal('hide');
217
  if ( $('#bookly-remember-my-choice').prop('checked') ) {
218
  update_staff_choice = true;
219
  }
220
- submitServiceFrom($modal.data('input'),true);
221
  })
222
  .on('click', '.bookly-no', function() {
223
  if ( $('#bookly-remember-my-choice').prop('checked') ) {
224
  update_staff_choice = false;
225
  }
226
- submitServiceFrom($modal.data('input'),false);
227
  });
228
 
229
  function refreshList(response,service_id) {
@@ -252,19 +300,6 @@ jQuery(function($) {
252
  });
253
  }
254
 
255
- $('#services_list').on('change', '[name=capacity_min],[name=capacity_max]', function(){
256
- if ($(this).val() > 1) {
257
- booklyAlert({error: [BooklyL10n.limitations]});
258
- $(this).val('1').prop('readonly',true);
259
- }
260
- }).on('change', '[name=padding_left],[name=padding_right]', function(){
261
- if ($(this).val() > 0) {
262
- booklyAlert({error: [BooklyL10n.limitations]});
263
- $(this).val('0').prop('readonly', true);
264
- $(this).find('option:gt(0)').prop('disabled', true);
265
- }
266
- });
267
-
268
  function submitServiceFrom($form, update_staff) {
269
  $form.find('input[name=update_staff]').val(update_staff ? 1 : 0);
270
  $form.find('input[name=package_service_changed]').val($form.find('[name=package_service]').data('last_value') != $form.find('[name=package_service]').val() ? 1 : 0);
@@ -393,10 +428,9 @@ jQuery(function($) {
393
  function onCollapseInitServiceForm() {
394
  $('.panel-collapse').on('show.bs.collapse.bookly', function () {
395
  var $panel = $(this),
396
- $staff_preference = $panel.find('[name=staff_preference]'),
397
  $staff_list = $panel.find('.bookly-staff-list'),
398
  $staff_box = $panel.find('.bookly-preference-box');
399
-
400
  $staff_preference.on('change', function () {
401
  /** @see Service::PREFERRED_ORDER */
402
  if ($(this).val() == 'order' && $staff_list.html() == '') {
@@ -430,7 +464,7 @@ jQuery(function($) {
430
  });
431
 
432
  $panel
433
- .find('[name=duration]').on('change', function () {
434
  $panel.find('[name=start_time_info]').closest('.form-group').toggle($(this).val() >= 86400);
435
  }).trigger('change');
436
 
@@ -439,6 +473,15 @@ jQuery(function($) {
439
  checkCapacityError($(this).parents('.bookly-js-collapse'));
440
  });
441
 
 
 
 
 
 
 
 
 
 
442
  $panel
443
  .find('.ajax-service-send').on('click', function (e) {
444
  e.preventDefault();
@@ -452,7 +495,7 @@ jQuery(function($) {
452
  });
453
  }
454
  if (show_modal) {
455
- $modal.data('input', $form).modal('show');
456
  } else {
457
  submitServiceFrom($form, update_staff_choice);
458
  }
@@ -463,15 +506,21 @@ jQuery(function($) {
463
  $(this).parents('form').trigger('reset');
464
  var $color = $(this).parents('form').find('.wp-color-picker'),
465
  $panel = $(this).parents('.bookly-js-collapse');
 
 
 
 
 
466
  $staff_list.html('');
467
  $staff_preference.trigger('change');
468
  $color.val($color.data('last-color')).trigger('change');
469
  $panel.find('.parent-range-start').trigger('change');
470
  $panel.find('input[name=type]:checked').trigger('change');
471
- $.each($('.bookly-js-entity-selector-container',$panel), function () {
472
- updateSelectorButton($(this));
473
- });
474
- checkCapacityError($panel);
 
475
  });
476
 
477
  $panel
@@ -491,7 +540,7 @@ jQuery(function($) {
491
  $.ajax({
492
  type : 'POST',
493
  url : ajaxurl,
494
- data : {action: 'bookly_update_service_staff_preference_orders', service_id: $(this).data('service_id'), positions: positions, csrf_token: BooklyL10n.csrf_token}
495
  });
496
  }
497
  });
1
  jQuery(function($) {
 
2
  // Remember user choice in the modal dialog.
3
+ var update_staff_choice = null,
4
+ $no_result = $('#bookly-services-wrapper .no-result'),
5
  $new_category_popover = $('#bookly-new-category'),
6
+ $new_category_form = $('#new-category-form'),
7
+ $new_category_name = $('#bookly-category-name'),
8
+ $update_service_modal = $('#bookly-update-service-settings'),
9
+ $delete_cascade_modal = $('.bookly-js-delete-cascade-confirm')
10
+ ;
11
 
12
  $new_category_popover.popover({
13
  html: true,
143
  data['category_id'] = selected_category_id;
144
  }
145
  $.post(ajaxurl, data, function(response) {
146
+ if(response.success) {
147
+ refreshList(response.data.html, response.data.service_id);
148
+ } else {
149
+ booklyAlert({error: [response.data.message]});
150
+ }
151
  ladda.stop();
152
  });
153
  })
154
  // On click on 'Delete' button.
155
  .on('click', '#bookly-delete', function(e) {
156
+ e.preventDefault();
157
+ var data = {
158
+ action: 'bookly_remove_services',
159
+ csrf_token: BooklyL10n.csrf_token
160
+ },
161
+ services = [],
162
+ $panels = [],
163
+ $for_delete = $('.service-checker:checked'),
164
+ button = this;
165
+
166
+ var delete_services = function (ajaxurl, data) {
167
+ var ladda = rangeTools.ladda(button);
168
  $for_delete.each(function(){
169
  var panel = $(this).parents('.bookly-js-collapse');
170
  $panels.push(panel);
178
  }
179
  });
180
  data['service_ids[]'] = services;
181
+
182
+ $.post(ajaxurl, data, function (response) {
183
+ if (!response.success) {
184
+ switch (response.data.action) {
185
+ case 'show_modal':
186
+ $delete_cascade_modal
187
+ .modal('show');
188
+ $('.bookly-js-delete', $delete_cascade_modal).off().on('click', function () {
189
+ delete_services(ajaxurl, $.extend(data, {force_delete: true}));
190
+ $delete_cascade_modal.modal('hide');
191
+ });
192
+ $('.bookly-js-edit', $delete_cascade_modal).off().on('click', function () {
193
+ rangeTools.ladda(this);
194
+ window.location.href = response.data.filter_url;
195
+ });
196
+ break;
197
+ case 'confirm':
198
+ if (confirm(BooklyL10n.are_you_sure)) {
199
+ delete_services(ajaxurl, $.extend(data, {force_delete: true}));
200
+ }
201
+ break;
202
+ }
203
+ } else {
204
  $.each($panels.reverse(), function (index) {
205
  $(this).delay(500 * index).fadeOut(200, function () {
206
  $(this).remove();
208
  });
209
  $(document.body).trigger( 'service.deleted', [ services ] );
210
  }
211
+ ladda.stop();
212
  });
213
+ };
214
+
215
+ delete_services(ajaxurl, data);
216
  })
217
 
218
  .on('change', 'input.bookly-check-all-entities, input.bookly-js-check-entity', function () {
223
  $container.find('.bookly-check-all-entities').prop('checked', $container.find('.bookly-js-check-entity:not(:checked)').length == 0);
224
  }
225
  var $form = $(this).closest('.panel.bookly-js-collapse'),
226
+ service_id = $form.data('service-id'),
227
  service_type = $form.find('.bookly-js-service-type input[name="type"]:checked').val(),
228
+ staff_index = $(this).closest('li').index() + 1;
229
  if (service_type == 'simple' && !$(this).is(':checked')) {
230
  $('#services_list .panel.bookly-js-collapse').each(function () {
231
  if ($(this).find('.bookly-js-service-type input[name="type"]:checked').val() == 'package' && $(this).find('.bookly-js-package-sub-service option:selected').val() == service_id) {
241
  });
242
  }
243
  updateSelectorButton($container);
244
+ })
245
+
246
+ .on('change', '.bookly-js-service-unit-duration', function () {
247
+ var $service = $(this).closest('.panel'),
248
+ $custom_duration = $service.find('.bookly-js-service-unit-fields');
249
+ if ($(this).val() == 'custom') {
250
+ $service.find('.bookly-js-price-label').hide();
251
+ $service.find('.bookly-js-unit-price-label').show();
252
+ $custom_duration.show();
253
+ } else {
254
+ $service.find('.bookly-js-price-label').show();
255
+ $service.find('.bookly-js-unit-price-label').hide();
256
+ $custom_duration.find('[name="unit_duration"]').val($(this).val())
257
+ $custom_duration.hide();
258
+ }
259
  });
260
 
261
  // Modal window events.
262
+ $update_service_modal
 
263
  .on('click', '.bookly-yes', function() {
264
+ $update_service_modal.modal('hide');
265
  if ( $('#bookly-remember-my-choice').prop('checked') ) {
266
  update_staff_choice = true;
267
  }
268
+ submitServiceFrom($update_service_modal.data('input'),true);
269
  })
270
  .on('click', '.bookly-no', function() {
271
  if ( $('#bookly-remember-my-choice').prop('checked') ) {
272
  update_staff_choice = false;
273
  }
274
+ submitServiceFrom($update_service_modal.data('input'),false);
275
  });
276
 
277
  function refreshList(response,service_id) {
300
  });
301
  }
302
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  function submitServiceFrom($form, update_staff) {
304
  $form.find('input[name=update_staff]').val(update_staff ? 1 : 0);
305
  $form.find('input[name=package_service_changed]').val($form.find('[name=package_service]').data('last_value') != $form.find('[name=package_service]').val() ? 1 : 0);
428
  function onCollapseInitServiceForm() {
429
  $('.panel-collapse').on('show.bs.collapse.bookly', function () {
430
  var $panel = $(this),
431
+ $staff_preference = $panel.find('[name=staff_preference]:last-child'),
432
  $staff_list = $panel.find('.bookly-staff-list'),
433
  $staff_box = $panel.find('.bookly-preference-box');
 
434
  $staff_preference.on('change', function () {
435
  /** @see Service::PREFERRED_ORDER */
436
  if ($(this).val() == 'order' && $staff_list.html() == '') {
464
  });
465
 
466
  $panel
467
+ .find('[name=duration], [name=unit_duration]').on('change', function () {
468
  $panel.find('[name=start_time_info]').closest('.form-group').toggle($(this).val() >= 86400);
469
  }).trigger('change');
470
 
473
  checkCapacityError($(this).parents('.bookly-js-collapse'));
474
  });
475
 
476
+ $panel
477
+ .find('.bookly-js-visibility').on('change', function () {
478
+ if ($(this).val() == 'group') {
479
+ $panel.find('.bookly-js-groups-list').show();
480
+ } else {
481
+ $panel.find('.bookly-js-groups-list').hide();
482
+ }
483
+ });
484
+
485
  $panel
486
  .find('.ajax-service-send').on('click', function (e) {
487
  e.preventDefault();
495
  });
496
  }
497
  if (show_modal) {
498
+ $update_service_modal.data('input', $form).modal('show');
499
  } else {
500
  submitServiceFrom($form, update_staff_choice);
501
  }
506
  $(this).parents('form').trigger('reset');
507
  var $color = $(this).parents('form').find('.wp-color-picker'),
508
  $panel = $(this).parents('.bookly-js-collapse');
509
+ $.each($('.bookly-js-entity-selector-container',$panel), function () {
510
+ updateSelectorButton($(this));
511
+ });
512
+ checkCapacityError($panel);
513
+
514
  $staff_list.html('');
515
  $staff_preference.trigger('change');
516
  $color.val($color.data('last-color')).trigger('change');
517
  $panel.find('.parent-range-start').trigger('change');
518
  $panel.find('input[name=type]:checked').trigger('change');
519
+ $panel.find('.bookly-js-visibility').trigger('change');
520
+ $panel.find('.bookly-js-service-unit-duration').trigger('change');
521
+ setTimeout(function () {
522
+ $(document.body).trigger('service.resetForm', [$panel, $panel.closest('.panel').data('service-id')]);
523
+ }, 50);
524
  });
525
 
526
  $panel
540
  $.ajax({
541
  type : 'POST',
542
  url : ajaxurl,
543
+ data : {action: 'bookly_pro_update_service_staff_preference_orders', service_id: $(this).data('service_id'), positions: positions, csrf_token: BooklyL10n.csrf_token}
544
  });
545
  }
546
  });
backend/modules/services/templates/_list.php CHANGED
@@ -1,11 +1,12 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Utils\Common;
3
- use BooklyLite\Lib\Utils\DateTime;
4
- use BooklyLite\Lib\Utils\Price;
5
- use BooklyLite\Lib\Proxy;
6
- use BooklyLite\Lib\Entities\Service;
 
 
7
 
8
- $time_interval = get_option( 'bookly_gen_time_slot_length' );
9
  ?>
10
  <?php if ( ! empty( $service_collection ) ) : ?>
11
  <div class="panel-group" id="services_list" role="tablist" aria-multiselectable="true">
@@ -27,9 +28,9 @@ $time_interval = get_option( 'bookly_gen_time_slot_length' );
27
  <span class="bookly-service-color bookly-margin-right-sm bookly-js-service bookly-js-service-simple bookly-js-service-compound bookly-js-service-package"
28
  style="background-color: <?php echo esc_attr( $service['colors'][0] == '-1' ? 'grey' : $service['colors'][0] ) ?>">&nbsp;</span>
29
  <span class="bookly-service-color bookly-margin-right-sm bookly-js-service bookly-js-service-compound bookly-js-service-package"
30
- style="background-color: <?php echo esc_attr( $service['colors'][1] == '-1' ? 'grey' : $service['colors'][1] ) ?>; <?php if ( $service['type'] == \BooklyLite\Lib\Entities\Service::TYPE_SIMPLE ) : ?>display: none;<?php endif ?>">&nbsp;</span>
31
  <span class="bookly-service-color bookly-margin-right-sm bookly-js-service bookly-js-service-package"
32
- style="background-color: <?php echo esc_attr( $service['colors'][2] == '-1' ? 'grey' : $service['colors'][2] ) ?>; <?php if ( $service['type'] != \BooklyLite\Lib\Entities\Service::TYPE_PACKAGE ) : ?>display: none;<?php endif ?>">&nbsp;</span>
33
  </div>
34
  <div class="bookly-flex-cell bookly-vertical-middle">
35
  <a role="button" class="panel-title collapsed bookly-js-service-title" data-toggle="collapse"
@@ -77,7 +78,7 @@ $time_interval = get_option( 'bookly_gen_time_slot_length' );
77
  <div class="form-group">
78
  <div class="radio">
79
  <label class="bookly-margin-right-md">
80
- <input type="radio" name="type" value="simple" data-panel-class="panel-default" <?php echo checked( $service['type'] == \BooklyLite\Lib\Entities\Service::TYPE_SIMPLE ) ?>><?php _e( 'Simple', 'bookly' ) ?>
81
  </label>
82
  </div>
83
  </div>
@@ -94,43 +95,31 @@ $time_interval = get_option( 'bookly_gen_time_slot_length' );
94
  <div class="form-group">
95
  <label><?php _e( 'Color', 'bookly' ) ?></label>
96
  <div class="bookly-color-picker-wrapper">
97
- <input name="color" value="<?php echo esc_attr( $service['color'] ) ?>" class="bookly-js-color-picker" data-last-color="<?php echo esc_attr( $service['color'] ) ?>" type="text">
98
  </div>
99
  </div>
100
  </div>
101
  </div>
102
- <?php Proxy\Packages::renderServicePackage( $service, $service_collection ) ?>
103
  <div class="row">
 
 
104
  <div class="col-sm-4 bookly-js-service bookly-js-service-simple bookly-js-service-compound bookly-js-service-package">
105
  <div class="form-group">
106
- <label for="visibility_<?php echo $service_id ?>"><?php _e( 'Visibility', 'bookly' ) ?></label>
107
- <p class="help-block"><?php _e( 'To make service invisible to your customers set the visibility to "Private".', 'bookly' ) ?></p>
108
- <select name="visibility" class="form-control" id="visibility_<?php echo $service_id ?>">
109
- <option value="public" <?php selected( $service['visibility'], 'public' ) ?>><?php _e( 'Public', 'bookly' ) ?></option>
110
- <option value="private" <?php selected( $service['visibility'], 'private' ) ?>><?php _e( 'Private', 'bookly' ) ?></option>
111
- </select>
112
- </div>
113
- </div>
114
- <div class="col-sm-4 bookly-js-service bookly-js-service-simple bookly-js-service-compound bookly-js-service-package">
115
- <div class="form-group">
116
- <label for="price_<?php echo $service_id ?>"><?php _e( 'Price', 'bookly' ) ?></label>
117
- <input id="price_<?php echo $service_id ?>" class="form-control bookly-question" type="number" min="0" step="1" name="price" value="<?php echo esc_attr( $service['price'] ) ?>">
118
- </div>
119
- </div>
120
- <div class="col-sm-4 bookly-js-service bookly-js-service-simple">
121
- <div class="form-group">
122
- <div class="row">
123
- <div class="col-xs-6">
124
- <input id="capacity_min_<?php echo $service_id ?>" class="form-control bookly-question bookly-js-capacity" type="hidden" min="1" step="1" name="capacity_min" value="1">
125
- </div>
126
- <div class="col-xs-6">
127
- <input id="capacity_max_<?php echo $service_id ?>" class="form-control bookly-question bookly-js-capacity" type="hidden" min="1" step="1" name="capacity_max" value="1">
128
- </div>
129
- </div>
130
  </div>
131
  </div>
 
 
 
132
  </div>
133
 
 
 
 
 
134
  <div class="bookly-js-service bookly-js-service-simple">
135
  <div class="row">
136
  <div class="col-sm-4 bookly-js-service bookly-js-service-simple">
@@ -138,70 +127,45 @@ $time_interval = get_option( 'bookly_gen_time_slot_length' );
138
  <label for="duration_<?php echo $service_id ?>">
139
  <?php _e( 'Duration', 'bookly' ) ?>
140
  </label>
141
- <select id="duration_<?php echo $service_id ?>" class="form-control" name="duration">
142
- <?php for ( $j = $time_interval; $j <= 720; $j += $time_interval ) : ?><?php if ( $service['duration'] / 60 > $j - $time_interval && $service['duration'] / 60 < $j ) : ?><option value="<?php echo esc_attr( $service['duration'] ) ?>" selected><?php echo DateTime::secondsToInterval( $service['duration'] ) ?></option><?php endif ?><option value="<?php echo $j * 60 ?>" <?php selected( $service['duration'], $j * 60 ) ?>><?php echo DateTime::secondsToInterval( $j * 60 ) ?></option><?php endfor ?>
143
- <?php for ( $j = 86400; $j <= 604800; $j += 86400 ) : ?><option value="<?php echo $j ?>" <?php selected( $service['duration'], $j ) ?>><?php echo DateTime::secondsToInterval( $j ) ?></option><?php endfor ?>
 
 
 
 
144
  </select>
145
  </div>
146
  </div>
 
 
 
 
 
 
 
 
 
147
  <div class="col-sm-8 bookly-js-service bookly-js-service-simple">
148
- <div class="form-group">
149
- <label for="padding_left_<?php echo $service_id ?>">
150
- <?php _e( 'Padding time (before and after)', 'bookly' ) ?>
151
- </label>
152
- <p class="help-block"><?php _e( 'Set padding time before and/or after an appointment. For example, if you require 15 minutes to prepare for the next appointment then you should set "padding before" to 15 min. If there is an appointment from 8:00 to 9:00 then the next available time slot will be 9:15 rather than 9:00.', 'bookly' ) ?></p>
153
- <div class="row">
154
- <div class="col-xs-6">
155
- <select id="padding_left_<?php echo $service_id ?>" class="form-control" name="padding_left">
156
- <option value="0"><?php _e( 'OFF', 'bookly' ) ?></option>
157
- <?php for ( $j = $time_interval; $j <= 1440; $j += $time_interval ) : ?><?php if ( $service['padding_left'] > 0 && $service['padding_left'] / 60 > $j - $time_interval && $service['padding_left'] / 60 < $j ) : ?><option value="<?php echo esc_attr( $service['padding_left'] ) ?>" selected><?php echo DateTime::secondsToInterval( $service['padding_left'] ) ?></option><?php endif ?><option value="<?php echo $j * 60 ?>" <?php selected( $service['padding_left'], $j * 60 ) ?>><?php echo DateTime::secondsToInterval( $j * 60 ) ?></option><?php endfor ?>
158
- </select>
159
- </div>
160
- <div class="col-xs-6">
161
- <select id="padding_right_<?php echo $service_id ?>" class="form-control" name="padding_right">
162
- <option value="0"><?php _e( 'OFF', 'bookly' ) ?></option>
163
- <?php for ( $j = $time_interval; $j <= 1440; $j += $time_interval ) : ?><?php if ( $service['padding_right'] > 0 && $service['padding_right'] / 60 > $j - $time_interval && $service['padding_right'] / 60 < $j ) : ?><option value="<?php echo esc_attr( $service['padding_right'] ) ?>" selected><?php echo DateTime::secondsToInterval( $service['padding_right'] ) ?></option><?php endif ?><option value="<?php echo $j * 60 ?>" <?php selected( $service['padding_right'], $j * 60 ) ?>><?php echo DateTime::secondsToInterval( $j * 60 ) ?></option><?php endfor ?>
164
- </select>
165
- </div>
166
- </div>
167
- </div>
168
- </div>
169
- <div class="col-sm-4 bookly-js-service bookly-js-service-simple">
170
  <div class="form-group">
171
  <label for="start_time_info_<?php echo $service_id ?>"><?php _e( 'Start and end times of the appointment', 'bookly' ) ?></label>
172
- <p class="help-block"><?php _e( 'Allows to set the start and end times for an appointment for services with the duration of 1 day or longer. This time will be displayed in notifications to customers.', 'bookly' ) ?></p>
173
  <div class="row">
174
  <div class="col-xs-6">
175
- <input id="start_time_info_<?php echo $service_id ?>" class="form-control" type="text" name="start_time_info" value="<?php echo esc_attr( $service['start_time_info'] ) ?>">
176
  </div>
177
  <div class="col-xs-6">
178
- <input class="form-control" type="text" name="end_time_info" value="<?php echo esc_attr( $service['end_time_info'] ) ?>">
179
  </div>
180
  </div>
181
  </div>
182
  </div>
183
  </div>
184
  </div>
185
-
186
  <div class="bookly-js-service bookly-js-service-simple">
187
  <div class="row">
188
- <div class="col-sm-4 bookly-js-service bookly-js-service-simple">
189
- <div class="form-group">
190
- <label for="staff_preference_<?php echo $service_id ?>">
191
- <?php _e( 'Providers preference for ANY', 'bookly' ) ?>
192
- </label>
193
- <p class="help-block"><?php _e( 'Allows you to define the rule of staff members auto assignment when ANY option is selected', 'bookly' ) ?></p>
194
- <select id="staff_preference_<?php echo $service_id ?>" class="form-control" name="staff_preference" data-default="[<?php echo $service['pref_staff_ids'] ?>]">
195
- <?php foreach ( $staff_preference as $rule => $name ) : ?><option value="<?php echo $rule ?>" <?php selected( $rule == $service['staff_preference'] ) ?>><?php echo $name ?></option><?php endforeach ?>
196
- </select>
197
- </div>
198
- </div>
199
- <div class="col-sm-8 bookly-preference-box">
200
- <div class="form-group">
201
- <label for="staff_preferred_<?php echo $service_id ?>"><?php _e( 'Providers', 'bookly' ) ?></label><br/>
202
- <div class="bookly-staff-list" data-service_id="<?php echo $service_id ?>"></div>
203
- </div>
204
- </div>
205
  </div>
206
  </div>
207
 
@@ -253,36 +217,9 @@ $time_interval = get_option( 'bookly_gen_time_slot_length' );
253
  </div>
254
  </div>
255
  </div>
256
-
257
- <div class="bookly-js-service bookly-js-service-simple bookly-js-service-compound">
258
- <div class="row">
259
- <div class="col-sm-8">
260
- <label for="appointments_limit_<?php echo $service_id ?>">
261
- <?php _e( 'Limit appointments per customer', 'bookly' ) ?>
262
- </label>
263
- <p class="help-block"><?php _e( 'Allows you to limit the frequency of service bookings per customer.', 'bookly' ) ?></p>
264
- <div class="row">
265
- <div class="col-sm-6">
266
- <div class="form-group">
267
- <input id="appointments_limit_<?php echo $service_id ?>" class="form-control" type="number" min="0" step="1" name="appointments_limit" value="<?php echo esc_attr( $service['appointments_limit'] ) ?>">
268
- </div>
269
- </div>
270
- <div class="col-sm-6">
271
- <div class="form-group">
272
- <select id="limit_period_<?php echo $service_id ?>" class="form-control" name="limit_period">
273
- <option value="off"><?php _e( 'OFF', 'bookly' ) ?></option>
274
- <option value="day"<?php selected( 'day', $service['limit_period'] ) ?>><?php _e( 'per day', 'bookly' ) ?></option>
275
- <option value="week"<?php selected( 'week', $service['limit_period'] ) ?>><?php _e( 'per week', 'bookly' ) ?></option>
276
- <option value="month"<?php selected( 'month', $service['limit_period'] ) ?>><?php _e( 'per month', 'bookly' ) ?></option>
277
- <option value="year"<?php selected( 'year', $service['limit_period'] ) ?>><?php _e( 'per year', 'bookly' ) ?></option>
278
- </select>
279
- </div>
280
- </div>
281
- </div>
282
- </div>
283
- </div>
284
- </div>
285
-
286
  <div class="form-group bookly-js-service bookly-js-service-simple bookly-js-service-compound bookly-js-service-package">
287
  <label for="info_<?php echo $service_id ?>">
288
  <?php _e( 'Info', 'bookly' ) ?>
@@ -293,16 +230,18 @@ $time_interval = get_option( 'bookly_gen_time_slot_length' );
293
  <textarea class="form-control" id="info_<?php echo $service_id ?>" name="info" rows="3" type="text"><?php echo esc_textarea( $service['info'] ) ?></textarea>
294
  </div>
295
 
296
- <?php Proxy\CompoundServices::renderSubServices( $service, $service_collection ) ?>
 
297
  <?php Proxy\Shared::renderServiceForm( $service ) ?>
 
298
  <div class="panel-footer">
299
  <input type="hidden" name="action" value="bookly_update_service">
300
  <input type="hidden" name="id" value="<?php echo esc_html( $service_id ) ?>">
301
  <input type="hidden" name="update_staff" value="0">
302
  <span class="bookly-js-services-error text-danger"></span>
303
- <?php Common::csrf() ?>
304
- <?php Common::submitButton( null, 'ajax-service-send' ) ?>
305
- <?php Common::resetButton( null, 'js-reset' ) ?>
306
  </div>
307
  </form>
308
  </div>
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
+ use Bookly\Backend\Modules\Services\Proxy;
5
+ use Bookly\Lib\Utils\Common;
6
+ use Bookly\Lib\Utils\DateTime;
7
+ use Bookly\Lib\Utils\Price;
8
+ use Bookly\Lib\Entities\Service;
9
 
 
10
  ?>
11
  <?php if ( ! empty( $service_collection ) ) : ?>
12
  <div class="panel-group" id="services_list" role="tablist" aria-multiselectable="true">
28
  <span class="bookly-service-color bookly-margin-right-sm bookly-js-service bookly-js-service-simple bookly-js-service-compound bookly-js-service-package"
29
  style="background-color: <?php echo esc_attr( $service['colors'][0] == '-1' ? 'grey' : $service['colors'][0] ) ?>">&nbsp;</span>
30
  <span class="bookly-service-color bookly-margin-right-sm bookly-js-service bookly-js-service-compound bookly-js-service-package"
31
+ style="background-color: <?php echo esc_attr( $service['colors'][1] == '-1' ? 'grey' : $service['colors'][1] ) ?>; <?php if ( $service['type'] == Service::TYPE_SIMPLE ) : ?>display: none;<?php endif ?>">&nbsp;</span>
32
  <span class="bookly-service-color bookly-margin-right-sm bookly-js-service bookly-js-service-package"
33
+ style="background-color: <?php echo esc_attr( $service['colors'][2] == '-1' ? 'grey' : $service['colors'][2] ) ?>; <?php if ( $service['type'] != Service::TYPE_PACKAGE ) : ?>display: none;<?php endif ?>">&nbsp;</span>
34
  </div>
35
  <div class="bookly-flex-cell bookly-vertical-middle">
36
  <a role="button" class="panel-title collapsed bookly-js-service-title" data-toggle="collapse"
78
  <div class="form-group">
79
  <div class="radio">
80
  <label class="bookly-margin-right-md">
81
+ <input type="radio" name="type" value="simple" data-panel-class="panel-default" <?php echo checked( $service['type'] == Service::TYPE_SIMPLE ) ?>><?php _e( 'Simple', 'bookly' ) ?>
82
  </label>
83
  </div>
84
  </div>
95
  <div class="form-group">
96
  <label><?php _e( 'Color', 'bookly' ) ?></label>
97
  <div class="bookly-color-picker-wrapper">
98
+ <input name="color" value="<?php echo esc_attr( $service['color'] ) ?>" class="bookly-js-color-picker" data-last-color="<?php echo esc_attr( $service['color'] ) ?>" type="text" />
99
  </div>
100
  </div>
101
  </div>
102
  </div>
103
+ <?php Proxy\Packages::renderSubForm( $service, $service_collection ) ?>
104
  <div class="row">
105
+ <input type="hidden" name="visibility" value="public">
106
+ <?php Proxy\Pro::renderVisibility( $service ) ?>
107
  <div class="col-sm-4 bookly-js-service bookly-js-service-simple bookly-js-service-compound bookly-js-service-package">
108
  <div class="form-group">
109
+ <label for="price_<?php echo $service_id ?>" class="bookly-js-price-label"><?php _e( 'Price', 'bookly' ) ?></label>
110
+ <?php Proxy\CustomDuration::renderServicePriceLabel( $service_id ) ?>
111
+ <input id="price_<?php echo $service_id ?>" class="form-control bookly-question" type="number" min="0" step="1" name="price" value="<?php echo esc_attr( $service['price'] ) ?>" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  </div>
113
  </div>
114
+ <input type="hidden" name="capacity_min" value="1">
115
+ <input type="hidden" name="capacity_max" value="1">
116
+ <?php Proxy\GroupBooking::renderCapacity( $service ) ?>
117
  </div>
118
 
119
+ <?php Proxy\Taxes::renderSubForm( $service ) ?>
120
+
121
+ <?php Proxy\CustomerGroups::renderSubForm( $service ) ?>
122
+
123
  <div class="bookly-js-service bookly-js-service-simple">
124
  <div class="row">
125
  <div class="col-sm-4 bookly-js-service bookly-js-service-simple">
127
  <label for="duration_<?php echo $service_id ?>">
128
  <?php _e( 'Duration', 'bookly' ) ?>
129
  </label>
130
+ <?php Proxy\CustomDuration::renderServiceDurationHelp() ?>
131
+ <?php
132
+ $options = Common::getDurationSelectOptions( $service['duration'] );
133
+ $options = Proxy\CustomDuration::prepareServiceDurationOptions( $options, $service );
134
+ ?>
135
+ <select id="duration_<?php echo $service_id ?>" class="bookly-js-service-unit-duration form-control" name="duration">
136
+ <?php foreach ( $options as $option ): ?><option value="<?php echo $option['value']; ?>" <?php echo $option['selected']; ?>><?php echo $option['label']; ?></option><?php endforeach; ?>
137
  </select>
138
  </div>
139
  </div>
140
+
141
+ <input type="hidden" name="padding_left" value="0">
142
+ <input type="hidden" name="padding_right" value="0">
143
+ <?php Proxy\Pro::renderPadding( $service ) ?>
144
+ </div>
145
+ <?php Proxy\CustomDuration::renderServiceDurationFields( $service ); ?>
146
+ </div>
147
+ <div class="bookly-js-service bookly-js-service-simple">
148
+ <div class="row">
149
  <div class="col-sm-8 bookly-js-service bookly-js-service-simple">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  <div class="form-group">
151
  <label for="start_time_info_<?php echo $service_id ?>"><?php _e( 'Start and end times of the appointment', 'bookly' ) ?></label>
152
+ <p class="help-block"><?php _e( 'Allows to set the start and end times for an appointment for services with the duration of 1 day or longer. This time will be displayed in notifications to customers, backend calendar and codes for booking form.', 'bookly' ) ?></p>
153
  <div class="row">
154
  <div class="col-xs-6">
155
+ <input id="start_time_info_<?php echo $service_id ?>" class="form-control" type="text" name="start_time_info" value="<?php echo esc_attr( $service['start_time_info'] ) ?>" />
156
  </div>
157
  <div class="col-xs-6">
158
+ <input class="form-control" type="text" name="end_time_info" value="<?php echo esc_attr( $service['end_time_info'] ) ?>" />
159
  </div>
160
  </div>
161
  </div>
162
  </div>
163
  </div>
164
  </div>
 
165
  <div class="bookly-js-service bookly-js-service-simple">
166
  <div class="row">
167
+ <input type="hidden" name="staff_preference" value="<?php echo Service::PREFERRED_LEAST_EXPENSIVE ?>" data-default="[]">
168
+ <?php Proxy\Pro::renderStaffPreference( $service ) ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  </div>
170
  </div>
171
 
217
  </div>
218
  </div>
219
  </div>
220
+ <input type="hidden" name="appointments_limit" value="">
221
+ <input type="hidden" name="limit_period" value="off">
222
+ <?php Proxy\Pro::renderLimitAppointmentsPerCustomer( $service ) ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  <div class="form-group bookly-js-service bookly-js-service-simple bookly-js-service-compound bookly-js-service-package">
224
  <label for="info_<?php echo $service_id ?>">
225
  <?php _e( 'Info', 'bookly' ) ?>
230
  <textarea class="form-control" id="info_<?php echo $service_id ?>" name="info" rows="3" type="text"><?php echo esc_textarea( $service['info'] ) ?></textarea>
231
  </div>
232
 
233
+ <?php Proxy\CompoundServices::renderSubForm( $service, $service_collection ) ?>
234
+
235
  <?php Proxy\Shared::renderServiceForm( $service ) ?>
236
+
237
  <div class="panel-footer">
238
  <input type="hidden" name="action" value="bookly_update_service">
239
  <input type="hidden" name="id" value="<?php echo esc_html( $service_id ) ?>">
240
  <input type="hidden" name="update_staff" value="0">
241
  <span class="bookly-js-services-error text-danger"></span>
242
+ <?php Inputs::renderCsrf() ?>
243
+ <?php Buttons::renderSubmit( null, 'ajax-service-send' ) ?>
244
+ <?php Buttons::renderReset( null, 'js-reset' ) ?>
245
  </div>
246
  </form>
247
  </div>
backend/modules/services/templates/index.php CHANGED
@@ -1,11 +1,16 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
 
 
 
2
  <div id="bookly-tbs" class="wrap">
3
  <div class="bookly-tbs-body">
4
  <div class="page-header text-right clearfix">
5
  <div class="bookly-page-title">
6
  <?php _e( 'Services', 'bookly' ) ?>
7
  </div>
8
- <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( $this::page_slug ) ?>
9
  </div>
10
  <div class="row">
11
  <div id="bookly-sidebar" class="col-sm-4">
@@ -14,7 +19,7 @@
14
  <div class="bookly-padding-vertical-xs"><?php _e( 'All Services', 'bookly' ) ?></div>
15
  </div>
16
  <ul id="bookly-category-item-list">
17
- <?php foreach ( $category_collection as $category ) $this->render( '_category_item', compact( 'category' ) ) ?>
18
  </ul>
19
  </div>
20
 
@@ -32,7 +37,7 @@
32
  <label for="bookly-category-name"><?php _e( 'Name', 'bookly' ) ?></label>
33
  <input class="form-control" id="bookly-category-name" type="text" name="name" />
34
  <input type="hidden" name="action" value="bookly_add_category" />
35
- <?php \BooklyLite\Lib\Utils\Common::csrf() ?>
36
  </div>
37
  </div>
38
 
@@ -66,7 +71,7 @@
66
  <?php include '_list.php' ?>
67
  </div>
68
  <div class="text-right">
69
- <?php \BooklyLite\Lib\Utils\Common::deleteButton() ?>
70
  </div>
71
  </div>
72
  </div>
@@ -99,4 +104,5 @@
99
  </div>
100
  </div>
101
  </div>
 
102
  </div>
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
+ use Bookly\Backend\Components\Dialogs;
5
+ use Bookly\Backend\Components\Support;
6
+ ?>
7
  <div id="bookly-tbs" class="wrap">
8
  <div class="bookly-tbs-body">
9
  <div class="page-header text-right clearfix">
10
  <div class="bookly-page-title">
11
  <?php _e( 'Services', 'bookly' ) ?>
12
  </div>
13
+ <?php Support\Buttons::render( $self::pageSlug() ) ?>
14
  </div>
15
  <div class="row">
16
  <div id="bookly-sidebar" class="col-sm-4">
19
  <div class="bookly-padding-vertical-xs"><?php _e( 'All Services', 'bookly' ) ?></div>
20
  </div>
21
  <ul id="bookly-category-item-list">
22
+ <?php foreach ( $category_collection as $category ) $self::renderTemplate( '_category_item', compact( 'category' ) ) ?>
23
  </ul>
24
  </div>
25
 
37
  <label for="bookly-category-name"><?php _e( 'Name', 'bookly' ) ?></label>
38
  <input class="form-control" id="bookly-category-name" type="text" name="name" />
39
  <input type="hidden" name="action" value="bookly_add_category" />
40
+ <?php Inputs::renderCsrf() ?>
41
  </div>
42
  </div>
43
 
71
  <?php include '_list.php' ?>
72
  </div>
73
  <div class="text-right">
74
+ <?php Buttons::renderDelete() ?>
75
  </div>
76
  </div>
77
  </div>
104
  </div>
105
  </div>
106
  </div>
107
+ <?php Dialogs\Common\CascadeDelete::render() ?>
108
  </div>
backend/modules/settings/Ajax.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Settings;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Ajax
8
+ * @package Bookly\Backend\Modules\Settings
9
+ */
10
+ class Ajax extends Page
11
+ {
12
+ /**
13
+ * Ajax request for Holidays calendar
14
+ */
15
+ public static function settingsHoliday()
16
+ {
17
+ $id = self::parameter( 'id', false );
18
+ $day = self::parameter( 'day', false );
19
+ $holiday = self::parameter( 'holiday' ) == 'true';
20
+ $repeat = (int) ( self::parameter( 'repeat' ) == 'true' );
21
+
22
+ // update or delete the event
23
+ if ( $id ) {
24
+ if ( $holiday ) {
25
+ Lib\Entities\Holiday::query()
26
+ ->update()
27
+ ->set( 'repeat_event', $repeat )
28
+ ->where( 'id', $id )
29
+ ->where( 'parent_id', $id , 'OR' )
30
+ ->execute();
31
+ } else {
32
+ Lib\Entities\Holiday::query()
33
+ ->delete()
34
+ ->where( 'id', $id )
35
+ ->where( 'parent_id', $id, 'OR' )
36
+ ->execute();
37
+ }
38
+ // add the new event
39
+ } elseif ( $holiday && $day ) {
40
+ $holiday = new Lib\Entities\Holiday( );
41
+ $holiday
42
+ ->setDate( $day )
43
+ ->setRepeatEvent( $repeat )
44
+ ->save();
45
+ foreach ( Lib\Entities\Staff::query()->fetchArray() as $employee ) {
46
+ $staff_holiday = new Lib\Entities\Holiday();
47
+ $staff_holiday
48
+ ->setDate( $day)
49
+ ->setRepeatEvent( $repeat )
50
+ ->setStaffId( $employee['id'] )
51
+ ->setParent( $holiday )
52
+ ->save();
53
+ }
54
+ }
55
+
56
+ // and return refreshed events
57
+ echo json_encode( self::_getHolidays() );
58
+ exit;
59
+ }
60
+ }
backend/modules/settings/Components.php DELETED
@@ -1,30 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Settings;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Components
8
- * @package BooklyLite\Backend\Modules\Support
9
- */
10
- class Components extends Lib\Base\Components
11
- {
12
- /**
13
- * Render collect stats notice and marks it as showed for every user
14
- */
15
- public function renderCollectStatsNotice()
16
- {
17
- if ( Lib\Utils\Common::isCurrentUserAdmin() &&
18
- get_option( 'bookly_gen_collect_stats' ) == '0' &&
19
- ! (int) get_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_collect_stats_notice', true )
20
- ) {
21
- $this->enqueueStyles( array(
22
- 'frontend' => array( 'css/ladda.min.css', ),
23
- ) );
24
- $this->enqueueScripts( array(
25
- 'module' => array( 'js/collect-stats-notice.js' => array( 'jquery' ), ),
26
- ) );
27
- $this->render( '_collect_stats_notice' );
28
- }
29
- }
30
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/Controller.php DELETED
@@ -1,299 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Settings;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Settings
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- const page_slug = 'bookly-settings';
13
-
14
- public function index()
15
- {
16
- /** @var \WP_Locale $wp_locale */
17
- global $wp_locale;
18
-
19
- wp_enqueue_media();
20
- $this->enqueueStyles( array(
21
- 'frontend' => array( 'css/ladda.min.css' ),
22
- 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', )
23
- ) );
24
-
25
- $this->enqueueScripts( array(
26
- 'backend' => array(
27
- 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
28
- 'js/jCal.js' => array( 'jquery' ),
29
- 'js/alert.js' => array( 'jquery' ),
30
- ),
31
- 'module' => array( 'js/settings.js' => array( 'jquery', 'bookly-intlTelInput.min.js', 'jquery-ui-sortable' ) ),
32
- 'frontend' => array(
33
- 'js/intlTelInput.min.js' => array( 'jquery' ),
34
- 'js/spin.min.js' => array( 'jquery' ),
35
- 'js/ladda.min.js' => array( 'jquery' ),
36
- )
37
- ) );
38
-
39
- $current_tab = $this->hasParameter( 'tab' ) ? $this->getParameter( 'tab' ) : 'general';
40
- $alert = array( 'success' => array(), 'error' => array() );
41
-
42
- // Save the settings.
43
- if ( ! empty ( $_POST ) ) {
44
- if ( $this->csrfTokenValid() ) {
45
- switch ( $this->getParameter( 'tab' ) ) {
46
- case 'calendar': // Calendar form.
47
- update_option( 'bookly_cal_one_participant', $this->getParameter( 'bookly_cal_one_participant' ) );
48
- update_option( 'bookly_cal_many_participants', $this->getParameter( 'bookly_cal_many_participants' ) );
49
- $alert['success'][] = __( 'Settings saved.', 'bookly' );
50
- break;
51
- case 'payments': // Payments form.
52
- $form = new Forms\Payments();
53
- break;
54
- case 'business_hours': // Business hours form.
55
- $form = new Forms\BusinessHours();
56
- break;
57
- case 'purchase_code': // Purchase Code form.
58
- break;
59
- case 'general': // General form.
60
- $bookly_gen_time_slot_length = $this->getParameter( 'bookly_gen_time_slot_length' );
61
- if ( in_array( $bookly_gen_time_slot_length, array( 5, 10, 12, 15, 20, 30, 45, 60, 90, 120, 180, 240, 360 ) ) ) {
62
- update_option( 'bookly_gen_time_slot_length', $bookly_gen_time_slot_length );
63
- }
64
- update_option( 'bookly_gen_lite_uninstall_remove_bookly_data', (int) $this->getParameter( 'bookly_gen_lite_uninstall_remove_bookly_data' ) );
65
- update_option( 'bookly_gen_service_duration_as_slot_length', (int) $this->getParameter( 'bookly_gen_service_duration_as_slot_length' ) );
66
- update_option( 'bookly_gen_allow_staff_edit_profile', (int) $this->getParameter( 'bookly_gen_allow_staff_edit_profile' ) );
67
- update_option( 'bookly_gen_default_appointment_status', $this->getParameter( 'bookly_gen_default_appointment_status' ) );
68
- update_option( 'bookly_gen_link_assets_method', $this->getParameter( 'bookly_gen_link_assets_method' ) );
69
- update_option( 'bookly_gen_max_days_for_booking', (int) $this->getParameter( 'bookly_gen_max_days_for_booking' ) );
70
- update_option( 'bookly_gen_min_time_prior_booking', $this->getParameter( 'bookly_gen_min_time_prior_booking' ) );
71
- update_option( 'bookly_gen_min_time_prior_cancel', $this->getParameter( 'bookly_gen_min_time_prior_cancel' ) );
72
- update_option( 'bookly_gen_use_client_time_zone', (int) $this->getParameter( 'bookly_gen_use_client_time_zone' ) );
73
- update_option( 'bookly_gen_collect_stats', $this->getParameter( 'bookly_gen_collect_stats' ) );
74
- $alert['success'][] = __( 'Settings saved.', 'bookly' );
75
- break;
76
- case 'url': // URL settings form.
77
- update_option( 'bookly_url_approve_page_url', $this->getParameter( 'bookly_url_approve_page_url' ) );
78
- update_option( 'bookly_url_approve_denied_page_url', $this->getParameter( 'bookly_url_approve_denied_page_url' ) );
79
- update_option( 'bookly_url_cancel_page_url', $this->getParameter( 'bookly_url_cancel_page_url' ) );
80
- update_option( 'bookly_url_cancel_denied_page_url', $this->getParameter( 'bookly_url_cancel_denied_page_url' ) );
81
- update_option( 'bookly_url_cancel_confirm_page_url', $this->getParameter( 'bookly_url_cancel_confirm_page_url' ) );
82
- update_option( 'bookly_url_reject_denied_page_url', $this->getParameter( 'bookly_url_reject_denied_page_url' ) );
83
- update_option( 'bookly_url_reject_page_url', $this->getParameter( 'bookly_url_reject_page_url' ) );
84
- update_option( 'bookly_url_final_step_url', '' );
85
- $alert['success'][] = __( 'Settings saved.', 'bookly' );
86
- break;
87
- case 'google_calendar': // Google calendar form.
88
- update_option( 'bookly_gc_client_id', '' );
89
- $alert['success'][] = __( 'Settings saved.', 'bookly' );
90
- break;
91
- case 'customers': // Customers form.
92
- update_option( 'bookly_cst_cancel_action', $this->getParameter( 'bookly_cst_cancel_action' ) );
93
- update_option( 'bookly_cst_combined_notifications', $this->getParameter( 'bookly_cst_combined_notifications' ) );
94
- update_option( 'bookly_cst_create_account', $this->getParameter( 'bookly_cst_create_account' ) );
95
- update_option( 'bookly_cst_default_country_code', $this->getParameter( 'bookly_cst_default_country_code' ) );
96
- update_option( 'bookly_cst_new_account_role', $this->getParameter( 'bookly_cst_new_account_role' ) );
97
- update_option( 'bookly_cst_phone_default_country', $this->getParameter( 'bookly_cst_phone_default_country' ) );
98
- update_option( 'bookly_cst_remember_in_cookie', $this->getParameter( 'bookly_cst_remember_in_cookie' ) );
99
- update_option( 'bookly_cst_show_update_details_dialog', $this->getParameter( 'bookly_cst_show_update_details_dialog' ) );
100
- $alert['success'][] = __( 'Settings saved.', 'bookly' );
101
- break;
102
- case 'woo_commerce': // WooCommerce form.
103
- update_option( 'bookly_wc_enabled', '0' );
104
- $alert['success'][] = __( 'Settings saved.', 'bookly' );
105
- break;
106
- case 'cart': // Cart form.
107
- update_option( 'bookly_cart_show_columns', $this->getParameter( 'bookly_cart_show_columns', array() ) );
108
- update_option( 'bookly_cart_enabled', '0' );
109
- $alert['success'][] = __( 'Settings saved.', 'bookly' );
110
- if ( get_option( 'bookly_wc_enabled' ) && $this->getParameter( 'bookly_cart_enabled' ) ) {
111
- $alert['error'][] = sprintf( __( 'To use the cart, disable integration with WooCommerce <a href="%s">here</a>.', 'bookly' ), Lib\Utils\Common::escAdminUrl( self::page_slug, array( 'tab' => 'woocommerce' ) ) );
112
- }
113
- break;
114
- case 'company': // Company form.
115
- update_option( 'bookly_co_address', $this->getParameter( 'bookly_co_address' ) );
116
- update_option( 'bookly_co_logo_attachment_id', $this->getParameter( 'bookly_co_logo_attachment_id' ) );
117
- update_option( 'bookly_co_name', $this->getParameter( 'bookly_co_name' ) );
118
- update_option( 'bookly_co_phone', $this->getParameter( 'bookly_co_phone' ) );
119
- update_option( 'bookly_co_website', $this->getParameter( 'bookly_co_website' ) );
120
- $alert['success'][] = __( 'Settings saved.', 'bookly' );
121
- break;
122
- }
123
-
124
- // Let Add-ons save their settings.
125
- $alert = Lib\Proxy\Shared::saveSettings( $alert, $this->getParameter( 'tab' ), $this->getPostParameters() );
126
-
127
- if ( in_array( $this->getParameter( 'tab' ), array( 'payments', 'business_hours' ) ) ) {
128
- $form->bind( $this->getPostParameters(), $_FILES );
129
- $form->save();
130
-
131
- $alert['success'][] = __( 'Settings saved.', 'bookly' );
132
- }
133
- }
134
- }
135
-
136
- $candidates = $this->getCandidatesBooklyProduct();
137
-
138
- // Check if WooCommerce cart exists.
139
- if ( get_option( 'bookly_wc_enabled' ) && class_exists( 'WooCommerce', false ) ) {
140
- $post = get_post( wc_get_page_id( 'cart' ) );
141
- if ( $post === null || $post->post_status != 'publish' ) {
142
- $alert['error'][] = sprintf(
143
- __( 'WooCommerce cart is not set up. Follow the <a href="%s">link</a> to correct this problem.', 'bookly' ),
144
- Lib\Utils\Common::escAdminUrl( 'wc-status', array( 'tab' => 'tools' ) )
145
- );
146
- }
147
- }
148
- $cart_columns = array(
149
- 'service' => Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_service' ),
150
- 'date' => __( 'Date', 'bookly' ),
151
- 'time' => __( 'Time', 'bookly' ),
152
- 'employee' => Lib\Utils\Common::getTranslatedOption( 'bookly_l10n_label_employee' ),
153
- 'price' => __( 'Price', 'bookly' ),
154
- 'deposit' => __( 'Deposit', 'bookly' ),
155
- );
156
-
157
- wp_localize_script( 'bookly-jCal.js', 'BooklyL10n', array(
158
- 'alert' => $alert,
159
- 'current_tab' => $current_tab,
160
- 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
161
- 'default_country' => get_option( 'bookly_cst_phone_default_country' ),
162
- 'holidays' => $this->getHolidays(),
163
- 'loading_img' => plugins_url( 'bookly-responsive-appointment-booking-tool/backend/resources/images/loading.gif' ),
164
- 'start_of_week' => get_option( 'start_of_week' ),
165
- 'days' => array_values( $wp_locale->weekday_abbrev ),
166
- 'months' => array_values( $wp_locale->month ),
167
- 'close' => __( 'Close', 'bookly' ),
168
- 'repeat' => __( 'Repeat every year', 'bookly' ),
169
- 'we_are_not_working' => __( 'We are not working on this day', 'bookly' ),
170
- 'sample_price' => number_format_i18n( 10, 3 ),
171
- 'limitations' => __( '<b class="h4">This function is not available in the Lite version of Bookly.</b><br><br>To get access to all Bookly features, lifetime free updates and 24/7 support, please upgrade to the Standard version of Bookly.<br>For more information visit', 'bookly' ) . ' <a href="http://booking-wp-plugin.com" target="_blank" class="alert-link">http://booking-wp-plugin.com</a>',
172
- ) );
173
- $values = array(
174
- 'bookly_gc_limit_events' => array( array( '0', __( 'Disabled', 'bookly' ) ), array( 25, 25 ), array( 50, 50 ), array( 100, 100 ), array( 250, 250 ), array( 500, 500 ), array( 1000, 1000 ), array( 2500, 2500 ) ),
175
- 'bookly_gen_min_time_prior_booking' => array( array( '0', __( 'Disabled', 'bookly' ) ) ),
176
- 'bookly_gen_min_time_prior_cancel' => array( array( '0', __( 'Disabled', 'bookly' ) ) ),
177
- );
178
- $wp_roles = new \WP_Roles();
179
- foreach ( $wp_roles->get_names() as $role => $name ) {
180
- $values['bookly_cst_new_account_role'][] = array( $role, $name );
181
- }
182
- foreach ( array( 5, 10, 12, 15, 20, 30, 45, 60, 90, 120, 180, 240, 360 ) as $duration ) {
183
- $values['bookly_gen_time_slot_length'][] = array( $duration, Lib\Utils\DateTime::secondsToInterval( $duration * MINUTE_IN_SECONDS ) );
184
- }
185
- foreach ( array_merge( array( 0.5 ), range( 1, 12 ), range( 24, 144, 24 ), range( 168, 672, 168 ) ) as $hour ) {
186
- $values['bookly_gen_min_time_prior_booking'][] = array( $hour, Lib\Utils\DateTime::secondsToInterval( $hour * HOUR_IN_SECONDS ) );
187
- }
188
- foreach ( array_merge( array( 1 ), range( 2, 12, 2 ), range( 24, 168, 24 ) ) as $hour ) {
189
- $values['bookly_gen_min_time_prior_cancel'][] = array( $hour, Lib\Utils\DateTime::secondsToInterval( $hour * HOUR_IN_SECONDS ) );
190
- }
191
-
192
- $this->render( 'index', compact( 'candidates', 'cart_columns', 'values' ) );
193
- }
194
-
195
- /**
196
- * Ajax request for Holidays calendar
197
- */
198
- public function executeSettingsHoliday()
199
- {
200
- global $wpdb;
201
-
202
- $id = $this->getParameter( 'id', false );
203
- $day = $this->getParameter( 'day', false );
204
- $holiday = $this->getParameter( 'holiday' ) == 'true';
205
- $repeat = (int) ( $this->getParameter( 'repeat' ) == 'true' );
206
-
207
- // update or delete the event
208
- if ( $id ) {
209
- if ( $holiday ) {
210
- $wpdb->update( Lib\Entities\Holiday::getTableName(), array( 'repeat_event' => $repeat ), array( 'id' => $id ), array( '%d' ) );
211
- $wpdb->update( Lib\Entities\Holiday::getTableName(), array( 'repeat_event' => $repeat ), array( 'parent_id' => $id ), array( '%d' ) );
212
- } else {
213
- Lib\Entities\Holiday::query()->delete()->where( 'id', $id )->where( 'parent_id', $id, 'OR' )->execute();
214
- }
215
- // add the new event
216
- } elseif ( $holiday && $day ) {
217
- $holiday = new Lib\Entities\Holiday( );
218
- $holiday
219
- ->setDate( $day )
220
- ->setRepeatEvent( $repeat )
221
- ->save();
222
- foreach ( Lib\Entities\Staff::query()->fetchArray() as $employee ) {
223
- $staff_holiday = new Lib\Entities\Holiday();
224
- $staff_holiday
225
- ->setDate( $day)
226
- ->setRepeatEvent( $repeat )
227
- ->setStaffId( $employee['id'] )
228
- ->setParent( $holiday )
229
- ->save();
230
- }
231
- }
232
-
233
- // and return refreshed events
234
- echo json_encode( $this->getHolidays() );
235
- exit;
236
- }
237
-
238
- /**
239
- * Dismiss collect stats notice.
240
- */
241
- public function executeDismissCollectStatsNotice()
242
- {
243
- update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_collect_stats_notice', 1 );
244
- update_option( 'bookly_gen_collect_stats', '0' );
245
-
246
- wp_send_json_success();
247
- }
248
-
249
- public function executeAllowCollectStats()
250
- {
251
- update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_collect_stats_notice', 1 );
252
- update_option( 'bookly_gen_collect_stats', '1' );
253
-
254
- wp_send_json_success();
255
- }
256
-
257
-
258
- /**
259
- * @return string
260
- */
261
- protected function getHolidays()
262
- {
263
- $collection = Lib\Entities\Holiday::query()->where( 'staff_id', null )->fetchArray();
264
- $holidays = array();
265
- if ( count( $collection ) ) {
266
- foreach ( $collection as $holiday ) {
267
- $holidays[ $holiday['id'] ] = array(
268
- 'm' => (int) date( 'm', strtotime( $holiday['date'] ) ),
269
- 'd' => (int) date( 'd', strtotime( $holiday['date'] ) ),
270
- );
271
- // If not repeated holiday, add the year
272
- if ( ! $holiday['repeat_event'] ) {
273
- $holidays[ $holiday['id'] ]['y'] = (int) date( 'Y', strtotime( $holiday['date'] ) );
274
- }
275
- }
276
- }
277
-
278
- return $holidays;
279
- }
280
-
281
- /**
282
- * @return array
283
- */
284
- protected function getCandidatesBooklyProduct()
285
- {
286
- /** @global \wpdb $wpdb */
287
- global $wpdb;
288
-
289
- $goods = array( array( 'id' => 0, 'name' => __( 'Select product', 'bookly' ) ) );
290
- $query = 'SELECT ID, post_title FROM ' . $wpdb->posts . ' WHERE post_type = \'product\' AND post_status = \'publish\' ORDER BY post_title';
291
- $products = $wpdb->get_results( $query );
292
-
293
- foreach ( $products as $product ) {
294
- $goods[] = array( 'id' => $product->ID, 'name' => $product->post_title );
295
- }
296
-
297
- return $goods;
298
- }
299
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/Page.php ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Settings;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Page
8
+ * @package Bookly\Backend\Modules\Settings
9
+ */
10
+ class Page extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * Render page.
14
+ */
15
+ public static function render()
16
+ {
17
+ /** @var \WP_Locale $wp_locale */
18
+ global $wp_locale;
19
+
20
+ wp_enqueue_media();
21
+ self::enqueueStyles( array(
22
+ 'frontend' => array( 'css/ladda.min.css' ),
23
+ 'backend' => array( 'bootstrap/css/bootstrap-theme.min.css', )
24
+ ) );
25
+
26
+ self::enqueueScripts( array(
27
+ 'backend' => array(
28
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
29
+ 'js/jCal.js' => array( 'jquery' ),
30
+ 'js/alert.js' => array( 'jquery' ),
31
+ ),
32
+ 'module' => array( 'js/settings.js' => array( 'jquery', 'bookly-intlTelInput.min.js', 'jquery-ui-sortable' ) ),
33
+ 'frontend' => array(
34
+ 'js/intlTelInput.min.js' => array( 'jquery' ),
35
+ 'js/spin.min.js' => array( 'jquery' ),
36
+ 'js/ladda.min.js' => array( 'jquery' ),
37
+ )
38
+ ) );
39
+
40
+ $current_tab = self::hasParameter( 'tab' ) ? self::parameter( 'tab' ) : 'general';
41
+ $alert = array( 'success' => array(), 'error' => array() );
42
+
43
+ // Save the settings.
44
+ if ( ! empty ( $_POST ) ) {
45
+ if ( self::csrfTokenValid() ) {
46
+ switch ( self::parameter( 'tab' ) ) {
47
+ case 'calendar': // Calendar form.
48
+ update_option( 'bookly_cal_one_participant', self::parameter( 'bookly_cal_one_participant' ) );
49
+ update_option( 'bookly_cal_many_participants', self::parameter( 'bookly_cal_many_participants' ) );
50
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
51
+ break;
52
+ case 'payments': // Payments form.
53
+ update_option( 'bookly_pmt_order', self::parameter( 'bookly_pmt_order' ) );
54
+ update_option( 'bookly_pmt_currency', self::parameter( 'bookly_pmt_currency' ) );
55
+ update_option( 'bookly_pmt_price_format', self::parameter( 'bookly_pmt_price_format' ) );
56
+ update_option( 'bookly_pmt_local', self::parameter( 'bookly_pmt_local' ) );
57
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
58
+ break;
59
+ case 'business_hours': // Business hours form.
60
+ $form = new Forms\BusinessHours();
61
+ $form->bind( self::postParameters(), $_FILES );
62
+ $form->save();
63
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
64
+ break;
65
+ case 'general': // General form.
66
+ $bookly_gen_time_slot_length = self::parameter( 'bookly_gen_time_slot_length' );
67
+ if ( in_array( $bookly_gen_time_slot_length, array( 5, 10, 12, 15, 20, 30, 45, 60, 90, 120, 180, 240, 360 ) ) ) {
68
+ update_option( 'bookly_gen_time_slot_length', $bookly_gen_time_slot_length );
69
+ }
70
+ update_option( 'bookly_gen_service_duration_as_slot_length', (int) self::parameter( 'bookly_gen_service_duration_as_slot_length' ) );
71
+ update_option( 'bookly_gen_allow_staff_edit_profile', (int) self::parameter( 'bookly_gen_allow_staff_edit_profile' ) );
72
+ update_option( 'bookly_gen_default_appointment_status', self::parameter( 'bookly_gen_default_appointment_status' ) );
73
+ update_option( 'bookly_gen_link_assets_method', self::parameter( 'bookly_gen_link_assets_method' ) );
74
+ update_option( 'bookly_gen_max_days_for_booking', (int) self::parameter( 'bookly_gen_max_days_for_booking' ) );
75
+ update_option( 'bookly_gen_use_client_time_zone', (int) self::parameter( 'bookly_gen_use_client_time_zone' ) );
76
+ update_option( 'bookly_gen_collect_stats', self::parameter( 'bookly_gen_collect_stats' ) );
77
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
78
+ break;
79
+ case 'url': // URL settings form.
80
+ update_option( 'bookly_url_approve_page_url', self::parameter( 'bookly_url_approve_page_url' ) );
81
+ update_option( 'bookly_url_approve_denied_page_url', self::parameter( 'bookly_url_approve_denied_page_url' ) );
82
+ update_option( 'bookly_url_cancel_page_url', self::parameter( 'bookly_url_cancel_page_url' ) );
83
+ update_option( 'bookly_url_cancel_denied_page_url', self::parameter( 'bookly_url_cancel_denied_page_url' ) );
84
+ update_option( 'bookly_url_reject_denied_page_url', self::parameter( 'bookly_url_reject_denied_page_url' ) );
85
+ update_option( 'bookly_url_reject_page_url', self::parameter( 'bookly_url_reject_page_url' ) );
86
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
87
+ break;
88
+ case 'customers': // Customers form.
89
+ update_option( 'bookly_cst_allow_duplicates', self::parameter( 'bookly_cst_allow_duplicates' ) );
90
+ update_option( 'bookly_cst_combined_notifications', self::parameter( 'bookly_cst_combined_notifications' ) );
91
+ update_option( 'bookly_cst_default_country_code', self::parameter( 'bookly_cst_default_country_code' ) );
92
+ update_option( 'bookly_cst_phone_default_country', self::parameter( 'bookly_cst_phone_default_country' ) );
93
+ update_option( 'bookly_cst_remember_in_cookie', self::parameter( 'bookly_cst_remember_in_cookie' ) );
94
+ update_option( 'bookly_cst_show_update_details_dialog', self::parameter( 'bookly_cst_show_update_details_dialog' ) );
95
+ // Update email required option if creating wordpress account for customers
96
+ $bookly_cst_required_details = get_option( 'bookly_cst_required_details', array() );
97
+ if ( self::parameter( 'bookly_cst_create_account' ) && ! in_array( 'email', $bookly_cst_required_details ) ) {
98
+ $bookly_cst_required_details[] = 'email';
99
+ update_option( 'bookly_cst_required_details', $bookly_cst_required_details );
100
+ }
101
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
102
+ break;
103
+ case 'company': // Company form.
104
+ update_option( 'bookly_co_address', self::parameter( 'bookly_co_address' ) );
105
+ update_option( 'bookly_co_logo_attachment_id', self::parameter( 'bookly_co_logo_attachment_id' ) );
106
+ update_option( 'bookly_co_name', self::parameter( 'bookly_co_name' ) );
107
+ update_option( 'bookly_co_phone', self::parameter( 'bookly_co_phone' ) );
108
+ update_option( 'bookly_co_website', self::parameter( 'bookly_co_website' ) );
109
+ $alert['success'][] = __( 'Settings saved.', 'bookly' );
110
+ break;
111
+ }
112
+
113
+ // Let Add-ons save their settings.
114
+ $alert = Proxy\Shared::saveSettings( $alert, self::parameter( 'tab' ), self::postParameters() );
115
+ }
116
+ }
117
+
118
+ // Check if WooCommerce cart exists.
119
+ if ( get_option( 'bookly_wc_enabled' ) && class_exists( 'WooCommerce', false ) ) {
120
+ $post = get_post( wc_get_page_id( 'cart' ) );
121
+ if ( $post === null || $post->post_status != 'publish' ) {
122
+ $alert['error'][] = sprintf(
123
+ __( 'WooCommerce cart is not set up. Follow the <a href="%s">link</a> to correct this problem.', 'bookly' ),
124
+ Lib\Utils\Common::escAdminUrl( 'wc-status', array( 'tab' => 'tools' ) )
125
+ );
126
+ }
127
+ }
128
+
129
+ wp_localize_script( 'bookly-jCal.js', 'BooklyL10n', array(
130
+ 'alert' => $alert,
131
+ 'current_tab' => $current_tab,
132
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
133
+ 'default_country' => get_option( 'bookly_cst_phone_default_country' ),
134
+ 'holidays' => self::_getHolidays(),
135
+ 'loading_img' => plugins_url( 'bookly-responsive-appointment-booking-tool/backend/resources/images/loading.gif' ),
136
+ 'start_of_week' => get_option( 'start_of_week' ),
137
+ 'days' => array_values( $wp_locale->weekday_abbrev ),
138
+ 'months' => array_values( $wp_locale->month ),
139
+ 'close' => __( 'Close', 'bookly' ),
140
+ 'repeat' => __( 'Repeat every year', 'bookly' ),
141
+ 'we_are_not_working' => __( 'We are not working on this day', 'bookly' ),
142
+ 'sample_price' => number_format_i18n( 10, 3 ),
143
+ ) );
144
+ $values = array();
145
+ foreach ( array( 5, 10, 12, 15, 20, 30, 45, 60, 90, 120, 180, 240, 360 ) as $duration ) {
146
+ $values['bookly_gen_time_slot_length'][] = array( $duration, Lib\Utils\DateTime::secondsToInterval( $duration * MINUTE_IN_SECONDS ) );
147
+ }
148
+
149
+ // Payments tab
150
+ $payments = array();
151
+ $payment_data = array(
152
+ 'local' => self::renderTemplate( '_payment_local', array(), false ),
153
+ );
154
+ $payment_data = Proxy\Shared::preparePaymentGatewaySettings( $payment_data );
155
+ $order = explode( ',', get_option( 'bookly_pmt_order' ) );
156
+ foreach ( $order as $payment_system ) {
157
+ if ( array_key_exists( $payment_system, $payment_data ) ) {
158
+ $payments[] = $payment_data[ $payment_system ];
159
+ }
160
+ }
161
+ foreach ( $payment_data as $slug => $data ) {
162
+ if ( ! $order || ! in_array( $slug, $order ) ) {
163
+ $payments[] = $data;
164
+ }
165
+ }
166
+
167
+ self::renderTemplate( 'index', compact( 'values', 'payments' ) );
168
+ }
169
+
170
+ /**
171
+ * Get holidays.
172
+ *
173
+ * @return array
174
+ */
175
+ protected static function _getHolidays()
176
+ {
177
+ $collection = Lib\Entities\Holiday::query()->where( 'staff_id', null )->fetchArray();
178
+ $holidays = array();
179
+ if ( count( $collection ) ) {
180
+ foreach ( $collection as $holiday ) {
181
+ $holidays[ $holiday['id'] ] = array(
182
+ 'm' => (int) date( 'm', strtotime( $holiday['date'] ) ),
183
+ 'd' => (int) date( 'd', strtotime( $holiday['date'] ) ),
184
+ );
185
+ // If not repeated holiday, add the year
186
+ if ( ! $holiday['repeat_event'] ) {
187
+ $holidays[ $holiday['id'] ]['y'] = (int) date( 'Y', strtotime( $holiday['date'] ) );
188
+ }
189
+ }
190
+ }
191
+
192
+ return $holidays;
193
+ }
194
+ }
backend/modules/settings/forms/BusinessHours.php CHANGED
@@ -1,11 +1,11 @@
1
  <?php
2
- namespace BooklyLite\Backend\Modules\Settings\Forms;
3
 
4
- use BooklyLite\Lib;
5
 
6
  /**
7
  * Class BusinessHours
8
- * @package BooklyLite\Backend\Modules\Settings\Forms
9
  */
10
  class BusinessHours extends Lib\Base\Form
11
  {
1
  <?php
2
+ namespace Bookly\Backend\Modules\Settings\Forms;
3
 
4
+ use Bookly\Lib;
5
 
6
  /**
7
  * Class BusinessHours
8
+ * @package Bookly\Backend\Modules\Settings\Forms
9
  */
10
  class BusinessHours extends Lib\Base\Form
11
  {
backend/modules/settings/forms/Payments.php DELETED
@@ -1,37 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Settings\Forms;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Payments
8
- * @package BooklyLite\Backend\Modules\Settings
9
- */
10
- class Payments extends Lib\Base\Form
11
- {
12
- public function __construct()
13
- {
14
- }
15
-
16
- public function bind( array $_post, array $files = array() )
17
- {
18
- $fields = Lib\Proxy\Shared::preparePaymentOptions( array(
19
- 'bookly_pmt_currency',
20
- 'bookly_pmt_price_format',
21
- 'bookly_pmt_local',
22
- ) );
23
-
24
- $_post = Lib\Proxy\Shared::preparePaymentOptionsData( $_post );
25
-
26
- $this->setFields( $fields );
27
- parent::bind( $_post, $files );
28
- }
29
-
30
- public function save()
31
- {
32
- foreach ( $this->data as $field => $value ) {
33
- update_option( $field, $value );
34
- }
35
- }
36
-
37
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/proxy/AdvancedGoogleCalendar.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Settings\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class AdvancedGoogleCalendar
8
+ * @package Bookly\Backend\Modules\Settings\Proxy
9
+ *
10
+ * @method static array preSaveSettings( array $alert, array $params ) Pre-save Google Calendar settings.
11
+ * @method static void renderSettings() Render Advanced Google Calendar settings.
12
+ */
13
+ abstract class AdvancedGoogleCalendar extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/modules/settings/proxy/DepositPayments.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Settings\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class DepositPayments
8
+ * @package Bookly\Backend\Modules\Settings\Proxy
9
+ *
10
+ * @method static array renderPayments() Render "Deposit options" in "Payments" tab.
11
+ */
12
+ abstract class DepositPayments extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/settings/proxy/PaypalPaymentsStandard.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Settings\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class PaypalPaymentsStandard
8
+ * @package Bookly\Backend\Modules\Settings\Proxy
9
+ *
10
+ * @method static array prepareToggleOptions( array $options ) Add option to enable PayPal Payments Standard.
11
+ * @method static string renderSetUpOptions() Print list of options to set up PayPal Payments Standard.
12
+ */
13
+ abstract class PaypalPaymentsStandard extends Lib\Base\Proxy
14
+ {
15
+
16
+ }
backend/modules/settings/proxy/Pro.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Settings\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Modules\Settings\Proxy
9
+ *
10
+ * @method static void renderCancelAppointmentAction() Render Cancel appointment action.
11
+ * @method static void renderCancellationConfirmationUrl() Render cancellation confirmation URL setting.
12
+ * @method static void renderCombinedNotifications() Render combined notifications in customers setting.
13
+ * @method static void renderCreateWordPressUser() Render Create WordPress user account for customers.
14
+ * @method static void renderCustomersAddress() Render address settings in the customers tab.
15
+ * @method static void renderCustomersBirthday() Render birthday settings in the customers tab.
16
+ * @method static void renderFinalStepUrl() Render final step URL setting.
17
+ * @method static void renderGoogleCalendarMenuItem() Render 'Google Calendar' menu item.
18
+ * @method static void renderGoogleCalendarTab() Render 'Google Calendar' tab.
19
+ * @method static void renderMinimumTimeRequirement() Render minimum time requirement prior to booking and canceling.
20
+ * @method static void renderNewUserAccountRole() Render New user account role.
21
+ * @method static void renderPurchaseCodeMenuItem() Render 'Purchase Code' menu item.
22
+ * @method static void renderPurchaseCodeTab() Render 'Purchase Code' tab.
23
+ */
24
+ abstract class Pro extends Lib\Base\Proxy
25
+ {
26
+
27
+ }
backend/modules/settings/proxy/ProSettings.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Settings\Proxy;
3
+
4
+ use Bookly\Lib\Base\Component;
5
+ use Bookly\Lib\Config;
6
+ use BooklyPro\Lib\Base\Proxy as ProxyPro;
7
+
8
+ /**
9
+ * Class ProSettings
10
+ * @package Bookly\Backend\Modules\Settings\Proxy
11
+ *
12
+ * @method static void renderProMenuItem()
13
+ * @method static void renderProTab()
14
+ */
15
+ abstract class ProSettings extends Component
16
+ {
17
+ /**
18
+ * Register proxy methods.
19
+ */
20
+ public static function init()
21
+ {
22
+ ProxyPro::init( get_called_class(), static::reflection() );
23
+ }
24
+
25
+ /**
26
+ * Invoke proxy method.
27
+ *
28
+ * @param string $method
29
+ * @param mixed $args
30
+ * @return mixed
31
+ */
32
+ public static function __callStatic( $method, $args )
33
+ {
34
+ if ( Config::proActive() && ProxyPro::canInvoke( get_called_class(), $method ) ) {
35
+ return ProxyPro::invoke( get_called_class(), $method, $args );
36
+ }
37
+ }
38
+
39
+ /**
40
+ * @inheritdoc
41
+ */
42
+ protected static function directory()
43
+ {
44
+ return dirname( parent::directory() );
45
+ }
46
+ }
backend/modules/settings/proxy/Shared.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Settings\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Shared
8
+ * @package Bookly\Backend\Modules\Settings\Proxy
9
+ *
10
+ * @method static array prepareCalendarAppointmentCodes( array $codes, string $participants ) Prepare codes for appointment description displayed in calendar.
11
+ * @method static array preparePaymentGatewaySettings( array $payment_data ) Prepare payment gateway settings.
12
+ * @method static array prepareWooCommerceCodes( array $codes ) Alter array of codes to be displayed in WooCommerce (Order,Cart,Checkout etc.).
13
+ * @method static void renderMenuItem() Render tab in settings page.
14
+ * @method static void renderTab() Render add-on settings form.
15
+ * @method static void renderUrlSettings() Render URL settings on Settings page.
16
+ * @method static array saveSettings( array $alert, string $tab, array $params ) Save add-on settings.
17
+ */
18
+ abstract class Shared extends Lib\Base\Proxy
19
+ {
20
+
21
+ }
backend/modules/settings/proxy/Taxes.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Settings\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Taxes
8
+ * @package Bookly\Backend\Modules\Settings\Proxy
9
+ *
10
+ * @method static array renderPayments() Render "Deposit options" in "Payments" tab.
11
+ */
12
+ abstract class Taxes extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/settings/resources/js/collect-stats-notice.js DELETED
@@ -1,13 +0,0 @@
1
- jQuery(function ($) {
2
- var $alert = $('#bookly-collect-stats-notice');
3
-
4
- $alert
5
- .on('click', '.bookly-js-disallow-stats', function () {
6
- $.post(ajaxurl, {action: 'bookly_dismiss_collect_stats_notice', csrf_token: SupportL10n.csrf_token});
7
- $alert.alert('close');
8
- })
9
- .on('click', '.bookly-js-allow-stats', function () {
10
- $.post(ajaxurl, {action: 'bookly_allow_collect_stats', csrf_token: SupportL10n.csrf_token});
11
- $alert.alert('close');
12
- });
13
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/resources/js/settings.js CHANGED
@@ -1,103 +1,88 @@
1
  jQuery(function ($) {
2
- var $form = $('#business-hours'),
3
- $final_step_url = $('input[name=bookly_url_final_step_url]'),
4
- $final_step_url_mode = $('#bookly_settings_final_step_url_mode'),
5
- $help_btn = $('#bookly-help-btn'),
6
- $participants = $('#bookly_appointment_participants')
7
- ;
 
 
 
 
 
 
 
 
8
 
9
  booklyAlert(BooklyL10n.alert);
10
 
11
- Ladda.bind('button[type=submit]', {timeout: 2000});
12
 
13
- $('.bookly-limitation').on('click', function (e) {
14
- e.preventDefault();
15
- Ladda.stopAll();
16
- booklyAlert({error: [BooklyL10n.limitations]});
17
- $(this).prop('disabled', true);
18
  });
19
- $('#bookly_cart_enabled,#bookly_wc_enabled,#bookly_pmt_coupons').on('change', function (e) {
20
- $(this).val('0');
21
- booklyAlert({error: [BooklyL10n.limitations]});
22
- $(this).find('option:gt(0)').prop('disabled', true);
23
  });
24
-
25
- $('#bookly_gc_client_id,#bookly_gc_client_secret,#bookly_gc_two_way_sync,#bookly_gc_limit_events,#bookly_gc_event_title').on('focus', function () {
26
- $(this).prop('disabled',true);
27
- booklyAlert({error: [BooklyL10n.limitations]});
28
  });
29
- $('#bookly_wc_product,#bookly_l10n_wc_cart_info_name,[name=bookly_l10n_wc_cart_info_value]').on('focus', function () {
30
- $(this).prop('disabled',true);
31
- booklyAlert({error: [BooklyL10n.limitations]});
32
  });
33
- $('.select_start', $form).on('change', function () {
34
- var $flexbox = $(this).closest('.bookly-flexbox'),
35
- $end_select = $('.select_end', $flexbox),
36
- start_time = this.value;
37
 
38
- if (start_time) {
39
- $flexbox.find('.bookly-hide-on-off').show();
40
-
41
- // Hides end time options with value less than in the start time.
42
- var frag = document.createDocumentFragment();
43
- var old_value = $end_select.val();
44
- var new_value = null;
45
- $('option', $end_select).each(function () {
46
- if (this.value <= start_time) {
47
- var span = document.createElement('span');
48
- span.style.display = 'none';
49
- span.appendChild(this.cloneNode(true));
50
- frag.appendChild(span);
51
- } else {
52
- frag.appendChild(this.cloneNode(true));
53
- if (new_value === null || old_value == this.value) {
54
- new_value = this.value;
55
- }
56
- }
57
- });
58
- $end_select.empty().append(frag).val(new_value);
59
- } else { // OFF
60
- $flexbox.find('.bookly-hide-on-off').hide();
61
- }
62
- }).each(function () {
63
- $(this).data('default_value', this.value);
64
  }).trigger('change');
65
 
66
- // Reset.
67
- $('#bookly-hours-reset', $form).on('click', function () {
68
- $('.select_start', $form).each(function () {
69
- $(this).val($(this).data('default_value')).trigger('change');
70
- });
71
- });
72
-
73
- // Customers Tab
74
- var $default_country = $('#bookly_cst_phone_default_country'),
75
- $default_country_code = $('#bookly_cst_default_country_code');
76
-
77
- $.each($.fn.intlTelInput.getCountryData(), function (index, value) {
78
- $default_country.append('<option value="' + value.iso2 + '" data-code="' + value.dialCode + '">' + value.name + ' +' + value.dialCode + '</option>');
79
- });
80
- $default_country.val(BooklyL10n.default_country);
81
-
82
- $default_country.on('change', function () {
83
- $default_country_code.val($default_country.find('option:selected').data('code'));
84
- });
85
-
86
- // Calendar tab
87
  $participants.on('change', function () {
88
-
89
  $('#bookly_cal_one_participant').hide();
90
  $('#bookly_cal_many_participants').hide();
91
  $('#' + $(this).val() ).show();
92
  }).trigger('change');
93
-
94
  $("#bookly_settings_calendar button[type=reset]").on( 'click', function () {
95
  setTimeout(function () {
96
  $participants.trigger('change');
97
  }, 50 );
98
  });
99
 
100
- // Company Tab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  $('#bookly-company-reset').on('click', function () {
102
  var $div = $('#bookly-js-logo .bookly-js-image'),
103
  $input = $('[name=bookly_co_logo_attachment_id]');
@@ -105,16 +90,24 @@ jQuery(function ($) {
105
  $input.val($input.data('default'));
106
  });
107
 
108
- // Cart Tab
109
  $('#bookly_cart_show_columns').sortable({
110
  axis : 'y',
111
  handle : '.bookly-js-handle'
112
  });
113
 
114
- // Payment Tab
115
- var $currency = $('#bookly_pmt_currency'),
116
- $formats = $('#bookly_pmt_price_format')
117
- ;
 
 
 
 
 
 
 
 
118
  $currency.on('change', function () {
119
  $formats.find('option').each(function () {
120
  var decimals = this.value.match(/{price\|(\d)}/)[1],
@@ -123,48 +116,84 @@ jQuery(function ($) {
123
  if (decimals < 3) {
124
  price = price.slice(0, -(decimals == 0 ? 4 : 3 - decimals));
125
  }
126
- this.innerHTML = this.value
 
127
  .replace('{symbol}', $currency.find('option:selected').data('symbol'))
128
  .replace(/{price\|\d}/, price)
129
  ;
 
 
 
 
 
 
130
  });
131
  }).trigger('change');
132
 
133
- // Payment Tab
134
  $('#bookly_paypal_enabled').change(function () {
135
- if (this.value != '0') {
136
- $(this).val('0');
137
- booklyAlert({error: [BooklyL10n.limitations]});
138
- $(this).find('option:gt(0)').prop('disabled', true);
139
- }
140
  $('.bookly-paypal').toggle(this.value != '0');
141
  }).change();
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  $('#bookly-payments-reset').on('click', function (event) {
144
  setTimeout(function () {
145
- $('#bookly_pmt_currency,#bookly_paypal_enabled,#bookly_authorize_net_enabled,#bookly_stripe_enabled,#bookly_2checkout_enabled,#bookly_payu_latam_enabled,#bookly_payson_enabled,#bookly_mollie_enabled,#bookly_payu_latam_sandbox').change();
146
  }, 0);
147
  });
148
 
149
- $('#bookly-customer-reset').on('click', function (event) {
150
- $default_country.val($default_country.data('country'));
151
- });
152
-
153
- if ($final_step_url.val()) { $final_step_url_mode.val(1); }
154
- $final_step_url_mode.change(function () {
155
- $(this).val(0);
156
- booklyAlert({error: [BooklyL10n.limitations]});
157
- $(this).find('option:gt(0)').prop('disabled', true);
158
- $final_step_url.hide().val('');
159
- });
160
-
161
- // Change link to Help page according to activated tab.
162
- var help_link = $help_btn.attr('href');
163
- $('.bookly-nav li[data-toggle="tab"]').on('shown.bs.tab', function(e) {
164
- $help_btn.attr('href', help_link + e.target.getAttribute('data-target').substring(1).replace(/_/g, '-'));
165
  });
166
 
167
- // Holidays
168
  var d = new Date();
169
  $('.bookly-js-annual-calendar').jCal({
170
  day: new Date(d.getFullYear(), 0, 1),
@@ -182,44 +211,58 @@ jQuery(function ($) {
182
  repeat: BooklyL10n.repeat,
183
  close: BooklyL10n.close
184
  });
185
-
186
  $('.bookly-js-jCalBtn').on('click', function (e) {
187
  e.preventDefault();
188
  var trigger = $(this).data('trigger');
189
  $('.bookly-js-annual-calendar').find($(trigger)).trigger('click');
190
  });
191
 
192
- // Activate tab.
193
- $('li[data-target="#bookly_settings_' + BooklyL10n.current_tab + '"]').tab('show');
 
 
 
194
 
195
- $('#bookly-js-logo .bookly-pretty-indicator').on('click', function(){
196
- var frame = wp.media({
197
- library: {type: 'image'},
198
- multiple: false
199
- });
200
- frame.on('select', function () {
201
- var selection = frame.state().get('selection').toJSON(),
202
- img_src
203
- ;
204
- if (selection.length) {
205
- if (selection[0].sizes['thumbnail'] !== undefined) {
206
- img_src = selection[0].sizes['thumbnail'].url;
 
207
  } else {
208
- img_src = selection[0].url;
 
 
 
209
  }
210
- $('[name=bookly_co_logo_attachment_id]').val(selection[0].id);
211
- $('#bookly-js-logo .bookly-js-image').css({'background-image': 'url(' + img_src + ')', 'background-size': 'cover'});
212
- $('#bookly-js-logo .bookly-thumb-delete').show();
213
- $(this).hide();
214
- }
 
 
 
 
 
 
 
215
  });
216
-
217
- frame.open();
218
  });
219
 
220
- $('#bookly-js-logo .bookly-thumb-delete').on('click', function () {
221
- var $thumb = $(this).parents('.bookly-js-image');
222
- $thumb.attr('style', '');
223
- $('[name=bookly_co_logo_attachment_id]').val('');
224
  });
 
 
 
225
  });
1
  jQuery(function ($) {
2
+ var $helpBtn = $('#bookly-help-btn'),
3
+ $form = $('#business-hours'),
4
+ $finalStepUrl = $('input[name=bookly_url_final_step_url]'),
5
+ $finalStepUrlMode = $('#bookly_settings_final_step_url_mode'),
6
+ $participants = $('#bookly_appointment_participants'),
7
+ $defaultCountry = $('#bookly_cst_phone_default_country'),
8
+ $defaultCountryCode = $('#bookly_cst_default_country_code'),
9
+ $gcSyncMode = $('#bookly_gc_sync_mode'),
10
+ $gcLimitEvents = $('#bookly_gc_limit_events'),
11
+ $gcFullSyncOffset = $('#bookly_gc_full_sync_offset_days'),
12
+ $gcFullSyncTitles = $('#bookly_gc_full_sync_titles'),
13
+ $currency = $('#bookly_pmt_currency'),
14
+ $formats = $('#bookly_pmt_price_format')
15
+ ;
16
 
17
  booklyAlert(BooklyL10n.alert);
18
 
19
+ Ladda.bind('button[type=submit]');
20
 
21
+ // Customers tab.
22
+ $.each($.fn.intlTelInput.getCountryData(), function (index, value) {
23
+ $defaultCountry.append('<option value="' + value.iso2 + '" data-code="' + value.dialCode + '">' + value.name + ' +' + value.dialCode + '</option>');
 
 
24
  });
25
+ $defaultCountry.val(BooklyL10n.default_country);
26
+ $defaultCountry.on('change', function () {
27
+ $defaultCountryCode.val($defaultCountry.find('option:selected').data('code'));
 
28
  });
29
+ $('#bookly_cst_address_show_fields').sortable({
30
+ axis : 'y',
31
+ handle : '.bookly-js-handle'
 
32
  });
33
+ $('#bookly-customer-reset').on('click', function (event) {
34
+ $defaultCountry.val($defaultCountry.data('country'));
 
35
  });
 
 
 
 
36
 
37
+ // Google Calendar tab.
38
+ $gcSyncMode.on('change', function () {
39
+ $gcLimitEvents.closest('.form-group').toggle(this.value == '1.5-way');
40
+ $gcFullSyncOffset.closest('.form-group').toggle(this.value == '2-way');
41
+ $gcFullSyncTitles.closest('.form-group').toggle(this.value == '2-way');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  }).trigger('change');
43
 
44
+ // Calendar tab.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  $participants.on('change', function () {
 
46
  $('#bookly_cal_one_participant').hide();
47
  $('#bookly_cal_many_participants').hide();
48
  $('#' + $(this).val() ).show();
49
  }).trigger('change');
 
50
  $("#bookly_settings_calendar button[type=reset]").on( 'click', function () {
51
  setTimeout(function () {
52
  $participants.trigger('change');
53
  }, 50 );
54
  });
55
 
56
+ // Company tab.
57
+ $('#bookly-js-logo .bookly-thumb-delete').on('click', function () {
58
+ var $thumb = $(this).parents('.bookly-js-image');
59
+ $thumb.attr('style', '');
60
+ $('[name=bookly_co_logo_attachment_id]').val('');
61
+ });
62
+ $('#bookly-js-logo .bookly-pretty-indicator').on('click', function(){
63
+ var frame = wp.media({
64
+ library: {type: 'image'},
65
+ multiple: false
66
+ });
67
+ frame.on('select', function () {
68
+ var selection = frame.state().get('selection').toJSON(),
69
+ img_src
70
+ ;
71
+ if (selection.length) {
72
+ if (selection[0].sizes['thumbnail'] !== undefined) {
73
+ img_src = selection[0].sizes['thumbnail'].url;
74
+ } else {
75
+ img_src = selection[0].url;
76
+ }
77
+ $('[name=bookly_co_logo_attachment_id]').val(selection[0].id);
78
+ $('#bookly-js-logo .bookly-js-image').css({'background-image': 'url(' + img_src + ')', 'background-size': 'cover'});
79
+ $('#bookly-js-logo .bookly-thumb-delete').show();
80
+ $(this).hide();
81
+ }
82
+ });
83
+
84
+ frame.open();
85
+ });
86
  $('#bookly-company-reset').on('click', function () {
87
  var $div = $('#bookly-js-logo .bookly-js-image'),
88
  $input = $('[name=bookly_co_logo_attachment_id]');
90
  $input.val($input.data('default'));
91
  });
92
 
93
+ // Cart tab.
94
  $('#bookly_cart_show_columns').sortable({
95
  axis : 'y',
96
  handle : '.bookly-js-handle'
97
  });
98
 
99
+ // Payments tab.
100
+ $('#bookly-payment-systems').sortable({
101
+ axis : 'y',
102
+ handle: '.bookly-js-handle',
103
+ update: function () {
104
+ var order = [];
105
+ $('#bookly_settings_payments .panel[data-slug]').each(function () {
106
+ order.push($(this).data('slug'));
107
+ });
108
+ $('#bookly_settings_payments [name="bookly_pmt_order"]').val(order.join(','));
109
+ }
110
+ });
111
  $currency.on('change', function () {
112
  $formats.find('option').each(function () {
113
  var decimals = this.value.match(/{price\|(\d)}/)[1],
116
  if (decimals < 3) {
117
  price = price.slice(0, -(decimals == 0 ? 4 : 3 - decimals));
118
  }
119
+ var html = this.value
120
+ .replace('{sign}', '')
121
  .replace('{symbol}', $currency.find('option:selected').data('symbol'))
122
  .replace(/{price\|\d}/, price)
123
  ;
124
+ html += ' (' + this.value
125
+ .replace('{sign}', '-')
126
+ .replace('{symbol}', $currency.find('option:selected').data('symbol'))
127
+ .replace(/{price\|\d}/, price) + ')'
128
+ ;
129
+ this.innerHTML = html;
130
  });
131
  }).trigger('change');
132
 
 
133
  $('#bookly_paypal_enabled').change(function () {
134
+ $('.bookly-paypal-ec').toggle(this.value == 'ec');
135
+ $('.bookly-paypal-ps').toggle(this.value == 'ps');
 
 
 
136
  $('.bookly-paypal').toggle(this.value != '0');
137
  }).change();
138
 
139
+ $('#bookly_authorize_net_enabled').change(function () {
140
+ $('.bookly-authorize-net').toggle(this.value != '0');
141
+ }).change();
142
+
143
+ $('#bookly_stripe_enabled').change(function () {
144
+ $('.bookly-stripe').toggle(this.value == 1);
145
+ }).change();
146
+
147
+ $('#bookly_2checkout_enabled').change(function () {
148
+ $('.bookly-2checkout').toggle(this.value != '0');
149
+ }).change();
150
+
151
+ $('#bookly_payu_biz_enabled').change(function () {
152
+ $('.bookly-payu_biz').toggle(this.value != '0');
153
+ }).change();
154
+
155
+ $('#bookly_payu_latam_enabled').change(function () {
156
+ $('.bookly-payu_latam').toggle(this.value != '0');
157
+ }).change();
158
+
159
+ $('#bookly_payson_enabled').change(function () {
160
+ $('.bookly-payson').toggle(this.value != '0');
161
+ }).change();
162
+
163
+ $('#bookly_mollie_enabled').change(function () {
164
+ $('.bookly-mollie').toggle(this.value != '0');
165
+ }).change();
166
+
167
+ $('#bookly_payu_biz_sandbox').change(function () {
168
+ var live = this.value != 1;
169
+ $('.bookly-payu_biz > .form-group:eq(1)').toggle(live);
170
+ $('.bookly-payu_biz > .form-group:eq(2)').toggle(live);
171
+ }).change();
172
+
173
+ $('#bookly_payu_latam_sandbox').change(function () {
174
+ var live = this.value != 1;
175
+ $('.bookly-payu_latam > .form-group:eq(1)').toggle(live);
176
+ $('.bookly-payu_latam > .form-group:eq(2)').toggle(live);
177
+ $('.bookly-payu_latam > .form-group:eq(3)').toggle(live);
178
+ }).change();
179
+
180
  $('#bookly-payments-reset').on('click', function (event) {
181
  setTimeout(function () {
182
+ $('#bookly_pmt_currency,#bookly_paypal_enabled,#bookly_authorize_net_enabled,#bookly_stripe_enabled,#bookly_2checkout_enabled,#bookly_payu_biz_enabled,#bookly_payu_latam_enabled,#bookly_payson_enabled,#bookly_mollie_enabled,#bookly_payu_biz_sandbox,#bookly_payu_latam_sandbox').change();
183
  }, 0);
184
  });
185
 
186
+ // URL tab.
187
+ if ($finalStepUrl.val()) { $finalStepUrlMode.val(1); }
188
+ $finalStepUrlMode.change(function () {
189
+ if (this.value == 0) {
190
+ $finalStepUrl.hide().val('');
191
+ } else {
192
+ $finalStepUrl.show();
193
+ }
 
 
 
 
 
 
 
 
194
  });
195
 
196
+ // Holidays Tab.
197
  var d = new Date();
198
  $('.bookly-js-annual-calendar').jCal({
199
  day: new Date(d.getFullYear(), 0, 1),
211
  repeat: BooklyL10n.repeat,
212
  close: BooklyL10n.close
213
  });
 
214
  $('.bookly-js-jCalBtn').on('click', function (e) {
215
  e.preventDefault();
216
  var trigger = $(this).data('trigger');
217
  $('.bookly-js-annual-calendar').find($(trigger)).trigger('click');
218
  });
219
 
220
+ // Business Hours tab.
221
+ $('.select_start', $form).on('change', function () {
222
+ var $flexbox = $(this).closest('.bookly-flexbox'),
223
+ $end_select = $('.select_end', $flexbox),
224
+ start_time = this.value;
225
 
226
+ if (start_time) {
227
+ $flexbox.find('.bookly-hide-on-off').show();
228
+
229
+ // Hides end time options with value less than in the start time.
230
+ var frag = document.createDocumentFragment();
231
+ var old_value = $end_select.val();
232
+ var new_value = null;
233
+ $('option', $end_select).each(function () {
234
+ if (this.value <= start_time) {
235
+ var span = document.createElement('span');
236
+ span.style.display = 'none';
237
+ span.appendChild(this.cloneNode(true));
238
+ frag.appendChild(span);
239
  } else {
240
+ frag.appendChild(this.cloneNode(true));
241
+ if (new_value === null || old_value == this.value) {
242
+ new_value = this.value;
243
+ }
244
  }
245
+ });
246
+ $end_select.empty().append(frag).val(new_value);
247
+ } else { // OFF
248
+ $flexbox.find('.bookly-hide-on-off').hide();
249
+ }
250
+ }).each(function () {
251
+ $(this).data('default_value', this.value);
252
+ }).trigger('change');
253
+ // Reset.
254
+ $('#bookly-hours-reset', $form).on('click', function () {
255
+ $('.select_start', $form).each(function () {
256
+ $(this).val($(this).data('default_value')).trigger('change');
257
  });
 
 
258
  });
259
 
260
+ // Change link to Help page according to activated tab.
261
+ var help_link = $helpBtn.attr('href');
262
+ $('.bookly-nav li[data-toggle="tab"]').on('shown.bs.tab', function(e) {
263
+ $helpBtn.attr('href', help_link + e.target.getAttribute('data-target').substring(1).replace(/_/g, '-'));
264
  });
265
+
266
+ // Activate tab.
267
+ $('li[data-target="#bookly_settings_' + BooklyL10n.current_tab + '"]').tab('show');
268
  });
backend/modules/settings/templates/_calendarForm.php CHANGED
@@ -1,21 +1,34 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
 
2
  <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'calendar' ) ) ?>">
3
- <div class="form-group"><label for="bookly_appointment_participants"><?php _e( 'Calendar', 'bookly' ) ?></label>
4
- <p class="help-block"><?php _e( 'Set order of the fields in calendar for', 'bookly' ) ?></p>
5
- </div>
6
- <input id="bookly_appointment_participants" type="hidden" name="bookly_appointment_participants" value="bookly_cal_one_participant">
 
 
 
 
 
 
 
 
 
 
7
  <div class="form-group" id="bookly_cal_one_participant">
8
  <textarea class="form-control" rows="9" name="bookly_cal_one_participant" placeholder="<?php _e( 'Enter a value', 'bookly' ) ?>"><?php echo esc_textarea( get_option( 'bookly_cal_one_participant' ) ) ?></textarea><br/>
9
- <?php $this->render( '_calendar_codes', array( 'participants' => 'one' ) ) ?>
10
  </div>
11
  <div class="form-group" id="bookly_cal_many_participants">
12
  <textarea class="form-control" rows="9" name="bookly_cal_many_participants" placeholder="<?php _e( 'Enter a value', 'bookly' ) ?>"><?php echo esc_textarea( get_option( 'bookly_cal_many_participants' ) ) ?></textarea><br/>
13
- <?php $this->render( '_calendar_codes', array( 'participants' => 'many' ) ) ?>
14
  </div>
15
 
16
  <div class="panel-footer">
17
- <?php \BooklyLite\Lib\Utils\Common::csrf() ?>
18
- <?php \BooklyLite\Lib\Utils\Common::submitButton() ?>
19
- <?php \BooklyLite\Lib\Utils\Common::resetButton() ?>
20
  </div>
21
  </form>
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 method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'calendar' ) ) ?>">
6
+ <?php if ( Bookly\Lib\Config::groupBookingActive() ) : ?>
7
+ <div class="form-group"><label for="bookly_appointment_participants"><?php _e( 'Calendar', 'bookly' ) ?></label>
8
+ <p class="help-block"><?php _e( 'Set order of the fields in calendar', 'bookly' ) ?></p>
9
+ <select class="form-control" id="bookly_appointment_participants">
10
+ <option value="bookly_cal_one_participant"><?php _e( 'Appointment with one participant', 'bookly' ) ?></option>
11
+ <option value="bookly_cal_many_participants"><?php _e( 'Appointment with many participants', 'bookly' ) ?></option>
12
+ </select>
13
+ </div>
14
+ <?php else : ?>
15
+ <div class="form-group"><label for="bookly_appointment_participants"><?php _e( 'Calendar', 'bookly' ) ?></label>
16
+ <p class="help-block"><?php _e( 'Set order of the fields in calendar', 'bookly' ) ?></p>
17
+ </div>
18
+ <input id="bookly_appointment_participants" type="hidden" name="bookly_appointment_participants" value="bookly_cal_one_participant">
19
+ <?php endif ?>
20
  <div class="form-group" id="bookly_cal_one_participant">
21
  <textarea class="form-control" rows="9" name="bookly_cal_one_participant" placeholder="<?php _e( 'Enter a value', 'bookly' ) ?>"><?php echo esc_textarea( get_option( 'bookly_cal_one_participant' ) ) ?></textarea><br/>
22
+ <?php $self::renderTemplate( '_calendar_codes', array( 'participants' => 'one' ) ) ?>
23
  </div>
24
  <div class="form-group" id="bookly_cal_many_participants">
25
  <textarea class="form-control" rows="9" name="bookly_cal_many_participants" placeholder="<?php _e( 'Enter a value', 'bookly' ) ?>"><?php echo esc_textarea( get_option( 'bookly_cal_many_participants' ) ) ?></textarea><br/>
26
+ <?php $self::renderTemplate( '_calendar_codes', array( 'participants' => 'many' ) ) ?>
27
  </div>
28
 
29
  <div class="panel-footer">
30
+ <?php Inputs::renderCsrf() ?>
31
+ <?php Buttons::renderSubmit() ?>
32
+ <?php Buttons::renderReset() ?>
33
  </div>
34
  </form>
backend/modules/settings/templates/_calendar_codes.php CHANGED
@@ -1,4 +1,6 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
2
  $codes = array(
3
  array( 'code' => 'appointment_date', 'description' => __( 'date of appointment', 'bookly' ) ),
4
  array( 'code' => 'appointment_time', 'description' => __( 'time of appointment', 'bookly' ) ),
@@ -30,6 +32,6 @@ if ( $participants == 'one' ) {
30
  $codes[] = array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) );
31
  }
32
 
33
- $codes = BooklyLite\Lib\Proxy\Shared::prepareCalendarAppointmentCodes( $codes, $participants );
34
 
35
- BooklyLite\Lib\Utils\Common::codes( $codes );
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Modules\Settings\Proxy;
3
+
4
  $codes = array(
5
  array( 'code' => 'appointment_date', 'description' => __( 'date of appointment', 'bookly' ) ),
6
  array( 'code' => 'appointment_time', 'description' => __( 'time of appointment', 'bookly' ) ),
32
  $codes[] = array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) );
33
  }
34
 
35
+ $codes = Proxy\Shared::prepareCalendarAppointmentCodes( $codes, $participants );
36
 
37
+ echo Bookly\Lib\Utils\Common::codes( $codes );
backend/modules/settings/templates/_cartForm.php DELETED
@@ -1,33 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'cart' ) ) ?>">
3
- <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'bookly_cart_enabled', __( 'Cart', 'bookly' ), __( 'If cart is enabled then your clients will be able to book several appointments at once. Please note that WooCommerce integration must be disabled.', 'bookly' ) ) ?>
4
- <?php \BooklyLite\Lib\Proxy\Shared::renderCartSettings() ?>
5
- <div class="form-group">
6
- <label for="bookly_cart_show_columns"><?php _e( 'Columns', 'bookly' ) ?></label><br/>
7
- <div class="bookly-flags" id="bookly_cart_show_columns">
8
- <?php foreach ( (array) get_option( 'bookly_cart_show_columns' ) as $column => $attr ) : ?>
9
- <div class="bookly-flexbox"<?php if ( $column == 'deposit' && ! \BooklyLite\Lib\Config::depositPaymentsEnabled() ) : ?> style="display:none"<?php endif ?>>
10
- <div class="bookly-flex-cell">
11
- <i class="bookly-js-handle bookly-margin-right-sm bookly-icon bookly-icon-draghandle bookly-cursor-move" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
12
- </div>
13
- <div class="bookly-flex-cell" style="width: 100%">
14
- <div class="checkbox">
15
- <label>
16
- <input type="hidden" name="bookly_cart_show_columns[<?php echo $column ?>][show]" value="0">
17
- <input type="checkbox"
18
- name="bookly_cart_show_columns[<?php echo $column ?>][show]"
19
- value="1" <?php checked( $attr['show'], true ) ?>>
20
- <?php echo isset( $cart_columns[ $column ] ) ? $cart_columns[ $column ] : '' ?>
21
- </label>
22
- </div>
23
- </div>
24
- </div>
25
- <?php endforeach ?>
26
- </div>
27
- </div>
28
- <div class="panel-footer">
29
- <?php \BooklyLite\Lib\Utils\Common::csrf() ?>
30
- <?php \BooklyLite\Lib\Utils\Common::customButton( null, 'btn btn-lg btn-success bookly-limitation' ) ?>
31
- <?php \BooklyLite\Lib\Utils\Common::resetButton() ?>
32
- </div>
33
- </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/templates/_collect_stats_notice.php DELETED
@@ -1,24 +0,0 @@
1
- <?php
2
- /**
3
- * Template to show notice about "we'r starting to collect statistics about usage"
4
- */
5
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
6
- use BooklyLite\Lib\Utils\Common;
7
- ?>
8
- <div id="bookly-tbs" class="wrap">
9
- <div id="bookly-collect-stats-notice" class="alert alert-info bookly-tbs-body bookly-flexbox">
10
- <div class="bookly-flex-row">
11
- <div class="bookly-flex-cell" style="width:39px"><i class="alert-icon"></i></div>
12
- <div class="bookly-flex-cell">
13
- <button type="button" class="close bookly-js-disallow-stats" data-dismiss="alert"></button>
14
- <?php _e( 'Dear customer,', 'bookly' ) ?>
15
- <br>
16
- <?php _e( 'Bookly needs your permission to collect anonymous plugin usage stats so we could constantly improve the plugin. You can always change permissions in Bookly settings.', 'bookly' ); ?>
17
- <br>
18
- <br>
19
- <?php Common::customButton( null, 'btn-success bookly-js-allow-stats', __( 'Allow (OK)', 'bookly' ) ) ?>
20
- <?php Common::customButton( null, 'btn-default bookly-js-disallow-stats', __( 'Don’t allow', 'bookly' ) ) ?>
21
- </div>
22
- </div>
23
- </div>
24
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/templates/_companyForm.php CHANGED
@@ -1,4 +1,8 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
 
 
2
  <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'company' ) ) ?>">
3
  <div class="row">
4
  <div class="col-xs-3 col-lg-2">
@@ -33,7 +37,7 @@
33
  </div>
34
  <div class="col-xs-9 col-lg-10">
35
  <div class="bookly-flex-cell bookly-vertical-middle">
36
- <?php \BooklyLite\Lib\Utils\Common::optionText( 'bookly_co_name', __( 'Company name', 'bookly' ) ) ?>
37
  </div>
38
  </div>
39
  </div>
@@ -43,12 +47,12 @@
43
  <textarea id="bookly_co_address" class="form-control" rows="5"
44
  name="bookly_co_address"><?php form_option( 'bookly_co_address' ) ?></textarea>
45
  </div>
46
- <?php \BooklyLite\Lib\Utils\Common::optionText( 'bookly_co_phone', __( 'Phone', 'bookly' ) ) ?>
47
- <?php \BooklyLite\Lib\Utils\Common::optionText( 'bookly_co_website', __( 'Website', 'bookly' ) ) ?>
48
 
49
  <div class="panel-footer">
50
- <?php \BooklyLite\Lib\Utils\Common::csrf() ?>
51
- <?php \BooklyLite\Lib\Utils\Common::submitButton() ?>
52
- <?php \BooklyLite\Lib\Utils\Common::resetButton( 'bookly-company-reset' ) ?>
53
  </div>
54
  </form>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ use Bookly\Backend\Components\Controls\Inputs as ControlInputs;
4
+ use Bookly\Backend\Components\Settings\Inputs;
5
+ ?>
6
  <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'company' ) ) ?>">
7
  <div class="row">
8
  <div class="col-xs-3 col-lg-2">
37
  </div>
38
  <div class="col-xs-9 col-lg-10">
39
  <div class="bookly-flex-cell bookly-vertical-middle">
40
+ <?php Inputs::renderText( 'bookly_co_name', __( 'Company name', 'bookly' ) ) ?>
41
  </div>
42
  </div>
43
  </div>
47
  <textarea id="bookly_co_address" class="form-control" rows="5"
48
  name="bookly_co_address"><?php form_option( 'bookly_co_address' ) ?></textarea>
49
  </div>
50
+ <?php Inputs::renderText( 'bookly_co_phone', __( 'Phone', 'bookly' ) ) ?>
51
+ <?php Inputs::renderText( 'bookly_co_website', __( 'Website', 'bookly' ) ) ?>
52
 
53
  <div class="panel-footer">
54
+ <?php ControlInputs::renderCsrf() ?>
55
+ <?php Buttons::renderSubmit() ?>
56
+ <?php Buttons::renderReset( 'bookly-company-reset' ) ?>
57
  </div>
58
  </form>
backend/modules/settings/templates/_customers.php CHANGED
@@ -1,22 +1,29 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Utils\Common;
 
 
 
 
3
  ?>
4
  <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'customers' ) ) ?>">
5
- <?php Common::optionToggle( 'bookly_cst_create_account', __( 'Create WordPress user account for customers', 'bookly' ), __( 'If this setting is enabled then Bookly will be creating WordPress user accounts for all new customers. If the user is logged in then the new customer will be associated with the existing user account.', 'bookly' ) );
6
- Common::optionToggle( 'bookly_cst_new_account_role', __( 'New user account role', 'bookly' ), __( 'Select what role will be assigned to newly created WordPress user accounts for customers.', 'bookly' ),
7
- $values['bookly_cst_new_account_role'] );
8
- Common::optionToggle( 'bookly_cst_phone_default_country', __( 'Phone field default country', 'bookly' ), __( 'Select default country for the phone field in the \'Details\' step of booking. You can also let Bookly determine the country based on the IP address of the client.', 'bookly' ),
9
  array( array( 'disabled', __( 'Disabled', 'bookly' ) ), array( 'auto', __( 'Guess country by user\'s IP address', 'bookly' ) ) ) );
10
- Common::optionText( 'bookly_cst_default_country_code', __( 'Default country code', 'bookly' ), __( 'Your clients must have their phone numbers in international format in order to receive text messages. However you can specify a default country code that will be used as a prefix for all phone numbers that do not start with "+" or "00". E.g. if you enter "1" as the default country code and a client enters their phone as "(600) 555-2222" the resulting phone number to send the SMS to will be "+1600555222".', 'bookly' ) );
11
- Common::optionToggle( 'bookly_cst_cancel_action', __( 'Cancel appointment action', 'bookly' ), __( 'Select what happens when customer clicks cancel appointment link. With "Delete" the appointment will be deleted from the calendar. With "Cancel" only appointment status will be changed to "Cancelled".', 'bookly' ),
12
- array( array( 'delete', __( 'Delete', 'bookly' ) ), array( 'cancel', __( 'Cancel', 'bookly' ) ) ) );
13
- Common::optionToggle( 'bookly_cst_combined_notifications', __( 'Combined notifications', 'bookly' ), __( 'If combined notifications are enabled then your clients will receive single notification for entire booking instead of separate notification per each booked appointment (e.g. when cart is enabled). You will need to edit corresponding templates in Email and SMS Notifications.', 'bookly' ) );
14
- Common::optionToggle( 'bookly_cst_remember_in_cookie', __( 'Remember personal information in cookies', 'bookly' ), __( 'If this setting is enabled then returning customers will have their personal information fields filled in at the Details step with the data previously saved in cookies.', 'bookly' ) );
15
- Common::optionToggle( 'bookly_cst_show_update_details_dialog', __( 'Show confirmation dialog before updating customer\'s data', 'bookly' ), __( 'If this option is enabled and customer enters contact info different from the previous order, a warning message will appear asking to update the data.', 'bookly' ) );
 
 
 
 
16
  ?>
17
  <div class="panel-footer">
18
- <?php Common::csrf() ?>
19
- <?php Common::submitButton() ?>
20
- <?php Common::resetButton( 'bookly-customer-reset' ) ?>
21
  </div>
22
  </form>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Inputs as ControlInputs;
3
+ use Bookly\Backend\Components\Controls\Buttons;
4
+ use Bookly\Backend\Components\Settings\Inputs;
5
+ use Bookly\Backend\Components\Settings\Selects;
6
+ use Bookly\Backend\Modules\Settings\Proxy;
7
  ?>
8
  <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'customers' ) ) ?>">
9
+ <?php Proxy\Pro::renderCreateWordPressUser();
10
+ Proxy\Pro::renderNewUserAccountRole();
11
+ Selects::renderSingle( 'bookly_cst_phone_default_country', __( 'Phone field default country', 'bookly' ), __( 'Select default country for the phone field in the \'Details\' step of booking. You can also let Bookly determine the country based on the IP address of the client.', 'bookly' ),
 
12
  array( array( 'disabled', __( 'Disabled', 'bookly' ) ), array( 'auto', __( 'Guess country by user\'s IP address', 'bookly' ) ) ) );
13
+ Inputs::renderText( 'bookly_cst_default_country_code', __( 'Default country code', 'bookly' ), __( 'Your clients must have their phone numbers in international format in order to receive text messages. However you can specify a default country code that will be used as a prefix for all phone numbers that do not start with "+" or "00". E.g. if you enter "1" as the default country code and a client enters their phone as "(600) 555-2222" the resulting phone number to send the SMS to will be "+1600555222".', 'bookly' ) );
14
+
15
+ Proxy\Pro::renderCustomersBirthday();
16
+ Proxy\Pro::renderCustomersAddress();
17
+
18
+ Proxy\Pro::renderCancelAppointmentAction();
19
+ Proxy\Pro::renderCombinedNotifications();
20
+ Selects::renderSingle( 'bookly_cst_remember_in_cookie', __( 'Remember personal information in cookies', 'bookly' ), __( 'If this setting is enabled then returning customers will have their personal information fields filled in at the Details step with the data previously saved in cookies.', 'bookly' ) );
21
+ Selects::renderSingle( 'bookly_cst_allow_duplicates', __( 'Allow duplicate customers', 'bookly' ), __( 'If enabled, a new user will be created if any of the registration data during the booking is different.', 'bookly' ) );
22
+ Selects::renderSingle( 'bookly_cst_show_update_details_dialog', __( 'Show confirmation dialog before updating customer\'s data', 'bookly' ), __( 'If this option is enabled and customer enters contact info different from the previous order, a warning message will appear asking to update the data.', 'bookly' ) );
23
  ?>
24
  <div class="panel-footer">
25
+ <?php ControlInputs::renderCsrf() ?>
26
+ <?php Buttons::renderSubmit() ?>
27
+ <?php Buttons::renderReset( 'bookly-customer-reset' ) ?>
28
  </div>
29
  </form>
backend/modules/settings/templates/_facebookForm.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ use Bookly\Backend\Components\Controls\Inputs as ControlInputs;
4
+ use Bookly\Backend\Components\Settings\Inputs;
5
+ ?>
6
+ <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'facebook' ) ) ?>">
7
+ <div class="form-group">
8
+ <h4><?php esc_html_e( 'Instructions', 'bookly' ) ?></h4>
9
+ <p><?php esc_html_e( 'To set up Facebook integration, do the following:', 'bookly' ) ?></p>
10
+ <ol>
11
+ <li><?php _e( 'Follow the steps at <a href="https://developers.facebook.com/docs/apps/register" target="_blank">https://developers.facebook.com/docs/apps/register</a> to create a Developer Account, register and configure your <b>Facebook App</b>. Below the App Details Panel click <b>Add Platform</b> button, select Website and enter your website URL.', 'bookly' ) ?></li>
12
+ <li><?php _e( 'Go to your <a href="https://developers.facebook.com/apps/" target="_blank">App Dashboard</a>. In the left side navigation panel of the App Dashboard, click <b>Settings > Basic</b> to view the App Details Panel with your <b>App ID</b>. Use it in the form below.', 'bookly' ) ?></li>
13
+ </ol>
14
+ </div>
15
+ <?php Inputs::renderText( 'bookly_fb_app_id', __( 'App ID', 'bookly' ) ) ?>
16
+ <div class="panel-footer">
17
+ <?php ControlInputs::renderCsrf() ?>
18
+ <?php Buttons::renderSubmit() ?>
19
+ <?php Buttons::renderReset() ?>
20
+ </div>
21
+ </form>
backend/modules/settings/templates/_generalForm.php CHANGED
@@ -1,36 +1,27 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Plugin;
3
- use BooklyLite\Lib\Utils\Common;
4
- use BooklyLite\Lib\Entities\CustomerAppointment;
 
 
 
5
  ?>
6
  <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'general' ) ) ?>">
7
  <?php
8
- Common::optionToggle( 'bookly_gen_lite_uninstall_remove_bookly_data', __( 'Delete all data on uninstall', 'bookly' ), __( 'If you want to replace Bookly Lite with full version of Bookly then disable this setting to prevent data from being deleted when you uninstall Bookly Lite.', 'bookly' ) );
9
- Common::optionToggle( 'bookly_gen_time_slot_length', __( 'Time slot length', 'bookly' ), __( 'Select a time interval which will be used as a step when building all time slots in the system.', 'bookly' ),
10
- $values['bookly_gen_time_slot_length'] );
11
- Common::optionToggle( 'bookly_gen_service_duration_as_slot_length', __( 'Service duration as slot length', 'bookly' ), __( 'Enable this option to make slot length equal to service duration at the Time step of booking form.', 'bookly' ) );
12
- Common::optionToggle( 'bookly_gen_default_appointment_status', __( 'Default appointment status', 'bookly' ), __( 'Select status for newly booked appointments.', 'bookly' ), array( array( CustomerAppointment::STATUS_PENDING, __( 'Pending', 'bookly' ) ), array( CustomerAppointment::STATUS_APPROVED, __( 'Approved', 'bookly' ) ), ) );
13
- Common::optionToggle( 'bookly_gen_min_time_prior_booking', __( 'Minimum time requirement prior to booking', 'bookly' ), __( 'Set how late appointments can be booked (for example, require customers to book at least 1 hour before the appointment time).', 'bookly' ),
14
- $values['bookly_gen_min_time_prior_booking'] );
15
- Common::optionToggle( 'bookly_gen_min_time_prior_cancel', __( 'Minimum time requirement prior to canceling', 'bookly' ), __( 'Set how late appointments can be cancelled (for example, require customers to cancel at least 1 hour before the appointment time).', 'bookly' ),
16
- $values['bookly_gen_min_time_prior_cancel'] );
17
- Common::optionNumeric( 'bookly_gen_max_days_for_booking', __( 'Number of days available for booking', 'bookly' ), __( 'Set how far in the future the clients can book appointments.', 'bookly' ), 1, 1 );
18
- Common::optionToggle( 'bookly_gen_use_client_time_zone', __( 'Display available time slots in client\'s time zone', 'bookly' ), __( 'The value is taken from client’s browser.', 'bookly' ) );
19
- Common::optionToggle( 'bookly_gen_allow_staff_edit_profile', __( 'Allow staff members to edit their profiles', 'bookly' ), __( 'If this option is enabled then all staff members who are associated with WordPress users will be able to edit their own profiles, services, schedule and days off.', 'bookly' ) );
20
- Common::optionToggle( 'bookly_gen_link_assets_method', __( 'Method to include Bookly JavaScript and CSS files on the page', 'bookly' ), __( 'With "Enqueue" method the JavaScript and CSS files of Bookly will be included on all pages of your website. This method should work with all themes. With "Print" method the files will be included only on the pages which contain Bookly booking form. This method may not work with all themes.', 'bookly' ),
21
- array( array( 'enqueue', 'Enqueue' ), array( 'print', 'Print' ) ) )
22
  ?>
23
- <div class="form-group">
24
- <label for="bookly_gen_collect_stats"><?php _e( 'Help us improve Bookly by sending anonymous usage stats', 'bookly' ); ?></label>
25
- <select class="form-control" name="bookly_gen_collect_stats" id="bookly_gen_collect_stats">
26
- <?php foreach ( array( __( 'Disabled', 'bookly' ) => 0, __( 'Enabled', 'bookly' ) => 1 ) as $text => $mode ) : ?>
27
- <option value="<?php echo esc_attr( $mode ) ?>" <?php selected( get_option( 'bookly_gen_collect_stats' ), $mode ) ?> ><?php echo $text ?></option>
28
- <?php endforeach ?>
29
- </select>
30
- </div>
31
  <div class="panel-footer">
32
- <?php Common::csrf() ?>
33
- <?php Common::submitButton() ?>
34
- <?php Common::resetButton() ?>
35
  </div>
36
  </form>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ use Bookly\Backend\Components\Controls\Inputs as ControlInputs;
4
+ use Bookly\Backend\Components\Settings\Inputs;
5
+ use Bookly\Backend\Components\Settings\Selects;
6
+ use Bookly\Lib\Entities\CustomerAppointment;
7
+ use Bookly\Backend\Modules\Settings\Proxy;
8
  ?>
9
  <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'general' ) ) ?>">
10
  <?php
11
+ Selects::renderSingle( 'bookly_gen_time_slot_length', __( 'Time slot length', 'bookly' ), __( 'Select a time interval which will be used as a step when building all time slots in the system.', 'bookly' ),
12
+ $values['bookly_gen_time_slot_length'] );
13
+ Selects::renderSingle( 'bookly_gen_service_duration_as_slot_length', __( 'Service duration as slot length', 'bookly' ), __( 'Enable this option to make slot length equal to service duration at the Time step of booking form.', 'bookly' ) );
14
+ Selects::renderSingle( 'bookly_gen_default_appointment_status', __( 'Default appointment status', 'bookly' ), __( 'Select status for newly booked appointments.', 'bookly' ), array( array( CustomerAppointment::STATUS_PENDING, __( 'Pending', 'bookly' ) ), array( CustomerAppointment::STATUS_APPROVED, __( 'Approved', 'bookly' ) ), ) );
15
+ Proxy\Pro::renderMinimumTimeRequirement();
16
+ Inputs::renderNumber( 'bookly_gen_max_days_for_booking', __( 'Number of days available for booking', 'bookly' ), __( 'Set how far in the future the clients can book appointments.', 'bookly' ), 1, 1 );
17
+ Selects::renderSingle( 'bookly_gen_use_client_time_zone', __( 'Display available time slots in client\'s time zone', 'bookly' ), __( 'The value is taken from client\'s browser.', 'bookly' ) );
18
+ Selects::renderSingle( 'bookly_gen_allow_staff_edit_profile', __( 'Allow staff members to edit their profiles', 'bookly' ), __( 'If this option is enabled then all staff members who are associated with WordPress users will be able to edit their own profiles, services, schedule and days off.', 'bookly' ) );
19
+ Selects::renderSingle( 'bookly_gen_link_assets_method', __( 'Method to include Bookly JavaScript and CSS files on the page', 'bookly' ), __( 'With "Enqueue" method the JavaScript and CSS files of Bookly will be included on all pages of your website. This method should work with all themes. With "Print" method the files will be included only on the pages which contain Bookly booking form. This method may not work with all themes.', 'bookly' ), array( array( 'enqueue', 'Enqueue' ), array( 'print', 'Print' ) ) );
20
+ Selects::renderSingle( 'bookly_gen_collect_stats', __( 'Help us improve Bookly by sending anonymous usage stats', 'bookly' ) );
 
 
 
 
21
  ?>
 
 
 
 
 
 
 
 
22
  <div class="panel-footer">
23
+ <?php ControlInputs::renderCsrf() ?>
24
+ <?php Buttons::renderSubmit() ?>
25
+ <?php Buttons::renderReset() ?>
26
  </div>
27
  </form>
backend/modules/settings/templates/_googleCalendarForm.php DELETED
@@ -1,36 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'google_calendar' ) ) ?>">
3
- <div class="form-group">
4
- <h4 class="bookly-bold"><?php _e( 'Instructions', 'bookly' ) ?></h4>
5
- <p><?php _e( 'To find your client ID and client secret, do the following:', 'bookly' ) ?></p>
6
- <ol>
7
- <li><?php _e( 'Go to the <a href="https://console.developers.google.com/" target="_blank">Google Developers Console</a>.', 'bookly' ) ?></li>
8
- <li><?php _e( 'Select a project, or create a new one.', 'bookly' ) ?></li>
9
- <li><?php _e( 'Click in the upper left part to see a sliding sidebar. Next, click <b>API Manager</b>. In the list of APIs look for <b>Calendar API</b> and make sure it is enabled.', 'bookly' ) ?></li>
10
- <li><?php _e( 'In the sidebar on the left, select <b>Credentials</b>.', 'bookly' ) ?></li>
11
- <li><?php _e( 'Go to <b>OAuth consent screen</b> tab and give a name to the product, then click <b>Save</b>.', 'bookly' ) ?></li>
12
- <li><?php _e( 'Go to <b>Credentials</b> tab and in <b>New credentials</b> drop-down menu select <b>OAuth client ID</b>.', 'bookly' ) ?></li>
13
- <li><?php _e( 'Select <b>Web application</b> and create your project\'s OAuth 2.0 credentials by providing the necessary information. For <b>Authorized redirect URIs</b> enter the <b>Redirect URI</b> found below on this page. Click <b>Create</b>.', 'bookly' ) ?></li>
14
- <li><?php _e( 'In the popup window look for the <b>Client ID</b> and <b>Client secret</b>. Use them in the form below on this page.', 'bookly' ) ?></li>
15
- <li><?php _e( 'Go to Staff Members, select a staff member and click <b>Connect</b> which is located at the bottom of the page.', 'bookly' ) ?></li>
16
- </ol>
17
- </div>
18
- <?php \BooklyLite\Lib\Utils\Common::optionText( 'bookly_gc_client_id', __( 'Client ID', 'bookly' ), __( 'The client ID obtained from the Developers Console', 'bookly' ) ) ?>
19
- <?php \BooklyLite\Lib\Utils\Common::optionText( 'bookly_gc_client_secret', __( 'Client secret', 'bookly' ), __( 'The client secret obtained from the Developers Console', 'bookly' ) ) ?>
20
- <div class="form-group">
21
- <label for="bookly-redirect-uri"><?php _e( 'Redirect URI', 'bookly' ) ?></label>
22
- <p class="help-block"><?php _e( 'Enter this URL as a redirect URI in the Developers Console', 'bookly' ) ?></p>
23
- <input id="bookly-redirect-uri" class="form-control" type="text" readonly
24
- value="<?php echo \BooklyLite\Lib\Google::generateRedirectURI() ?>" onclick="this.select();"
25
- style="cursor: pointer;"/>
26
- </div>
27
- <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'bookly_gc_two_way_sync', __( '2 way sync', 'bookly' ), __( 'By default Bookly pushes new appointments and any further changes to Google Calendar. If you enable this option then Bookly will fetch events from Google Calendar and remove corresponding time slots before displaying the second step of the booking form (this may lead to a delay when users click Next at the first step).', 'bookly' ) ) ?>
28
- <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'bookly_gc_limit_events', __( 'Limit number of fetched events', 'bookly' ), __( 'If there is a lot of events in Google Calendar sometimes this leads to a lack of memory in PHP when Bookly tries to fetch all events. You can limit the number of fetched events here. This only works when 2 way sync is enabled.', 'bookly' ),
29
- $values['bookly_gc_limit_events'] ) ?>
30
- <?php \BooklyLite\Lib\Utils\Common::optionText( 'bookly_gc_event_title', __( 'Template for event title', 'bookly' ), __( 'Configure what information should be placed in the title of Google Calendar event. Available codes are {service_name}, {staff_name} and {client_names}.', 'bookly' ) ) ?>
31
- <div class="panel-footer">
32
- <?php \BooklyLite\Lib\Utils\Common::csrf() ?>
33
- <?php \BooklyLite\Lib\Utils\Common::customButton( null, 'btn btn-lg btn-success bookly-limitation' ) ?>
34
- <?php \BooklyLite\Lib\Utils\Common::resetButton() ?>
35
- </div>
36
- </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/templates/_hoursForm.php CHANGED
@@ -1,13 +1,16 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- $start_of_week = (int) get_option( 'start_of_week' );
3
- $form = new \BooklyLite\Backend\Modules\Settings\Forms\BusinessHours()
 
 
 
4
  ?>
5
  <p>
6
  <?php _e( 'Please note, the business hours below work as a template for all new staff members. To render a list of available time slots the system takes into account only staff members\' schedule, not the company business hours. Be sure to check the schedule of your staff members if you have some unexpected behavior of the booking system.', 'bookly' ) ?>
7
  </p>
8
  <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'business_hours' ) ) ?>" id="business-hours">
9
  <?php for ( $i = 0; $i < 7; $i ++ ) :
10
- $day = strtolower( \BooklyLite\Lib\Utils\DateTime::getWeekDayByNumber( ( $i + $start_of_week ) % 7 ) );
11
  ?>
12
  <div class="row">
13
  <div class="form-group col-sm-7 col-xs-8">
@@ -28,8 +31,8 @@
28
  <?php endfor ?>
29
 
30
  <div class="panel-footer">
31
- <?php \BooklyLite\Lib\Utils\Common::csrf() ?>
32
- <?php \BooklyLite\Lib\Utils\Common::submitButton() ?>
33
- <?php \BooklyLite\Lib\Utils\Common::resetButton( 'bookly-hours-reset' ) ?>
34
  </div>
35
  </form>
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
+ $start_of_week = (int) get_option( 'start_of_week' );
6
+ $form = new Bookly\Backend\Modules\Settings\Forms\BusinessHours()
7
  ?>
8
  <p>
9
  <?php _e( 'Please note, the business hours below work as a template for all new staff members. To render a list of available time slots the system takes into account only staff members\' schedule, not the company business hours. Be sure to check the schedule of your staff members if you have some unexpected behavior of the booking system.', 'bookly' ) ?>
10
  </p>
11
  <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'business_hours' ) ) ?>" id="business-hours">
12
  <?php for ( $i = 0; $i < 7; $i ++ ) :
13
+ $day = strtolower( Bookly\Lib\Utils\DateTime::getWeekDayByNumber( ( $i + $start_of_week ) % 7 ) );
14
  ?>
15
  <div class="row">
16
  <div class="form-group col-sm-7 col-xs-8">
31
  <?php endfor ?>
32
 
33
  <div class="panel-footer">
34
+ <?php Inputs::renderCsrf() ?>
35
+ <?php Buttons::renderSubmit() ?>
36
+ <?php Buttons::renderReset( 'bookly-hours-reset' ) ?>
37
  </div>
38
  </form>
backend/modules/settings/templates/_payment_local.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Settings\Selects;
3
+ ?>
4
+ <div class="panel panel-default bookly-js-collapse" data-slug="local">
5
+ <div class="panel-heading">
6
+ <i class="bookly-js-handle bookly-margin-right-sm bookly-icon bookly-icon-draghandle bookly-cursor-move ui-sortable-handle" title="<?php esc_attr_e( 'Reorder', 'bookly' ) ?>"></i>
7
+ <a href="#bookly_pmt_local" class="panel-title" role="button" data-toggle="collapse">
8
+ <?php _e( 'Service paid locally', 'bookly' ) ?>
9
+ </a>
10
+ </div>
11
+ <div id="bookly_pmt_local" class="panel-collapse collapse in">
12
+ <div class="panel-body">
13
+ <?php Selects::renderSingle( 'bookly_pmt_local' ) ?>
14
+ </div>
15
+ </div>
16
+ </div>
backend/modules/settings/templates/_paymentsForm.php CHANGED
@@ -1,11 +1,12 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Utils\Common;
3
- use BooklyLite\Lib\Utils\Price;
4
- use BooklyLite\Lib\Proxy;
 
5
  ?>
6
  <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'payments' ) ) ?>">
7
  <div class="row">
8
- <div class="col-lg-4">
9
  <div class="form-group">
10
  <label for="bookly_pmt_currency"><?php _e( 'Currency', 'bookly' ) ?></label>
11
  <select id="bookly_pmt_currency" class="form-control" name="bookly_pmt_currency">
@@ -15,7 +16,7 @@ use BooklyLite\Lib\Proxy;
15
  </select>
16
  </div>
17
  </div>
18
- <div class="col-lg-4">
19
  <div class="form-group">
20
  <label for="bookly_pmt_price_format"><?php _e( 'Price format', 'bookly' ) ?></label>
21
  <select id="bookly_pmt_price_format" class="form-control" name="bookly_pmt_price_format">
@@ -25,49 +26,18 @@ use BooklyLite\Lib\Proxy;
25
  </select>
26
  </div>
27
  </div>
28
- <?php Proxy\Coupons::renderSettings() ?>
29
  </div>
30
-
31
- <div class="panel panel-default">
32
- <div class="panel-heading">
33
- <label for="bookly_pmt_local"><?php _e( 'Service paid locally', 'bookly' ) ?></label>
34
- </div>
35
- <div class="panel-body">
36
- <?php Common::optionToggle( 'bookly_pmt_local' ) ?>
37
- </div>
38
- </div>
39
-
40
- <div class="panel panel-default">
41
- <div class="panel-heading">
42
- <label for="bookly_paypal_enabled">PayPal</label>
43
- <img style="margin-left: 10px; float: right" src="<?php echo plugins_url( 'frontend/resources/images/paypal.png', \BooklyLite\Lib\Plugin::getMainFile() ) ?>" />
44
- </div>
45
- <div class="panel-body">
46
- <div class="form-group">
47
- <?php Common::optionToggle( 'bookly_paypal_enabled', null, null,
48
- array(
49
- array( '0', __( 'Disabled', 'bookly' ) ),
50
- array( 'ec', 'PayPal Express Checkout' ),
51
- )
52
- ) ?>
53
- </div>
54
- <div class="bookly-paypal">
55
- <div class="bookly-paypal-ec">
56
- <?php Common::optionText( 'bookly_paypal_api_username', __( 'API Username', 'bookly' ) ) ?>
57
- <?php Common::optionText( 'bookly_paypal_api_password', __( 'API Password', 'bookly' ) ) ?>
58
- <?php Common::optionText( 'bookly_paypal_api_signature', __( 'API Signature', 'bookly' ) ) ?>
59
- </div>
60
- <?php Proxy\PaypalPaymentsStandard::renderSetUpOptions() ?>
61
- <?php Common::optionToggle( 'bookly_paypal_sandbox', __( 'Sandbox Mode', 'bookly' ), null, array( array( 1, __( 'Yes', 'bookly' ) ), array( 0, __( 'No', 'bookly' ) ) ) ) ?>
62
- </div>
63
- </div>
64
  </div>
65
-
66
- <?php BooklyLite\Lib\Proxy\Shared::renderPaymentGatewaySettings() ?>
67
-
68
  <div class="panel-footer">
69
- <?php Common::csrf() ?>
70
- <?php Common::submitButton() ?>
71
- <?php Common::resetButton( 'bookly-payments-reset' ) ?>
 
72
  </div>
73
  </form>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Inputs;
3
+ use Bookly\Backend\Components\Controls\Buttons;
4
+ use Bookly\Lib\Utils\Price;
5
+ use Bookly\Backend\Modules\Settings\Proxy;
6
  ?>
7
  <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'payments' ) ) ?>">
8
  <div class="row">
9
+ <div class="col-lg-6">
10
  <div class="form-group">
11
  <label for="bookly_pmt_currency"><?php _e( 'Currency', 'bookly' ) ?></label>
12
  <select id="bookly_pmt_currency" class="form-control" name="bookly_pmt_currency">
16
  </select>
17
  </div>
18
  </div>
19
+ <div class="col-lg-6">
20
  <div class="form-group">
21
  <label for="bookly_pmt_price_format"><?php _e( 'Price format', 'bookly' ) ?></label>
22
  <select id="bookly_pmt_price_format" class="form-control" name="bookly_pmt_price_format">
26
  </select>
27
  </div>
28
  </div>
 
29
  </div>
30
+ <?php Proxy\DepositPayments::renderPayments() ?>
31
+ <?php Proxy\Taxes::renderPayments() ?>
32
+ <div id="bookly-payment-systems">
33
+ <?php foreach ( $payments as $payment ) : ?>
34
+ <?php echo $payment ?>
35
+ <?php endforeach ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  </div>
 
 
 
37
  <div class="panel-footer">
38
+ <input type="hidden" name="bookly_pmt_order" value="<?php echo get_option( 'bookly_pmt_order' ) ?>"/>
39
+ <?php Inputs::renderCsrf() ?>
40
+ <?php Buttons::renderSubmit() ?>
41
+ <?php Buttons::renderReset( 'bookly-payments-reset' ) ?>
42
  </div>
43
  </form>
backend/modules/settings/templates/_urlForm.php CHANGED
@@ -1,35 +1,24 @@
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- use BooklyLite\Lib\Proxy;
3
- use BooklyLite\Lib\Utils\Common;
 
 
4
  ?>
5
  <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'url' ) ) ?>">
6
  <?php
7
- Common::optionText( 'bookly_url_approve_page_url', __( 'Approve appointment URL (success)', 'bookly' ), __( 'Set the URL of a page that is shown to staff after they successfully approved the appointment.', 'bookly' ) );
8
- Common::optionText( 'bookly_url_approve_denied_page_url', __( 'Approve appointment URL (denied)', 'bookly' ), __( 'Set the URL of a page that is shown to staff when the approval of appointment cannot be done (due to capacity, changed status, etc.).', 'bookly' ) );
9
- Common::optionText( 'bookly_url_cancel_page_url', __( 'Cancel appointment URL (success)', 'bookly' ), __( 'Set the URL of a page that is shown to clients after they successfully cancelled their appointment.', 'bookly' ) );
10
- Common::optionText( 'bookly_url_cancel_denied_page_url', __( 'Cancel appointment URL (denied)', 'bookly' ), __( 'Set the URL of a page that is shown to clients when the cancellation of appointment is not available anymore.', 'bookly' ) );
11
- Common::optionText( 'bookly_url_cancel_confirm_page_url', __( 'Appointment cancellation confirmation URL', 'bookly' ), __( 'Set the URL of an appointment cancellation confirmation page that is shown to clients when they press cancellation link.', 'bookly' ) );
12
- Common::optionText( 'bookly_url_reject_page_url', __( 'Reject appointment URL (success)', 'bookly' ), __( 'Set the URL of a page that is shown to staff after they successfully rejected the appointment.', 'bookly' ) );
13
- Common::optionText( 'bookly_url_reject_denied_page_url', __( 'Reject appointment URL (denied)', 'bookly' ), __( 'Set the URL of a page that is shown to staff when the rejection of appointment cannot be done (due to changed status, etc.).', 'bookly' ) );
 
 
14
  ?>
15
- <div class="form-group">
16
- <label for="bookly_settings_final_step_url_mode"><?php _e( 'Final step URL', 'bookly' ) ?></label>
17
- <p class="help-block"><?php _e( 'Set the URL of a page that the user will be forwarded to after successful booking. If disabled then the default Done step is displayed.', 'bookly' ) ?></p>
18
- <select class="form-control" id="bookly_settings_final_step_url_mode">
19
- <?php foreach ( array( __( 'Disabled', 'bookly' ) => 0, __( 'Enabled', 'bookly' ) => 1 ) as $text => $mode ) : ?>
20
- <option value="<?php echo esc_attr( $mode ) ?>" <?php selected( get_option( 'bookly_url_final_step_url' ), $mode ) ?> ><?php echo $text ?></option>
21
- <?php endforeach ?>
22
- </select>
23
- <input class="form-control"
24
- style="margin-top: 5px; <?php echo get_option( 'bookly_url_final_step_url' ) == '' ? 'display: none' : '' ?>"
25
- type="text" name="bookly_url_final_step_url"
26
- value="<?php form_option( 'bookly_url_final_step_url' ) ?>"
27
- placeholder="<?php esc_attr_e( 'Enter a URL', 'bookly' ) ?>"/>
28
- </div>
29
- <?php Proxy\Shared::renderUrlSettings() ?>
30
  <div class="panel-footer">
31
- <?php Common::csrf() ?>
32
- <?php Common::submitButton() ?>
33
- <?php Common::resetButton() ?>
34
  </div>
35
  </form>
1
  <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ use Bookly\Backend\Components\Controls\Inputs as ControlInputs;
4
+ use Bookly\Backend\Components\Settings\Inputs;
5
+ use Bookly\Backend\Modules\Settings\Proxy;
6
  ?>
7
  <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'url' ) ) ?>">
8
  <?php
9
+ Inputs::renderText( 'bookly_url_approve_page_url', __( 'Approve appointment URL (success)', 'bookly' ), __( 'Set the URL of a page that is shown to staff after they successfully approved the appointment.', 'bookly' ) );
10
+ Inputs::renderText( 'bookly_url_approve_denied_page_url', __( 'Approve appointment URL (denied)', 'bookly' ), __( 'Set the URL of a page that is shown to staff when the approval of appointment cannot be done (due to capacity, changed status, etc.).', 'bookly' ) );
11
+ Inputs::renderText( 'bookly_url_cancel_page_url', __( 'Cancel appointment URL (success)', 'bookly' ), __( 'Set the URL of a page that is shown to clients after they successfully cancelled their appointment.', 'bookly' ) );
12
+ Inputs::renderText( 'bookly_url_cancel_denied_page_url', __( 'Cancel appointment URL (denied)', 'bookly' ), __( 'Set the URL of a page that is shown to clients when the cancellation of appointment is not available anymore.', 'bookly' ) );
13
+ Proxy\Pro::renderCancellationConfirmationUrl();
14
+ Inputs::renderText( 'bookly_url_reject_page_url', __( 'Reject appointment URL (success)', 'bookly' ), __( 'Set the URL of a page that is shown to staff after they successfully rejected the appointment.', 'bookly' ) );
15
+ Inputs::renderText( 'bookly_url_reject_denied_page_url', __( 'Reject appointment URL (denied)', 'bookly' ), __( 'Set the URL of a page that is shown to staff when the rejection of appointment cannot be done (due to changed status, etc.).', 'bookly' ) );
16
+ Proxy\Pro::renderFinalStepUrl();
17
+ Proxy\Shared::renderUrlSettings();
18
  ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  <div class="panel-footer">
20
+ <?php ControlInputs::renderCsrf() ?>
21
+ <?php Buttons::renderSubmit() ?>
22
+ <?php Buttons::renderReset() ?>
23
  </div>
24
  </form>
backend/modules/settings/templates/_woocommerce.php DELETED
@@ -1,45 +0,0 @@
1
- <?php if (!defined('ABSPATH')) exit; // Exit if accessed directly ?>
2
- <form method="post" action="<?php echo esc_url( add_query_arg( 'tab', 'woo_commerce' ) ) ?>"
3
- id="woocommerce">
4
- <div class="form-group">
5
- <h4><?php _e( 'Instructions', 'bookly' ) ?></h4>
6
- <p>
7
- <?php _e( 'You need to install and activate WooCommerce plugin before using the options below.<br/><br/>Once the plugin is activated do the following steps:', 'bookly' ) ?>
8
- </p>
9
- <ol>
10
- <li><?php _e( 'Create a product in WooCommerce that can be placed in cart.', 'bookly' ) ?></li>
11
- <li><?php _e( 'In the form below enable WooCommerce option.', 'bookly' ) ?></li>
12
- <li><?php _e( 'Select the product that you created at step 1 in the drop down list of products.', 'bookly' ) ?></li>
13
- <li><?php _e( 'If needed, edit item data which will be displayed in the cart.', 'bookly' ) ?></li>
14
- </ol>
15
- <p>
16
- <?php _e( 'Note that once you have enabled WooCommerce option in Bookly the built-in payment methods will no longer work. All your customers will be redirected to WooCommerce cart instead of standard payment step.', 'bookly' ) ?>
17
- </p>
18
- </div>
19
-
20
- <?php \BooklyLite\Lib\Utils\Common::optionToggle( 'bookly_wc_enabled', 'WooCommerce' ) ?>
21
-
22
- <div class="form-group">
23
- <label for="bookly_wc_product"><?php _e( 'Booking product', 'bookly' ) ?></label>
24
- <select id="bookly_wc_product" class="form-control" name="bookly_wc_product">
25
- <?php foreach ( $candidates as $item ) : ?>
26
- <option value="<?php echo $item['id'] ?>" <?php selected( get_option( 'bookly_wc_product' ), $item['id'] ) ?>>
27
- <?php echo $item['name'] ?>
28
- </option>
29
- <?php endforeach ?>
30
- </select>
31
- </div>
32
-
33
- <?php \BooklyLite\Lib\Utils\Common::optionText( 'bookly_l10n_wc_cart_info_name', __( 'Cart item data', 'bookly' ) ) ?>
34
- <div class="form-group">
35
- <textarea class="form-control" rows="8" name="bookly_l10n_wc_cart_info_value"
36
- placeholder="<?php _e( 'Enter a value', 'bookly' ) ?>"><?php echo esc_textarea( get_option( 'bookly_l10n_wc_cart_info_value' ) ) ?></textarea><br/>
37
- <?php $this->render( '_woocommerce_codes' ) ?>
38
- </div>
39
-
40
- <div class="panel-footer">
41
- <?php \BooklyLite\Lib\Utils\Common::csrf() ?>
42
- <?php \BooklyLite\Lib\Utils\Common::customButton( null, 'btn btn-lg btn-success bookly-limitation' ) ?>
43
- <?php \BooklyLite\Lib\Utils\Common::resetButton() ?>
44
- </div>
45
- </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/templates/_woocommerce_codes.php DELETED
@@ -1,13 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
- $codes = array(
3
- array( 'code' => 'appointment_date', 'description' => __( 'date of appointment', 'bookly' ), ),
4
- array( 'code' => 'appointment_time', 'description' => __( 'time of appointment', 'bookly' ), ),
5
- array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ), ),
6
- array( 'code' => 'number_of_persons', 'description' => __( 'number of persons', 'bookly' ), ),
7
- array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ), ),
8
- array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ), ),
9
- array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ), ),
10
- array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ), ),
11
- array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ), ),
12
- );
13
- BooklyLite\Lib\Utils\Common::codes( BooklyLite\Lib\Proxy\Shared::prepareWooCommerceShortCodes( $codes ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/settings/templates/index.php CHANGED
@@ -1,11 +1,14 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
 
 
 
2
  <div id="bookly-tbs" class="wrap">
3
  <div class="bookly-tbs-body">
4
  <div class="page-header text-right clearfix">
5
  <div class="bookly-page-title">
6
  <?php _e( 'Settings', 'bookly' ) ?>
7
  </div>
8
- <?php \BooklyLite\Backend\Modules\Support\Components::getInstance()->renderButtons( '' ) ?>
9
  </div>
10
  <div class="row">
11
  <div id="bookly-sidebar" class="col-sm-4">
@@ -25,16 +28,9 @@
25
  <li class="bookly-nav-item" data-target="#bookly_settings_customers" data-toggle="tab">
26
  <?php _e( 'Customers', 'bookly' ) ?>
27
  </li>
28
- <li class="bookly-nav-item" data-target="#bookly_settings_google_calendar" data-toggle="tab">
29
- <?php _e( 'Google Calendar', 'bookly' ) ?>
30
- </li>
31
- <li class="bookly-nav-item" data-target="#bookly_settings_woo_commerce" data-toggle="tab">
32
- WooCommerce
33
- </li>
34
- <li class="bookly-nav-item" data-target="#bookly_settings_cart" data-toggle="tab">
35
- <?php _e( 'Cart', 'bookly' ) ?>
36
- </li>
37
- <?php \BooklyLite\Lib\Proxy\Shared::renderSettingsMenu() ?>
38
  <li class="bookly-nav-item" data-target="#bookly_settings_payments" data-toggle="tab">
39
  <?php _e( 'Payments', 'bookly' ) ?>
40
  </li>
@@ -44,6 +40,7 @@
44
  <li class="bookly-nav-item" data-target="#bookly_settings_holidays" data-toggle="tab">
45
  <?php _e( 'Holidays', 'bookly' ) ?>
46
  </li>
 
47
  </ul>
48
  </div>
49
 
@@ -66,16 +63,9 @@
66
  <div class="tab-pane" id="bookly_settings_customers">
67
  <?php include '_customers.php' ?>
68
  </div>
69
- <div class="tab-pane" id="bookly_settings_google_calendar">
70
- <?php include '_googleCalendarForm.php' ?>
71
- </div>
72
- <div class="tab-pane" id="bookly_settings_woo_commerce">
73
- <?php include '_woocommerce.php' ?>
74
- </div>
75
- <div class="tab-pane" id="bookly_settings_cart">
76
- <?php include '_cartForm.php' ?>
77
- </div>
78
- <?php \BooklyLite\Lib\Proxy\Shared::renderSettingsForm() ?>
79
  <div class="tab-pane" id="bookly_settings_payments">
80
  <?php include '_paymentsForm.php' ?>
81
  </div>
@@ -85,6 +75,7 @@
85
  <div class="tab-pane" id="bookly_settings_holidays">
86
  <?php include '_holidaysForm.php' ?>
87
  </div>
 
88
  </div>
89
  </div>
90
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Modules\Settings\Proxy;
3
+ use Bookly\Backend\Components;
4
+ ?>
5
  <div id="bookly-tbs" class="wrap">
6
  <div class="bookly-tbs-body">
7
  <div class="page-header text-right clearfix">
8
  <div class="bookly-page-title">
9
  <?php _e( 'Settings', 'bookly' ) ?>
10
  </div>
11
+ <?php Components\Support\Buttons::render( '' ) ?>
12
  </div>
13
  <div class="row">
14
  <div id="bookly-sidebar" class="col-sm-4">
28
  <li class="bookly-nav-item" data-target="#bookly_settings_customers" data-toggle="tab">
29
  <?php _e( 'Customers', 'bookly' ) ?>
30
  </li>
31
+ <?php Proxy\Pro::renderGoogleCalendarMenuItem() ?>
32
+ <?php Proxy\ProSettings::renderProMenuItem() ?>
33
+ <?php Proxy\Shared::renderMenuItem() ?>
 
 
 
 
 
 
 
34
  <li class="bookly-nav-item" data-target="#bookly_settings_payments" data-toggle="tab">
35
  <?php _e( 'Payments', 'bookly' ) ?>
36
  </li>
40
  <li class="bookly-nav-item" data-target="#bookly_settings_holidays" data-toggle="tab">
41
  <?php _e( 'Holidays', 'bookly' ) ?>
42
  </li>
43
+ <?php Proxy\Pro::renderPurchaseCodeMenuItem() ?>
44
  </ul>
45
  </div>
46
 
63
  <div class="tab-pane" id="bookly_settings_customers">
64
  <?php include '_customers.php' ?>
65
  </div>
66
+ <?php Proxy\Pro::renderGoogleCalendarTab() ?>
67
+ <?php Proxy\ProSettings::renderProTab() ?>
68
+ <?php Proxy\Shared::renderTab() ?>
 
 
 
 
 
 
 
69
  <div class="tab-pane" id="bookly_settings_payments">
70
  <?php include '_paymentsForm.php' ?>
71
  </div>
75
  <div class="tab-pane" id="bookly_settings_holidays">
76
  <?php include '_holidaysForm.php' ?>
77
  </div>
78
+ <?php Proxy\Pro::renderPurchaseCodeTab() ?>
79
  </div>
80
  </div>
81
  </div>
backend/modules/shop/Ajax.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Shop;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Ajax
8
+ * @package Bookly\Backend\Modules\Shop
9
+ */
10
+ class Ajax extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * Get data for shop page.
14
+ */
15
+ public static function getShopData()
16
+ {
17
+ $response = array();
18
+ $order = self::parameter( 'sort' );
19
+ if ( ! Lib\Entities\Shop::query()->count() ) {
20
+ Lib\Routines::handleDailyInfo();
21
+ $order = 'date';
22
+ }
23
+ $query = Lib\Entities\Shop::query();
24
+ switch ( $order ) {
25
+ case 'sales':
26
+ $query = $query
27
+ ->sortBy( 'sales' )
28
+ ->order( 'DESC' );
29
+ break;
30
+ case 'rating':
31
+ $query = $query
32
+ ->sortBy( 'rating' )
33
+ ->order( 'DESC' );
34
+ break;
35
+ case 'date':
36
+ $query = $query
37
+ ->sortBy( 'published' )
38
+ ->order( 'DESC' );
39
+ break;
40
+ case 'price_low':
41
+ $query = $query
42
+ ->sortBy( 'price' );
43
+ break;
44
+ case 'price_high':
45
+ $query = $query
46
+ ->sortBy( 'price' )
47
+ ->order( 'DESC' );
48
+ break;
49
+ default:
50
+ $query = $query
51
+ ->sortBy( 'type DESC, created' )
52
+ ->order( 'DESC' );
53
+ break;
54
+ }
55
+ $shop = $query->fetchArray();
56
+
57
+ // Get a list of installed plugins
58
+ $plugins_installed = array_keys( apply_filters( 'bookly_plugins', array() ) );
59
+ foreach ( glob( Lib\Plugin::getDirectory() . '/../bookly-addon-*', GLOB_ONLYDIR ) as $path ) {
60
+ $plugins_installed[] = basename( $path );
61
+ }
62
+
63
+ $disabled = Lib\Config::proActive() ? null : ' disabled';
64
+ // Build a list of plugins for a shop page
65
+ $response['shop'] = array();
66
+ foreach ( $shop as $plugin ) {
67
+ $installed = in_array( $plugin['slug'], $plugins_installed );
68
+ $response['shop'][] = array(
69
+ 'plugin_class' => $plugin['type'] == 'bundle' ? 'bookly-shop-bundle' : '',
70
+ 'title' => $plugin['title'],
71
+ 'description' => $plugin['description'],
72
+ 'icon' => '<img src="' . $plugin['icon'] . '"/>',
73
+ 'new' => ( $plugin['seen'] == 0 || ( strtotime( $plugin['published'] ) > strtotime( '-2 weeks' ) ) ) ? __( 'New', 'bookly' ) : '',
74
+ 'price' => '$' . $plugin['price'],
75
+ 'sales' => sprintf( _n( '%d sale', '%d sales', $plugin['sales'], 'bookly' ), $plugin['sales'] ),
76
+ 'rating_class' => (int) $plugin['rating'] ? '': 'collapse',
77
+ 'rating' => $plugin['rating'],
78
+ 'reviews' => sprintf( _n( '%d review', '%d reviews', $plugin['reviews'], 'bookly' ), $plugin['reviews'] ),
79
+ 'url_class' => $installed ? 'btn-default' : $plugin['slug'] == 'bookly-addon-pro' ? 'btn-success' : 'btn-success' . $disabled,
80
+ 'url_text' => $installed ? __( 'Installed', 'bookly' ) : __( 'Get it!', 'bookly' ),
81
+ 'url' => Lib\Utils\Common::prepareUrlReferrers( $plugin['url'] . '?ref=ladela', 'shop' ),
82
+ );
83
+ }
84
+
85
+ // Mark all plugins as seen
86
+ Lib\Entities\Shop::query()->update()->set( 'seen', 1 )->execute();
87
+
88
+ return wp_send_json_success( $response );
89
+ }
90
+ }
backend/modules/shop/Page.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Shop;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Page
8
+ * @package Bookly\Backend\Modules\Shop
9
+ */
10
+ class Page extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render page.
14
+ */
15
+ public static function render()
16
+ {
17
+ self::enqueueStyles( array(
18
+ 'backend' => array(
19
+ 'css/select2.min.css',
20
+ 'bootstrap/css/bootstrap-theme.min.css',
21
+ ),
22
+ 'module' => array( 'css/shop.css', ),
23
+ ) );
24
+
25
+ self::enqueueScripts( array(
26
+ 'backend' => array(
27
+ 'bootstrap/js/bootstrap.min.js' => array( 'jquery' ),
28
+ 'js/select2.full.min.js' => array( 'jquery' ),
29
+ ),
30
+ 'module' => array( 'js/shop.js' => array( 'jquery' ) ),
31
+ ) );
32
+
33
+ wp_localize_script( 'bookly-shop.js', 'BooklyL10n', array(
34
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
35
+ ) );
36
+
37
+ $has_new_items = Lib\Entities\Shop::query()
38
+ ->whereGt( 'published', date_create( 'now' )->modify( '-2 weeks' )->format( 'Y-m-d H:i:s' ) )
39
+ ->where( 'seen', 0, 'OR' )
40
+ ->count();
41
+
42
+ self::renderTemplate( 'index', compact( 'has_new_items' ) );
43
+ }
44
+
45
+ /**
46
+ * @return int
47
+ */
48
+ public static function getNotSeenCount()
49
+ {
50
+ return Lib\Entities\Shop::query()
51
+ ->where( 'seen', 0 )
52
+ ->count();
53
+ }
54
+
55
+ /**
56
+ * Show 'Addons' submenu with counter inside Bookly main menu
57
+ */
58
+ public static function addBooklyMenuItem()
59
+ {
60
+ $title = __( 'Addons', 'bookly' );
61
+ $count = self::getNotSeenCount();
62
+ if ( $count ) {
63
+ add_submenu_page( 'bookly-menu', $title, sprintf( '%s <span class="update-plugins count-%d"><span class="update-count">%d</span></span>', $title, $count, $count ), 'manage_options',
64
+ self::pageSlug(), function () { Page::render(); } );
65
+ } else {
66
+ add_submenu_page( 'bookly-menu', $title, $title, 'manage_options',
67
+ self::pageSlug(), function () { Page::render(); } );
68
+ }
69
+ }
70
+ }
backend/modules/shop/resources/css/shop.css ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #bookly-tbs .panel.bookly-shop-plugin.bookly-shop-bundle {
2
+ border-color: #e0e0a0;
3
+ }
4
+
5
+ #bookly-tbs .panel.bookly-shop-plugin.bookly-shop-bundle .panel-heading {
6
+ background-color: #ffffe0;
7
+ }
8
+
9
+ #bookly-tbs .panel.bookly-shop-plugin .panel-heading .badge-danger {
10
+ background-color: #ff0000;
11
+ }
12
+
13
+ #bookly-tbs .panel.bookly-shop-plugin .panel-heading .bookly-shop-rating {
14
+ color: #ffa500;
15
+ }
backend/modules/shop/resources/js/shop.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(function ($) {
2
+ var $sort = $('#bookly-shop-sort'),
3
+ $shop = $('#bookly-shop'),
4
+ $loading = $('#bookly-shop-loading'),
5
+ $template = $('#bookly-shop-template')
6
+ ;
7
+ $('.bookly-js-select').select2({
8
+ width : '100%',
9
+ theme : 'bootstrap',
10
+ allowClear : true,
11
+ minimumResultsForSearch: -1
12
+ });
13
+ $sort.on('change', function () {
14
+ $loading.show();
15
+ $shop.hide();
16
+ $.ajax({
17
+ url : ajaxurl,
18
+ type : 'GET',
19
+ data : {
20
+ action : 'bookly_get_shop_data',
21
+ csrf_token: BooklyL10n.csrf_token,
22
+ sort : $sort.val()
23
+ },
24
+ dataType: 'json',
25
+ success : function (response) {
26
+ if (response.data.shop.length) {
27
+ $shop.html('');
28
+ $.each(response.data.shop, function (id, plugin) {
29
+ var rating = '';
30
+ for (var i = 0; i < 5; i++) {
31
+ if (plugin.rating - i > 0.5) {
32
+ rating += '<i class="dashicons dashicons-star-filled"></i>';
33
+ } else if (plugin.rating - i > 0) {
34
+ rating += '<i class="dashicons dashicons-star-half"></i>';
35
+ } else {
36
+ rating += '<i class="dashicons dashicons-star-empty"></i>';
37
+ }
38
+ }
39
+ $shop.append(
40
+ $template.clone().show().html()
41
+ .replace(/{{rating}}/g, rating)
42
+ .replace(/{{(.+?)}}/g, function (match) {
43
+ return plugin[match.substring(2, match.length - 2)];
44
+ })
45
+ );
46
+ });
47
+
48
+ }
49
+ $shop.show();
50
+ $loading.hide();
51
+ }
52
+ });
53
+ }).trigger('change');
54
+ });
backend/modules/shop/templates/index.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components;
3
+ use Bookly\Lib as BooklyLib;
4
+ ?>
5
+ <div id="bookly-tbs" class="wrap">
6
+ <div class="bookly-tbs-body">
7
+ <div class="page-header text-right clearfix">
8
+ <div class="bookly-page-title">
9
+ <?php _e( 'Addons', 'bookly' ) ?>
10
+ </div>
11
+ <?php Components\Support\Buttons::render( $self::pageSlug() ) ?>
12
+ </div>
13
+ <div class="panel panel-default bookly-main">
14
+ <div class="panel-body">
15
+ <div class="row">
16
+ <div class="col-sm-2">
17
+ <div class="form-group">
18
+ <select class="form-control bookly-js-select" id="bookly-shop-sort" data-placeholder="<?php echo esc_attr( __( 'Sort by', 'bookly' ) ) ?>">
19
+ <option></option>
20
+ <option value="sales"<?php selected( ! $has_new_items ) ?>><?php esc_html_e( 'Best Sellers', 'bookly' ) ?></option>
21
+ <option value="rating"><?php esc_html_e( 'Best Rated', 'bookly' ) ?></option>
22
+ <option value="date"<?php selected( $has_new_items ) ?>><?php esc_html_e( 'Newest Items', 'bookly' ) ?></option>
23
+ <option value="price_low"><?php esc_html_e( 'Price: low to high', 'bookly' ) ?></option>
24
+ <option value="price_high"><?php esc_html_e( 'Price: high to low', 'bookly' ) ?></option>
25
+ </select>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ <div class="panel-group">
30
+ <div id="bookly-shop" class="collapse"></div>
31
+ </div>
32
+ <div id="bookly-shop-loading" class="bookly-loading"></div>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ <div id="bookly-shop-template" class="collapse">
37
+ <div class="bookly-shop-plugin {{plugin_class}} panel panel-default">
38
+ <div class="panel-heading">
39
+ <div class="row">
40
+ <div class="col-lg-10 col-md-9 col-xs-7">
41
+ <div class="row">
42
+ <div class="col-lg-4 col-md-6 bookly-margin-bottom-md">
43
+ <div class="bookly-flexbox">
44
+ <div class="bookly-flex-cell bookly-margin-bottom-md bookly-vertical-top" style="width: 1%">
45
+ <a href="{{url}}" target="_blank">{{icon}}</a>
46
+ </div>
47
+ <div class="bookly-flex-cell bookly-vertical-top">
48
+ <div class="h2 bookly-margin-top-remove bookly-margin-left-lg"><a href="{{url}}" target="_blank">{{title}}</a> <span class="badge badge-danger">{{new}}</span></div>
49
+ <a class="bookly-margin-bottom-lg bookly-margin-left-lg" href="<?php echo BooklyLib\Utils\Common::prepareUrlReferrers( 'https://codecanyon.net/user/ladela/portfolio?ref=ladela', 'shop' ) ?>" target="_blank">Ladela</a>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ <div class="col-lg-8 col-md-6">{{description}}</div>
54
+ </div>
55
+ </div>
56
+ <div class="col-lg-2 col-md-3 col-xs-5">
57
+ <div class="text-center">
58
+ <div class="h2 bookly-margin-top-remove bookly-margin-bottom-remove">{{price}}</div>
59
+ <div>{{sales}}</div>
60
+ <div class="bookly-shop-rating {{rating_class}}">{{rating}}</div>
61
+ <div class="bookly-margin-bottom-lg">{{reviews}}</div>
62
+ <a href="{{url}}" class="btn btn-lg {{url_class}}" target="_blank"><b>{{url_text}}</b></a><br/>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </div>
backend/modules/sms/Ajax.php ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Sms;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Ajax
8
+ * @package Bookly\Backend\Modules\Sms
9
+ */
10
+ class Ajax extends Lib\Base\Ajax
11
+ {
12
+ /**
13
+ * Get purchases list.
14
+ */
15
+ public static function getPurchasesList()
16
+ {
17
+ $sms = new Lib\SMS();
18
+
19
+ $dates = explode( ' - ', self::parameter( 'range' ), 2 );
20
+ $start = Lib\Utils\DateTime::applyTimeZoneOffset( $dates[0], 0 );
21
+ $end = Lib\Utils\DateTime::applyTimeZoneOffset( date( 'Y-m-d', strtotime( '+1 day', strtotime( $dates[1] ) ) ), 0 );
22
+
23
+ wp_send_json( $sms->getPurchasesList( $start, $end ) );
24
+ }
25
+
26
+ /**
27
+ * Get SMS list.
28
+ */
29
+ public static function getSmsList()
30
+ {
31
+ $sms = new Lib\SMS();
32
+
33
+ $dates = explode( ' - ', self::parameter( 'range' ), 2 );
34
+ $start = Lib\Utils\DateTime::applyTimeZoneOffset( $dates[0], 0 );
35
+ $end = Lib\Utils\DateTime::applyTimeZoneOffset( date( 'Y-m-d', strtotime( '+1 day', strtotime( $dates[1] ) ) ), 0 );
36
+
37
+ wp_send_json( $sms->getSmsList( $start, $end ) );
38
+ }
39
+
40
+ /**
41
+ * Get price-list.
42
+ */
43
+ public static function getPriceList()
44
+ {
45
+ $sms = new Lib\SMS();
46
+ wp_send_json( $sms->getPriceList() );
47
+ }
48
+
49
+ /**
50
+ * Initial for enabling Auto-Recharge balance
51
+ */
52
+ public static function initAutoRecharge()
53
+ {
54
+ $sms = new Lib\SMS();
55
+ $key = $sms->getPreapprovalKey( self::parameter( 'amount' ) );
56
+ if ( $key !== false ) {
57
+ wp_send_json_success( array( 'paypal_preapproval' => 'https://www.paypal.com/cgi-bin/webscr?cmd=_ap-preapproval&preapprovalkey=' . $key ) );
58
+ } else {
59
+ wp_send_json_error( array( 'message' => __( 'Auto-Recharge has failed, please replenish your balance directly.', 'bookly' ) ) );
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Disable Auto-Recharge balance
65
+ */
66
+ public static function declineAutoRecharge()
67
+ {
68
+ $sms = new Lib\SMS();
69
+ $declined = $sms->declinePreapproval();
70
+ if ( $declined !== false ) {
71
+ wp_send_json_success( array( 'message' => __( 'Auto-Recharge disabled', 'bookly' ) ) );
72
+ } else {
73
+ wp_send_json_error( array( 'message' => __( 'Error. Can\'t disable Auto-Recharge, you can perform this action in your PayPal account.', 'bookly' ) ) );
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Change password.
79
+ */
80
+ public static function changePassword()
81
+ {
82
+ $sms = new Lib\SMS();
83
+ $old_password = self::parameter( 'old_password' );
84
+ $new_password = self::parameter( 'new_password' );
85
+
86
+ $result = $sms->changePassword( $new_password, $old_password );
87
+ if ( $result === false ) {
88
+ wp_send_json_error( array( 'message' => current( $sms->getErrors() ) ) );
89
+ } else {
90
+ wp_send_json_success();
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Send test SMS.
96
+ */
97
+ public static function sendTestSms()
98
+ {
99
+ $sms = new Lib\SMS();
100
+
101
+ $response = array( 'success' => $sms->sendSms(
102
+ self::parameter( 'phone_number' ),
103
+ 'Bookly test SMS.',
104
+ Lib\Entities\Notification::$type_ids['test_message']
105
+ ) );
106
+
107
+ if ( $response['success'] ) {
108
+ $response['message'] = __( 'SMS has been sent successfully.', 'bookly' );
109
+ } else {
110
+ $response['message'] = implode( ' ', $sms->getErrors() );
111
+ }
112
+
113
+ wp_send_json( $response );
114
+ }
115
+
116
+ /**
117
+ * Forgot password.
118
+ */
119
+ public static function forgotPassword()
120
+ {
121
+ $sms = new Lib\SMS();
122
+ $step = self::parameter( 'step' );
123
+ $code = self::parameter( 'code' );
124
+ $username = self::parameter( 'username' );
125
+ $password = self::parameter( 'password' );
126
+ $result = $sms->forgotPassword( $username, $step, $code, $password );
127
+ if ( $result === false ) {
128
+ wp_send_json_error( array( 'message' => current( $sms->getErrors() ) ) );
129
+ } else {
130
+ wp_send_json_success();
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Get Sender IDs list.
136
+ */
137
+ public static function getSenderIdsList()
138
+ {
139
+ $sms = new Lib\SMS();
140
+ wp_send_json( $sms->getSenderIdsList() );
141
+ }
142
+
143
+ /**
144
+ * Request new Sender ID.
145
+ */
146
+ public static function requestSenderId()
147
+ {
148
+ $sms = new Lib\SMS();
149
+ $result = $sms->requestSenderId( self::parameter( 'sender_id' ) );
150
+ if ( $result === false ) {
151
+ wp_send_json_error( array( 'message' => current( $sms->getErrors() ) ) );
152
+ } else {
153
+ wp_send_json_success( array( 'request_id' => $result->request_id ) );
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Cancel request for Sender ID.
159
+ */
160
+ public static function cancelSenderId()
161
+ {
162
+ $sms = new Lib\SMS();
163
+ $result = $sms->cancelSenderId();
164
+ if ( $result === false ) {
165
+ wp_send_json_error( array( 'message' => current( $sms->getErrors() ) ) );
166
+ } else {
167
+ wp_send_json_success();
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Reset Sender ID to default (Bookly).
173
+ */
174
+ public static function resetSenderId()
175
+ {
176
+ $sms = new Lib\SMS();
177
+ $result = $sms->resetSenderId();
178
+ if ( $result === false ) {
179
+ wp_send_json_error( array( 'message' => current( $sms->getErrors() ) ) );
180
+ } else {
181
+ wp_send_json_success();
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Enable or Disable administrators email reports.
187
+ */
188
+ public static function adminNotify()
189
+ {
190
+ if ( in_array( self::parameter( 'option_name' ), array( 'bookly_sms_notify_low_balance', 'bookly_sms_notify_weekly_summary' ) ) ) {
191
+ update_option( self::parameter( 'option_name' ), self::parameter( 'value' ) );
192
+ }
193
+ wp_send_json_success();
194
+ }
195
+
196
+ /**
197
+ * Create new custom sms notification
198
+ */
199
+ public static function createCustomSms()
200
+ {
201
+ $notification = new Lib\Entities\Notification();
202
+ $notification
203
+ ->setType( Lib\Entities\Notification::TYPE_APPOINTMENT_START_TIME )
204
+ ->setToCustomer( 1 )
205
+ ->setToStaff( 1 )
206
+ ->setSettings( json_encode( Lib\DataHolders\Notification\Settings::getDefault() ) )
207
+ ->setGateway( 'sms' )
208
+ ->save();
209
+
210
+ $notification = $notification->getFields();
211
+ $id = $notification['id'];
212
+
213
+ $form = new \Bookly\Backend\Modules\Notifications\Forms\Notifications( 'sms' );
214
+ $statuses = Lib\Entities\CustomerAppointment::getStatuses();
215
+
216
+ $html = self::renderTemplate( '_custom_notification', compact( 'form', 'notification', 'statuses' ), false );
217
+ wp_send_json_success( compact( 'html', 'id' ) );
218
+ }
219
+ }
backend/modules/sms/Components.php DELETED
@@ -1,348 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Sms;
3
-
4
- use BooklyLite\Lib;
5
- use BooklyLite\Lib\Entities\Notification;
6
-
7
- /**
8
- * Class Components
9
- * @package BooklyLite\Backend\Modules\Notifications
10
- */
11
- class Components extends Lib\Base\Components
12
- {
13
- protected $css_prefix = 'bookly-js-codes-';
14
-
15
- /**
16
- * Codes for all notifications.
17
- *
18
- * @return array
19
- */
20
- private function getCommonCodes()
21
- {
22
- return array(
23
- array( 'code' => 'company_name', 'description' => __( 'name of company', 'bookly' ) ),
24
- array( 'code' => 'company_address', 'description' => __( 'address of company', 'bookly' ) ),
25
- array( 'code' => 'company_phone', 'description' => __( 'company phone', 'bookly' ) ),
26
- array( 'code' => 'company_website', 'description' => __( 'company web-site address', 'bookly' ) ),
27
- );
28
- }
29
-
30
- /**
31
- * Render codes for notifications
32
- *
33
- * @param string $notification_type
34
- */
35
- public function renderCodes( $notification_type )
36
- {
37
- switch ( $notification_type ) {
38
- case Notification::TYPE_APPOINTMENT_START_TIME:
39
- case Notification::TYPE_CUSTOMER_APPOINTMENT_CREATED:
40
- case Notification::TYPE_LAST_CUSTOMER_APPOINTMENT:
41
- case Notification::TYPE_CUSTOMER_APPOINTMENT_STATUS_CHANGED:
42
- $this->renderCustomerAppointmentCodesForCN( $notification_type );
43
- break;
44
- case 'staff_agenda':
45
- case Notification::TYPE_STAFF_DAY_AGENDA:
46
- $this->renderStaffDayAgendaCodes();
47
- break;
48
- case 'client_birthday_greeting':
49
- case Notification::TYPE_CUSTOMER_BIRTHDAY:
50
- $this->renderBirthdayGreetingCodes();
51
- break;
52
- case 'client_new_wp_user':
53
- $this->renderNewWpUserCodes();
54
- break;
55
- case 'client_pending_appointment_cart':
56
- case 'client_approved_appointment_cart':
57
- $this->renderCompoundCodes();
58
- break;
59
- case 'staff_waiting_list':
60
- $this->renderWaitingListCodes();
61
- break;
62
- case 'client_pending_appointment':
63
- case 'staff_pending_appointment':
64
- case 'client_approved_appointment':
65
- case 'staff_approved_appointment':
66
- case 'client_cancelled_appointment':
67
- case 'staff_cancelled_appointment':
68
- case 'client_rejected_appointment':
69
- case 'staff_rejected_appointment':
70
- case 'client_waitlisted_appointment':
71
- case 'staff_waitlisted_appointment':
72
- case 'client_reminder':
73
- case 'client_reminder_1st':
74
- case 'client_reminder_2nd':
75
- case 'client_reminder_3rd':
76
- case 'client_follow_up':
77
- $this->renderBaseCodes();
78
- break;
79
- case 'staff_package_purchased':
80
- case 'client_package_purchased':
81
- case 'staff_package_deleted':
82
- case 'client_package_deleted':
83
- $this->renderPackageCodes( $notification_type );
84
- break;
85
- case 'client_pending_recurring_appointment':
86
- case 'staff_pending_recurring_appointment':
87
- case 'client_approved_recurring_appointment':
88
- case 'staff_approved_recurring_appointment':
89
- case 'client_cancelled_recurring_appointment':
90
- case 'staff_cancelled_recurring_appointment':
91
- case 'client_rejected_recurring_appointment':
92
- case 'staff_rejected_recurring_appointment':
93
- case 'client_waitlisted_recurring_appointment':
94
- case 'staff_waitlisted_recurring_appointment':
95
- $this->renderRecurringCodes();
96
- break;
97
- }
98
- }
99
-
100
- /**
101
- * Render codes for custom notifications with appointment(s)
102
- *
103
- * @param string $notification_type
104
- */
105
- private function renderCustomerAppointmentCodesForCN( $notification_type )
106
- {
107
- $codes = $this->getCommonCodes();
108
- $codes[] = array( 'code' => 'appointment_date', 'description' => __( 'date of appointment', 'bookly' ) );
109
- $codes[] = array( 'code' => 'appointment_end_date', 'description' => __( 'end date of appointment', 'bookly' ) );
110
- $codes[] = array( 'code' => 'appointment_end_time', 'description' => __( 'end time of appointment', 'bookly' ) );
111
- $codes[] = array( 'code' => 'appointment_notes', 'description' => __( 'customer notes for appointment', 'bookly' ) );
112
- $codes[] = array( 'code' => 'appointment_time', 'description' => __( 'time of appointment', 'bookly' ) );
113
- $codes[] = array( 'code' => 'approve_appointment_url', 'description' => esc_html__( 'URL of approve appointment link (to use inside <a> tag)', 'bookly' ) );
114
- $codes[] = array( 'code' => 'booking_number', 'description' => __( 'booking number', 'bookly' ) );
115
- $codes[] = array( 'code' => 'cancel_appointment_confirm_url', 'description' => esc_html__( 'URL of cancel appointment link with confirmation (to use inside <a> tag)', 'bookly' ) );
116
- $codes[] = array( 'code' => 'cancel_appointment_url', 'description' => esc_html__( 'URL of cancel appointment link (to use inside <a> tag)', 'bookly' ) );
117
- $codes[] = array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) );
118
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
119
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
120
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
121
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
122
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
123
- $codes[] = array( 'code' => 'google_calendar_url', 'description' => esc_html__( 'URL for adding event to client\'s Google Calendar (to use inside <a> tag)', 'bookly' ) );
124
- $codes[] = array( 'code' => 'number_of_persons', 'description' => __( 'number of persons', 'bookly' ) );
125
- $codes[] = array( 'code' => 'payment_type', 'description' => __( 'payment type', 'bookly' ) );
126
- $codes[] = array( 'code' => 'reject_appointment_url', 'description' => esc_html__( 'URL of reject appointment link (to use inside <a> tag)', 'bookly' ) );
127
- $codes[] = array( 'code' => 'service_duration', 'description' => __( 'duration of service', 'bookly' ) );
128
- $codes[] = array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) );
129
- $codes[] = array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) );
130
- $codes[] = array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ) );
131
- $codes[] = array( 'code' => 'staff_email', 'description' => __( 'email of staff', 'bookly' ) );
132
- $codes[] = array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) );
133
- $codes[] = array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) );
134
- $codes[] = array( 'code' => 'staff_phone', 'description' => __( 'phone of staff', 'bookly' ) );
135
- $codes[] = array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) );
136
-
137
- $codes = Lib\Proxy\Packages::prepareNotificationCodesList( $codes );
138
- $codes = Lib\Proxy\RecurringAppointments::prepareNotificationCodesList( $codes );
139
-
140
- Lib\Utils\Common::codes(
141
- Lib\Proxy\Shared::prepareNotificationCodesList( $codes, 'customer' ),
142
- array( 'type' => $notification_type )
143
- );
144
- }
145
-
146
- /**
147
- * Render codes for staff agenda.
148
- */
149
- private function renderStaffDayAgendaCodes()
150
- {
151
- $codes = $this->getCommonCodes();
152
- $codes[] = array( 'code' => 'agenda_date', 'description' => __( 'agenda date', 'bookly' ) );
153
- $codes[] = array( 'code' => 'next_day_agenda', 'description' => __( 'staff agenda for next day', 'bookly' ) );
154
- $codes[] = array( 'code' => 'staff_email', 'description' => __( 'email of staff', 'bookly' ) );
155
- $codes[] = array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) );
156
- $codes[] = array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) );
157
- $codes[] = array( 'code' => 'staff_phone', 'description' => __( 'phone of staff', 'bookly' ) );
158
- $codes[] = array( 'code' => 'tomorrow_date', 'description' => __( 'date of next day', 'bookly' ) );
159
-
160
- Lib\Utils\Common::codes(
161
- $codes,
162
- array( 'type' => Notification::TYPE_STAFF_DAY_AGENDA )
163
- );
164
- }
165
-
166
- /**
167
- * Render codes for Greeting notifications
168
- */
169
- private function renderBirthdayGreetingCodes()
170
- {
171
- $codes = $this->getCommonCodes();
172
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
173
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
174
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
175
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
176
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
177
-
178
- Lib\Utils\Common::codes(
179
- $codes,
180
- array( 'type' => Notification::TYPE_CUSTOMER_BIRTHDAY )
181
- );
182
- }
183
-
184
- /**
185
- * Render codes for new WordPress users.
186
- */
187
- private function renderNewWpUserCodes()
188
- {
189
- $codes = $this->getCommonCodes();
190
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
191
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
192
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
193
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
194
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
195
- $codes[] = array( 'code' => 'new_password', 'description' => __( 'customer new password', 'bookly' ) );
196
- $codes[] = array( 'code' => 'new_username', 'description' => __( 'customer new username', 'bookly' ) );
197
- $codes[] = array( 'code' => 'site_address', 'description' => __( 'site address', 'bookly' ) );
198
-
199
- Lib\Utils\Common::codes( $codes );
200
- }
201
-
202
- /**
203
- * Render codes for compound (cart) notifications.
204
- */
205
- private function renderCompoundCodes()
206
- {
207
- $codes = $this->getCommonCodes();
208
- $codes[] = array( 'code' => 'cart_info', 'description' => __( 'cart information', 'bookly' ) );
209
- $codes[] = array( 'code' => 'cart_info_c', 'description' => __( 'cart information with cancel', 'bookly' ) );
210
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
211
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
212
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
213
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
214
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
215
- $codes[] = array( 'code' => 'payment_type', 'description' => __( 'payment type', 'bookly' ) );
216
- $codes[] = array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) );
217
-
218
- Lib\Utils\Common::codes( Lib\Proxy\Shared::prepareCartNotificationShortCodes( $codes ) );
219
- }
220
-
221
- /**
222
- * Render base codes
223
- */
224
- private function renderBaseCodes()
225
- {
226
- $codes = $this->getCommonCodes();
227
- $codes[] = array( 'code' => 'appointment_date', 'description' => __( 'date of appointment', 'bookly' ) );
228
- $codes[] = array( 'code' => 'appointment_end_date', 'description' => __( 'end date of appointment', 'bookly' ) );
229
- $codes[] = array( 'code' => 'appointment_end_time', 'description' => __( 'end time of appointment', 'bookly' ) );
230
- $codes[] = array( 'code' => 'appointment_notes', 'description' => __( 'customer notes for appointment', 'bookly' ) );
231
- $codes[] = array( 'code' => 'appointment_time', 'description' => __( 'time of appointment', 'bookly' ) );
232
- $codes[] = array( 'code' => 'approve_appointment_url', 'description' => esc_html__( 'URL of approve appointment link (to use inside <a> tag)', 'bookly' ) );
233
- $codes[] = array( 'code' => 'booking_number', 'description' => __( 'booking number', 'bookly' ) );
234
- $codes[] = array( 'code' => 'cancel_appointment_confirm_url', 'description' => esc_html__( 'URL of cancel appointment link with confirmation (to use inside <a> tag)', 'bookly' ) );
235
- $codes[] = array( 'code' => 'cancel_appointment_url', 'description' => esc_html__( 'URL of cancel appointment link (to use inside <a> tag)', 'bookly' ) );
236
- $codes[] = array( 'code' => 'cancellation_reason', 'description' => __( 'reason you mentioned while deleting appointment', 'bookly' ) );
237
- $codes[] = array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) );
238
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
239
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
240
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
241
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
242
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
243
- $codes[] = array( 'code' => 'google_calendar_url', 'description' => esc_html__( 'URL for adding event to client\'s Google Calendar (to use inside <a> tag)', 'bookly' ) );
244
- $codes[] = array( 'code' => 'number_of_persons', 'description' => __( 'number of persons', 'bookly' ) );
245
- $codes[] = array( 'code' => 'payment_type', 'description' => __( 'payment type', 'bookly' ) );
246
- $codes[] = array( 'code' => 'reject_appointment_url', 'description' => esc_html__( 'URL of reject appointment link (to use inside <a> tag)', 'bookly' ) );
247
- $codes[] = array( 'code' => 'service_duration', 'description' => __( 'duration of service', 'bookly' ) );
248
- $codes[] = array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) );
249
- $codes[] = array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) );
250
- $codes[] = array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ) );
251
- $codes[] = array( 'code' => 'staff_email', 'description' => __( 'email of staff', 'bookly' ) );
252
- $codes[] = array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) );
253
- $codes[] = array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) );
254
- $codes[] = array( 'code' => 'staff_phone', 'description' => __( 'phone of staff', 'bookly' ) );
255
- $codes[] = array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) );
256
-
257
- Lib\Utils\Common::codes(
258
- Lib\Proxy\Shared::prepareNotificationCodesList( $codes, 'customer' )
259
- );
260
- }
261
-
262
- /**
263
- * Render codes notifications about package appointments
264
- *
265
- * @param string $notification_type
266
- */
267
- private function renderPackageCodes( $notification_type )
268
- {
269
- $codes = $this->getCommonCodes();
270
- $codes[] = array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) );
271
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
272
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
273
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
274
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
275
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
276
- $codes[] = array( 'code' => 'service_duration', 'description' => __( 'duration of service', 'bookly' ) );
277
- $codes[] = array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) );
278
- $codes[] = array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) );
279
- $codes[] = array( 'code' => 'staff_email', 'description' => __( 'email of staff', 'bookly' ) );
280
- $codes[] = array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) );
281
- $codes[] = array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) );
282
- $codes[] = array( 'code' => 'staff_phone', 'description' => __( 'phone of staff', 'bookly' ) );
283
-
284
- $codes = Lib\Proxy\Packages::prepareNotificationCodesList( $codes, '', $notification_type );
285
-
286
- Lib\Utils\Common::codes( $codes );
287
- }
288
-
289
- /**
290
- * Render codes notifications wor appointments in waiting list
291
- */
292
- private function renderWaitingListCodes()
293
- {
294
- $codes = $this->getCommonCodes();
295
- $codes[] = array( 'code' => 'appointment_date', 'description' => __( 'date of appointment', 'bookly' ) );
296
- $codes[] = array( 'code' => 'appointment_end_date', 'description' => __( 'end date of appointment', 'bookly' ) );
297
- $codes[] = array( 'code' => 'appointment_end_time', 'description' => __( 'end time of appointment', 'bookly' ) );
298
- $codes[] = array( 'code' => 'appointment_time', 'description' => __( 'time of appointment', 'bookly' ) );
299
- $codes[] = array( 'code' => 'appointment_waiting_list', 'description' => __( 'waiting list of appointment', 'bookly-waiting-list' ) );
300
- $codes[] = array( 'code' => 'booking_number', 'description' => __( 'booking number', 'bookly' ) );
301
- $codes[] = array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) );
302
- $codes[] = array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) );
303
- $codes[] = array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) );
304
- $codes[] = array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ) );
305
- $codes[] = array( 'code' => 'service_duration', 'description' => __( 'duration of service', 'bookly' ) );
306
- $codes[] = array( 'code' => 'staff_email', 'description' => __( 'email of staff', 'bookly' ) );
307
- $codes[] = array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) );
308
- $codes[] = array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) );
309
- $codes[] = array( 'code' => 'staff_phone', 'description' => __( 'phone of staff', 'bookly' ) );
310
-
311
- $codes = Lib\Proxy\WaitingList::prepareNotificationCodesList( $codes );
312
-
313
- Lib\Utils\Common::codes( Lib\Proxy\Shared::prepareNotificationCodesList( $codes, 'appointment' ) );
314
- }
315
-
316
- private function renderRecurringCodes()
317
- {
318
- $codes = $this->getCommonCodes();
319
- $codes[] = array( 'code' => 'appointment_date', 'description' => __( 'date of appointment', 'bookly' ) );
320
- $codes[] = array( 'code' => 'appointment_end_date','description' => __( 'end date of appointment', 'bookly' ) );
321
- $codes[] = array( 'code' => 'appointment_end_time','description' => __( 'end time of appointment', 'bookly' ) );
322
- $codes[] = array( 'code' => 'appointment_time', 'description' => __( 'time of appointment', 'bookly' ) );
323
- $codes[] = array( 'code' => 'booking_number', 'description' => __( 'booking number', 'bookly' ) );
324
- $codes[] = array( 'code' => 'category_name', 'description' => __( 'name of category', 'bookly' ) );
325
- $codes[] = array( 'code' => 'client_email', 'description' => __( 'email of client', 'bookly' ) );
326
- $codes[] = array( 'code' => 'client_first_name', 'description' => __( 'first name of client', 'bookly' ) );
327
- $codes[] = array( 'code' => 'client_last_name', 'description' => __( 'last name of client', 'bookly' ) );
328
- $codes[] = array( 'code' => 'client_name', 'description' => __( 'full name of client', 'bookly' ) );
329
- $codes[] = array( 'code' => 'client_phone', 'description' => __( 'phone of client', 'bookly' ) );
330
- $codes[] = array( 'code' => 'google_calendar_url', 'description' => esc_html__( 'URL for adding event to client\'s Google Calendar (to use inside <a> tag)', 'bookly' ) );
331
- $codes[] = array( 'code' => 'number_of_persons', 'description' => __( 'number of persons', 'bookly' ) );
332
- $codes[] = array( 'code' => 'payment_type', 'description' => __( 'payment type', 'bookly' ) );
333
- $codes[] = array( 'code' => 'service_duration', 'description' => __( 'duration of service', 'bookly' ) );
334
- $codes[] = array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) );
335
- $codes[] = array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) );
336
- $codes[] = array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ) );
337
- $codes[] = array( 'code' => 'staff_email', 'description' => __( 'email of staff', 'bookly' ) );
338
- $codes[] = array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) );
339
- $codes[] = array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) );
340
- $codes[] = array( 'code' => 'staff_phone', 'description' => __( 'phone of staff', 'bookly' ) );
341
- $codes[] = array( 'code' => 'total_price', 'description' => __( 'total price of booking (sum of all cart items after applying coupon)', 'bookly' ) );
342
-
343
- $codes = Lib\Proxy\RecurringAppointments::prepareNotificationCodesList( $codes );
344
-
345
- Lib\Utils\Common::codes( Lib\Proxy\Shared::prepareNotificationCodesList( $codes, 'customer' ) );
346
- }
347
-
348
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/modules/sms/Controller.php DELETED
@@ -1,360 +0,0 @@
1
- <?php
2
- namespace BooklyLite\Backend\Modules\Sms;
3
-
4
- use BooklyLite\Lib;
5
-
6
- /**
7
- * Class Controller
8
- * @package BooklyLite\Backend\Modules\Sms
9
- */
10
- class Controller extends Lib\Base\Controller
11
- {
12
- const page_slug = 'bookly-sms';
13
-
14
- public function index()
15
- {
16
- global $wp_locale;
17
-
18
- $this->enqueueStyles( array(
19
- 'frontend' => array_merge(
20
- array( 'css/ladda.min.css', ),
21
- get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
22
- ? array()
23
-