WordPress Online Booking and Scheduling Plugin – Bookly - Version 16.2

Version Description

Download this release

Release Info

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

Code changes from version 16.1 to 16.2

Files changed (127) hide show
  1. autoload.php +18 -18
  2. backend/Backend.php +117 -117
  3. backend/components/appearance/Codes.php +39 -40
  4. backend/components/appearance/Editable.php +112 -108
  5. backend/components/appearance/proxy/Pro.php +15 -15
  6. backend/components/appearance/proxy/Shared.php +14 -14
  7. backend/components/controls/Buttons.php +126 -126
  8. backend/components/controls/Inputs.php +21 -21
  9. backend/components/dialogs/appointment/attach_payment/proxy/Pro.php +14 -14
  10. backend/components/dialogs/appointment/attach_payment/proxy/Taxes.php +14 -14
  11. backend/components/dialogs/appointment/customer_details/Dialog.php +18 -18
  12. backend/components/dialogs/appointment/customer_details/proxy/Files.php +14 -14
  13. backend/components/dialogs/appointment/customer_details/proxy/Pro.php +14 -14
  14. backend/components/dialogs/appointment/customer_details/proxy/Shared.php +14 -14
  15. backend/components/dialogs/appointment/customer_details/templates/customer_details.php +53 -53
  16. backend/components/dialogs/appointment/delete/Ajax.php +55 -55
  17. backend/components/dialogs/appointment/delete/Dialog.php +32 -32
  18. backend/components/dialogs/appointment/delete/resources/js/delete_dialog.js +8 -8
  19. backend/components/dialogs/appointment/delete/templates/delete.php +28 -28
  20. backend/components/dialogs/appointment/edit/Ajax.php +852 -807
  21. backend/components/dialogs/appointment/edit/Dialog.php +63 -63
  22. backend/components/dialogs/appointment/edit/proxy/Pro.php +16 -16
  23. backend/components/dialogs/appointment/edit/proxy/RecurringAppointments.php +16 -16
  24. backend/components/dialogs/appointment/edit/proxy/Shared.php +18 -18
  25. backend/components/dialogs/appointment/edit/proxy/Tasks.php +14 -14
  26. backend/components/dialogs/appointment/edit/resources/js/ng-appointment.js +1371 -1340
  27. backend/components/dialogs/appointment/edit/templates/edit.php +253 -241
  28. backend/components/dialogs/common/CascadeDelete.php +29 -29
  29. backend/components/dialogs/common/UnsavedChanges.php +29 -29
  30. backend/components/dialogs/common/templates/delete_cascade.php +20 -20
  31. backend/components/dialogs/common/templates/unsaved_changes.php +20 -20
  32. backend/components/dialogs/customer/delete/Ajax.php +130 -0
  33. backend/components/dialogs/customer/delete/Dialog.php +36 -0
  34. backend/components/dialogs/customer/delete/resources/js/delete-customers.js +94 -0
  35. backend/components/dialogs/customer/delete/templates/dialog.php +42 -0
  36. backend/components/dialogs/customer/{EditAjax.php → edit/Ajax.php} +102 -102
  37. backend/components/dialogs/customer/{Edit.php → edit/Dialog.php} +62 -60
  38. backend/components/dialogs/customer/{forms → edit/forms}/Customer.php +36 -36
  39. backend/components/dialogs/customer/edit/proxy/CustomerGroups.php +15 -0
  40. backend/components/dialogs/customer/edit/proxy/CustomerInformation.php +16 -0
  41. backend/components/dialogs/customer/edit/proxy/Pro.php +16 -0
  42. backend/components/dialogs/customer/{resources → edit/resources}/js/ng-customer.js +214 -196
  43. backend/components/dialogs/customer/{templates → edit/templates}/edit.php +82 -83
  44. backend/components/dialogs/customer/proxy/CustomerGroups.php +13 -15
  45. backend/components/dialogs/customer/proxy/CustomerInformation.php +13 -15
  46. backend/components/dialogs/customer/proxy/Pro.php +13 -15
  47. backend/components/dialogs/payment/Ajax.php +104 -104
  48. backend/components/dialogs/payment/Dialog.php +31 -31
  49. backend/components/dialogs/payment/resources/js/ng-payment_details.js +150 -150
  50. backend/components/dialogs/payment/templates/details.php +216 -216
  51. backend/components/dialogs/payment/templates/dialog.php +20 -20
  52. backend/components/dialogs/special_price/proxy/SpecialHours.php +13 -13
  53. backend/components/notices/CollectStats.php +46 -46
  54. backend/components/notices/CollectStatsAjax.php +40 -40
  55. backend/components/notices/Limitation.php +18 -18
  56. backend/components/notices/LiteRebranding.php +26 -26
  57. backend/components/notices/LiteRebrandingAjax.php +20 -20
  58. backend/components/notices/Nps.php +47 -47
  59. backend/components/notices/NpsAjax.php +39 -39
  60. backend/components/notices/Subscribe.php +37 -37
  61. backend/components/notices/SubscribeAjax.php +45 -35
  62. backend/components/notices/proxy/Pro.php +14 -14
  63. backend/components/notices/resources/css/bootstrap-stars.css +38 -38
  64. backend/components/notices/resources/js/collect-stats.js +8 -8
  65. backend/components/notices/resources/js/lite-rebranding.js +5 -5
  66. backend/components/notices/resources/js/nps.js +59 -59
  67. backend/components/notices/resources/js/subscribe.js +23 -23
  68. backend/components/notices/templates/collect_stats.php +22 -22
  69. backend/components/notices/templates/limitation.php +2 -2
  70. backend/components/notices/templates/lite_rebranding.php +11 -11
  71. backend/components/notices/templates/nps.php +40 -40
  72. backend/components/notices/templates/subscribe.php +20 -20
  73. backend/components/settings/Image.php +29 -29
  74. backend/components/settings/Inputs.php +98 -75
  75. backend/components/settings/Menu.php +22 -22
  76. backend/components/settings/Payments.php +40 -40
  77. backend/components/settings/Selects.php +79 -79
  78. backend/components/settings/proxy/Pro.php +14 -14
  79. backend/components/settings/proxy/Taxes.php +14 -14
  80. backend/components/settings/templates/image.php +47 -47
  81. backend/components/settings/templates/price_correction.php +17 -17
  82. backend/components/sms/custom/Notification.php +46 -0
  83. backend/components/sms/custom/templates/after_event.php +93 -0
  84. backend/components/sms/custom/templates/existing_event_with_date.php +31 -0
  85. backend/components/sms/custom/templates/existing_event_with_date_and_time.php +93 -0
  86. backend/components/sms/custom/templates/existing_event_with_date_before.php +28 -0
  87. backend/components/sms/custom/templates/layout.php +125 -0
  88. backend/components/support/Buttons.php +74 -73
  89. backend/components/support/ButtonsAjax.php +87 -77
  90. backend/components/support/lib/Urls.php +11 -10
  91. backend/components/support/resources/js/support.js +173 -135
  92. backend/components/support/templates/_email_to_support.php +18 -18
  93. backend/components/support/templates/buttons.php +126 -85
  94. backend/components/tiny_mce/Tools.php +58 -58
  95. backend/components/tiny_mce/proxy/DepositPayments.php +14 -14
  96. backend/components/tiny_mce/proxy/GroupBooking.php +14 -14
  97. backend/components/tiny_mce/proxy/Shared.php +17 -17
  98. backend/components/tiny_mce/proxy/SpecialDays.php +14 -14
  99. backend/components/tiny_mce/proxy/SpecialHours.php +14 -14
  100. backend/components/tiny_mce/templates/bookly_form.php +424 -423
  101. backend/modules/appearance/Ajax.php +135 -131
  102. backend/modules/appearance/Page.php +107 -102
  103. backend/modules/appearance/proxy/Cart.php +16 -16
  104. backend/modules/appearance/proxy/ChainAppointments.php +13 -13
  105. backend/modules/appearance/proxy/Coupons.php +15 -15
  106. backend/modules/appearance/proxy/CustomDuration.php +14 -14
  107. backend/modules/appearance/proxy/CustomFields.php +14 -14
  108. backend/modules/appearance/proxy/CustomerInformation.php +14 -14
  109. backend/modules/appearance/proxy/DepositPayments.php +14 -14
  110. backend/modules/appearance/proxy/Files.php +15 -15
  111. backend/modules/appearance/proxy/GoogleMapsAddress.php +14 -14
  112. backend/modules/appearance/proxy/GroupBooking.php +15 -15
  113. backend/modules/appearance/proxy/Locations.php +15 -15
  114. backend/modules/appearance/proxy/MultiplyAppointments.php +15 -15
  115. backend/modules/appearance/proxy/Pro.php +22 -22
  116. backend/modules/appearance/proxy/RecurringAppointments.php +17 -17
  117. backend/modules/appearance/proxy/ServiceExtras.php +17 -17
  118. backend/modules/appearance/proxy/Shared.php +18 -18
  119. backend/modules/appearance/proxy/Tasks.php +15 -0
  120. backend/modules/appearance/proxy/WaitingList.php +14 -14
  121. backend/modules/appearance/resources/css/bootstrap-editable.css +663 -663
  122. backend/modules/appearance/resources/js/appearance.js +837 -781
  123. backend/modules/appearance/resources/js/bootstrap-editable.bookly.js +124 -124
  124. backend/modules/appearance/resources/js/bootstrap-editable.min.js +6 -6
  125. backend/modules/appearance/templates/_1_service.php +166 -165
  126. backend/modules/appearance/templates/_3_time.php +207 -204
  127. backend/modules/appearance/templates/_6_details.php +0 -78
autoload.php CHANGED
@@ -1,19 +1,19 @@
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]
13
- . '.php';
14
- if ( is_readable( $file ) ) {
15
- require_once $file;
16
- }
17
- }
18
- }
19
  spl_autoload_register( 'bookly_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]
13
+ . '.php';
14
+ if ( is_readable( $file ) ) {
15
+ require_once $file;
16
+ }
17
+ }
18
+ }
19
  spl_autoload_register( 'bookly_loader', true, true );
backend/Backend.php CHANGED
@@ -1,118 +1,118 @@
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
  }
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'] ) && strncmp( $_REQUEST['page'], 'bookly-', 7 ) === 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 CHANGED
@@ -1,41 +1,40 @@
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
  }
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' => 'service_date', 'description' => __( 'date of service', 'bookly' ), 'flags' => array( 'step' => '>3' ) ),
27
+ array( 'code' => 'service_duration', 'description' => __( 'duration of service', 'bookly' ) ),
28
+ array( 'code' => 'service_info', 'description' => __( 'info of service', 'bookly' ) ),
29
+ array( 'code' => 'service_name', 'description' => __( 'name of service', 'bookly' ) ),
30
+ array( 'code' => 'service_price', 'description' => __( 'price of service', 'bookly' ) ),
31
+ array( 'code' => 'service_time', 'description' => __( 'time of service', 'bookly' ), 'flags' => array( 'step' => '>3' ) ),
32
+ array( 'code' => 'staff_info', 'description' => __( 'info of staff', 'bookly' ) ),
33
+ array( 'code' => 'staff_name', 'description' => __( 'name of staff', 'bookly' ) ),
34
+ array( 'code' => 'staff_photo', 'description' => __( 'photo of staff', 'bookly' ), 'flags' => array( 'step' => '>1' ) ),
35
+ array( 'code' => 'total_price', 'description' => __( 'total price of booking', 'bookly' ) ),
36
+ );
37
+
38
+ return Lib\Utils\Common::codes( Proxy\Shared::prepareCodes( $codes ), array( 'step' => $step, 'extra_codes' => isset ( $extra_codes ) ) );
39
+ }
 
40
  }
backend/components/appearance/Editable.php CHANGED
@@ -1,109 +1,113 @@
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
  }
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" data-option="%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
+ $option_name,
53
+ esc_html( $option_value )
54
+ );
55
+ }
56
+
57
+ /**
58
+ * Render editable number.
59
+ *
60
+ * @param string $option_name
61
+ * @param int $min
62
+ * @param int $step
63
+ */
64
+ public static function renderNumber( $option_name, $min = 1, $step = 1 )
65
+ {
66
+ $option_value = get_option( $option_name );
67
+
68
+ 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" data-option="%s">%s</span>',
69
+ $option_name,
70
+ esc_attr( json_encode( array( $option_name => $option_value ) ) ),
71
+ esc_attr( $min ),
72
+ esc_attr( $step ),
73
+ $option_name,
74
+ esc_html( $option_value )
75
+ );
76
+ }
77
+
78
+ /**
79
+ * Render editable element.
80
+ *
81
+ * @param array $options
82
+ * @param string $tag
83
+ * @param bool $echo
84
+ * @return string|void
85
+ */
86
+ private static function _renderEditable( array $options, $tag, $echo = true )
87
+ {
88
+ $data = array();
89
+ foreach ( $options as $option_name ) {
90
+ $data[ $option_name ] = get_option( $option_name );
91
+ }
92
+
93
+ $main_option = $options[0];
94
+ $class = implode( ' ', $options );
95
+ $data_values = esc_attr( json_encode( $data ) );
96
+ $content = esc_html( $data[ $options[0] ] );
97
+
98
+ $template = '<{tag} class="bookly-js-editable bookly-js-option {class}" data-type="bookly" data-values="{data-values}" data-option="{data-option}">{content}</{tag}>';
99
+ $html = strtr( $template, array(
100
+ '{tag}' => $tag,
101
+ '{class}' => $class,
102
+ '{data-values}' => $data_values,
103
+ '{data-option}' => $main_option,
104
+ '{content}' => $content,
105
+ ) );
106
+
107
+ if ( ! $echo ) {
108
+ return $html;
109
+ }
110
+
111
+ echo $html;
112
+ }
113
  }
backend/components/appearance/proxy/Pro.php CHANGED
@@ -1,16 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,127 +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
  }
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 CHANGED
@@ -1,22 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,19 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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/components/dialogs/appointment/customer_details/templates/customer_details.php CHANGED
@@ -1,54 +1,54 @@
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">
10
- <div class="modal-content">
11
- <div class="modal-header">
12
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
13
- <div class="modal-title h2"><?php _e( 'Edit booking details', 'bookly' ) ?></div>
14
- </div>
15
- <form ng-hide=loading style="z-index: 1050">
16
- <div class="modal-body">
17
- <div class="form-group">
18
- <label for="bookly-appointment-status"><?php _e( 'Status', 'bookly' ) ?></label>
19
- <select class="bookly-custom-field form-control" id="bookly-appointment-status">
20
- <option value="<?php echo CustomerAppointment::STATUS_PENDING ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_PENDING ) ) ?></option>
21
- <option value="<?php echo CustomerAppointment::STATUS_APPROVED ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_APPROVED ) ) ?></option>
22
- <option value="<?php echo CustomerAppointment::STATUS_CANCELLED ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_CANCELLED ) ) ?></option>
23
- <option value="<?php echo CustomerAppointment::STATUS_REJECTED ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_REJECTED ) ) ?></option>
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>
40
- <textarea class="bookly-custom-field form-control" id="bookly-appointment-notes"></textarea>
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>
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">
10
+ <div class="modal-content">
11
+ <div class="modal-header">
12
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
13
+ <div class="modal-title h2"><?php _e( 'Edit booking details', 'bookly' ) ?></div>
14
+ </div>
15
+ <form ng-hide=loading style="z-index: 1050">
16
+ <div class="modal-body">
17
+ <div class="form-group">
18
+ <label for="bookly-appointment-status"><?php _e( 'Status', 'bookly' ) ?></label>
19
+ <select class="bookly-custom-field form-control" id="bookly-appointment-status">
20
+ <option value="<?php echo CustomerAppointment::STATUS_PENDING ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_PENDING ) ) ?></option>
21
+ <option value="<?php echo CustomerAppointment::STATUS_APPROVED ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_APPROVED ) ) ?></option>
22
+ <option value="<?php echo CustomerAppointment::STATUS_CANCELLED ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_CANCELLED ) ) ?></option>
23
+ <option value="<?php echo CustomerAppointment::STATUS_REJECTED ?>"><?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_REJECTED ) ) ?></option>
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>
40
+ <textarea class="bookly-custom-field form-control" id="bookly-appointment-notes"></textarea>
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 CHANGED
@@ -1,56 +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
  }
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 CHANGED
@@ -1,33 +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
  }
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
+ 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( 'jquery' ),
25
+ ),
26
+ 'module' => array(
27
+ 'js/delete_dialog.js' => array( 'jquery' ),
28
+ ),
29
+ ) );
30
+
31
+ static::renderTemplate( 'delete' );
32
+ }
33
  }
backend/components/dialogs/appointment/delete/resources/js/delete_dialog.js CHANGED
@@ -1,9 +1,9 @@
1
- /**
2
- * Scripts for delete dialog
3
- */
4
-
5
- jQuery(function($) {
6
- $("#bookly-delete-notify").on('change', function() {
7
- $('#bookly-delete-reason-cover').toggle();
8
- })
9
  });
1
+ /**
2
+ * Scripts for delete dialog
3
+ */
4
+
5
+ jQuery(function($) {
6
+ $("#bookly-delete-notify").on('change', function() {
7
+ $('#bookly-delete-reason-cover').toggle();
8
+ })
9
  });
backend/components/dialogs/appointment/delete/templates/delete.php CHANGED
@@ -1,28 +1,28 @@
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">
7
- <div class="modal-header">
8
- <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
9
- <div class="modal-title h2"><?php _e( 'Delete', 'bookly' ) ?></div>
10
- </div>
11
- <div class="modal-body">
12
- <div class="checkbox">
13
- <label>
14
- <input id="bookly-delete-notify" type="checkbox" />
15
- <?php _e( 'Send notifications', 'bookly' ) ?>
16
- </label>
17
- </div>
18
- <div class="form-group" style="display: none;" id="bookly-delete-reason-cover">
19
- <input class="form-control" type="text" id="bookly-delete-reason" placeholder="<?php _e( 'Cancellation reason (optional)', 'bookly' ) ?>" />
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>
28
- </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">
7
+ <div class="modal-header">
8
+ <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
9
+ <div class="modal-title h2"><?php _e( 'Delete', 'bookly' ) ?></div>
10
+ </div>
11
+ <div class="modal-body">
12
+ <div class="checkbox">
13
+ <label>
14
+ <input id="bookly-delete-notify" type="checkbox" />
15
+ <?php _e( 'Send notifications', 'bookly' ) ?>
16
+ </label>
17
+ </div>
18
+ <div class="form-group" style="display: none;" id="bookly-delete-reason-cover">
19
+ <input class="form-control" type="text" id="bookly-delete-reason" placeholder="<?php _e( 'Cancellation reason (optional)', 'bookly' ) ?>" />
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>
28
+ </div>
backend/components/dialogs/appointment/edit/Ajax.php CHANGED
@@ -1,808 +1,853 @@
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
  }
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
+ 'extras_consider_duration' => (int) Lib\Proxy\ServiceExtras::considerDuration( true ),
51
+ 'extras_multiply_nop' => (int) get_option( 'bookly_service_extras_multiply_nop', 1 ),
52
+ );
53
+
54
+ // Staff list.
55
+ $staff = Lib\Entities\Staff::query()->findOne();
56
+ $staff_members = $staff ? Lib\Config::proActive() ? Lib\Entities\Staff::query()->find() : array( $staff ) : array();
57
+ $postfix_archived = sprintf( ' (%s)', __( 'Archived', 'bookly' ) );
58
+
59
+ $max_duration = 0;
60
+
61
+ foreach ( $staff_members as $staff_member ) {
62
+ $services = array();
63
+ if ( $type == Lib\Entities\Service::TYPE_SIMPLE ) {
64
+ $services = Proxy\Pro::addCustomService( $services );
65
+ }
66
+ foreach ( $staff_member->getStaffServices( $type ) as $staff_service ) {
67
+ $sub_services = $staff_service->service->getSubServices();
68
+ if ( $type == Lib\Entities\Service::TYPE_SIMPLE || ! empty( $sub_services ) ) {
69
+ if ( $staff_service->getLocationId() === null || Lib\Proxy\Locations::prepareStaffLocationId( $staff_service->getLocationId(), $staff_service->getStaffId() ) == $staff_service->getLocationId() ) {
70
+ if ( ! in_array( $staff_service->service->getId(), array_map( function ( $service ) { return $service['id']; }, $services ) ) ) {
71
+ $services[] = array(
72
+ 'id' => $staff_service->service->getId(),
73
+ 'title' => sprintf(
74
+ '%s (%s)',
75
+ $staff_service->service->getTitle(),
76
+ Lib\Utils\DateTime::secondsToInterval( $staff_service->service->getDuration() )
77
+ ),
78
+ 'duration' => $staff_service->service->getDuration(),
79
+ 'units_min' => $staff_service->service->getUnitsMin(),
80
+ 'units_max' => $staff_service->service->getUnitsMax(),
81
+ 'locations' => array(
82
+ ( $staff_service->getLocationId() ?: 0 ) => array(
83
+ 'capacity_min' => Lib\Config::groupBookingActive() ? $staff_service->getCapacityMin() : 1,
84
+ 'capacity_max' => Lib\Config::groupBookingActive() ? $staff_service->getCapacityMax() : 1,
85
+ ),
86
+ ),
87
+ );
88
+ $max_duration = max( $max_duration, $staff_service->service->getUnitsMax() * $staff_service->service->getDuration() );
89
+ } else {
90
+ array_walk( $services, function ( &$item ) use ( $staff_service ) {
91
+ if ( $item['id'] == $staff_service->service->getId() ) {
92
+ $item['locations'][ $staff_service->getLocationId() ?: 0 ] = array(
93
+ 'capacity_min' => Lib\Config::groupBookingActive() ? $staff_service->getCapacityMin() : 1,
94
+ 'capacity_max' => Lib\Config::groupBookingActive() ? $staff_service->getCapacityMax() : 1,
95
+ );
96
+ }
97
+ } );
98
+ }
99
+ }
100
+ }
101
+ }
102
+ $locations = array();
103
+ foreach ( (array) Lib\Proxy\Locations::findByStaffId( $staff_member->getId() ) as $location ) {
104
+ $locations[] = array(
105
+ 'id' => $location->getId(),
106
+ 'name' => $location->getName(),
107
+ );
108
+ }
109
+ $result['staff'][] = array(
110
+ 'id' => $staff_member->getId(),
111
+ 'full_name' => $staff_member->getFullName() . ( $staff_member->getVisibility() == 'archive' ? $postfix_archived : '' ),
112
+ 'archived' => $staff_member->getVisibility() == 'archive',
113
+ 'services' => $services,
114
+ 'locations' => $locations,
115
+ );
116
+ }
117
+
118
+ /** @var Lib\Entities\Customer $customer */
119
+ // Customers list.
120
+ foreach ( Lib\Entities\Customer::query()->sortBy( 'full_name' )->find() as $customer ) {
121
+ $name = $customer->getFullName();
122
+ if ( $customer->getEmail() != '' || $customer->getPhone() != '' ) {
123
+ $name .= ' (' . trim( $customer->getEmail() . ', ' . $customer->getPhone(), ', ' ) . ')';
124
+ }
125
+
126
+ $result['customers'][] = array(
127
+ 'id' => $customer->getId(),
128
+ 'name' => $name,
129
+ 'status' => Lib\Proxy\CustomerGroups::prepareDefaultAppointmentStatus( get_option( 'bookly_gen_default_appointment_status' ), $customer->getGroupId() ),
130
+ 'custom_fields' => array(),
131
+ 'timezone' => Lib\Proxy\Pro::getLastCustomerTimezone( $customer->getId() ),
132
+ 'number_of_persons' => 1,
133
+ );
134
+ }
135
+
136
+ // Time list.
137
+ $ts_length = Lib\Config::getTimeSlotLength();
138
+ $time_start = 0;
139
+ $time_end = max( $max_duration + DAY_IN_SECONDS, DAY_IN_SECONDS * 2 );
140
+
141
+ // Run the loop.
142
+ while ( $time_start <= $time_end ) {
143
+ $slot = array(
144
+ 'value' => Lib\Utils\DateTime::buildTimeString( $time_start, false ),
145
+ 'title' => Lib\Utils\DateTime::formatTime( $time_start ),
146
+ );
147
+ if ( $time_start < DAY_IN_SECONDS ) {
148
+ $result['start_time'][] = $slot;
149
+ }
150
+ $result['end_time'][] = $slot;
151
+ $time_start += $ts_length;
152
+ }
153
+
154
+ $days_times = Lib\Config::getDaysAndTimes();
155
+ $weekdays = array( 1 => 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', );
156
+ foreach ( $days_times['days'] as $index => $abbrev ) {
157
+ $result['week_days'][] = $weekdays[ $index ];
158
+ }
159
+
160
+ if ( $type == Lib\Entities\Service::TYPE_PACKAGE ) {
161
+ $result = Proxy\Shared::prepareDataForPackage( $result );
162
+ }
163
+
164
+ wp_send_json( $result );
165
+ }
166
+
167
+ /**
168
+ * Get appointment data when editing an appointment.
169
+ */
170
+ public static function getDataForAppointment()
171
+ {
172
+ $response = array( 'success' => false, 'data' => array( 'customers' => array() ) );
173
+
174
+ $appointment = new Lib\Entities\Appointment();
175
+ if ( $appointment->load( self::parameter( 'id' ) ) ) {
176
+ $response['success'] = true;
177
+
178
+ $query = Lib\Entities\Appointment::query( 'a' )
179
+ ->select( 'SUM(ca.number_of_persons) AS total_number_of_persons,
180
+ a.staff_id,
181
+ a.staff_any,
182
+ a.service_id,
183
+ a.custom_service_name,
184
+ a.custom_service_price,
185
+ a.start_date,
186
+ a.end_date,
187
+ a.internal_note,
188
+ a.location_id' )
189
+ ->leftJoin( 'CustomerAppointment', 'ca', 'ca.appointment_id = a.id' )
190
+ ->leftJoin( 'StaffService', 'ss', 'ss.staff_id = a.staff_id AND ss.service_id = a.service_id AND ss.location_id = a.location_id' )
191
+ ->where( 'a.id', $appointment->getId() );
192
+ if ( Lib\Config::groupBookingActive() ) {
193
+ $query->addSelect( 'ss.capacity_min AS min_capacity, ss.capacity_max AS max_capacity' );
194
+ } else {
195
+ $query->addSelect( '1 AS min_capacity, 1 AS max_capacity' );
196
+ }
197
+
198
+ $info = $query->fetchRow();
199
+ $response['data']['total_number_of_persons'] = $info['total_number_of_persons'];
200
+ $response['data']['min_capacity'] = $info['min_capacity'];
201
+ $response['data']['max_capacity'] = $info['max_capacity'];
202
+ $response['data']['start_date'] = $info['start_date'];
203
+ $response['data']['end_date'] = $info['end_date'];
204
+ $response['data']['start_time'] = $info['start_date'] ?
205
+ array(
206
+ 'value' => date( 'H:i', strtotime( $info['start_date'] ) ),
207
+ 'title' => Lib\Utils\DateTime::formatTime( $info['start_date'] ),
208
+ ) : null;
209
+ $response['data']['end_time'] = $info['end_date'] ?
210
+ array(
211
+ 'value' => date( 'H:i', strtotime( $info['end_date'] ) ),
212
+ 'title' => Lib\Utils\DateTime::formatTime( $info['end_date'] ),
213
+ ) : null;
214
+ $response['data']['staff_id'] = $info['staff_id'];
215
+ $response['data']['staff_any'] = (int) $info['staff_any'];
216
+ $response['data']['service_id'] = $info['service_id'];
217
+ $response['data']['custom_service_name'] = $info['custom_service_name'];
218
+ $response['data']['custom_service_price'] = (float) $info['custom_service_price'];
219
+ $response['data']['internal_note'] = $info['internal_note'];
220
+ $response['data']['location_id'] = $info['location_id'];
221
+
222
+ $customers = Lib\Entities\CustomerAppointment::query( 'ca' )
223
+ ->select( 'ca.id,
224
+ ca.series_id,
225
+ ca.customer_id,
226
+ ca.package_id,
227
+ ca.custom_fields,
228
+ ca.extras,
229
+ ca.extras_multiply_nop,
230
+ ca.extras_consider_duration,
231
+ ca.number_of_persons,
232
+ ca.notes,
233
+ ca.status,
234
+ ca.payment_id,
235
+ ca.collaborative_service_id,
236
+ ca.collaborative_token,
237
+ ca.compound_service_id,
238
+ ca.compound_token,
239
+ ca.time_zone,
240
+ ca.time_zone_offset,
241
+ p.paid AS payment,
242
+ p.total AS payment_total,
243
+ p.type AS payment_type,
244
+ p.details AS payment_details,
245
+ p.status AS payment_status' )
246
+ ->leftJoin( 'Payment', 'p', 'p.id = ca.payment_id' )
247
+ ->where( 'ca.appointment_id', $appointment->getId() )
248
+ ->fetchArray();
249
+ foreach ( $customers as $customer ) {
250
+ $payment_title = '';
251
+ if ( $customer['payment'] !== null ) {
252
+ $payment_title = Lib\Utils\Price::format( $customer['payment'] );
253
+ if ( $customer['payment'] != $customer['payment_total'] ) {
254
+ $payment_title = sprintf( __( '%s of %s', 'bookly' ), $payment_title, Lib\Utils\Price::format( $customer['payment_total'] ) );
255
+ }
256
+ $payment_title .= sprintf(
257
+ ' %s <span%s>%s</span>',
258
+ Lib\Entities\Payment::typeToString( $customer['payment_type'] ),
259
+ $customer['payment_status'] == Lib\Entities\Payment::STATUS_PENDING ? ' class="text-danger"' : '',
260
+ Lib\Entities\Payment::statusToString( $customer['payment_status'] )
261
+ );
262
+ }
263
+ $collaborative_service = '';
264
+ if ( $customer['collaborative_service_id'] !== null ) {
265
+ $service = new Lib\Entities\Service();
266
+ if ( $service->load( $customer['collaborative_service_id'] ) ) {
267
+ $collaborative_service = $service->getTranslatedTitle();
268
+ }
269
+ }
270
+ $compound_service = '';
271
+ if ( $customer['compound_service_id'] !== null ) {
272
+ $service = new Lib\Entities\Service();
273
+ if ( $service->load( $customer['compound_service_id'] ) ) {
274
+ $compound_service = $service->getTranslatedTitle();
275
+ }
276
+ }
277
+ $custom_fields = (array) json_decode( $customer['custom_fields'], true );
278
+ $response['data']['customers'][] = array(
279
+ 'id' => $customer['customer_id'],
280
+ 'ca_id' => $customer['id'],
281
+ 'series_id' => $customer['series_id'],
282
+ 'package_id' => $customer['package_id'],
283
+ 'collaborative_service' => $collaborative_service,
284
+ 'collaborative_token' => $customer['collaborative_token'],
285
+ 'compound_service' => $compound_service,
286
+ 'compound_token' => $customer['compound_token'],
287
+ 'custom_fields' => $custom_fields,
288
+ 'files' => Lib\Proxy\Files::getFileNamesForCustomFields( $custom_fields ),
289
+ 'extras' => (array) json_decode( $customer['extras'], true ),
290
+ 'extras_multiply_nop' => $customer['extras_multiply_nop'],
291
+ 'extras_consider_duration' => (int) $customer['extras_consider_duration'],
292
+ 'number_of_persons' => $customer['number_of_persons'],
293
+ 'notes' => $customer['notes'],
294
+ 'payment_id' => $customer['payment_id'],
295
+ 'payment_type' => $customer['payment'] != $customer['payment_total'] ? 'partial' : 'full',
296
+ 'payment_title' => $payment_title,
297
+ 'status' => $customer['status'],
298
+ 'timezone' => Lib\Proxy\Pro::getCustomerTimezone( $customer['time_zone'], $customer['time_zone_offset'] ),
299
+ );
300
+ }
301
+ }
302
+
303
+ wp_send_json( $response );
304
+ }
305
+
306
+ /**
307
+ * Save appointment form (for both create and edit).
308
+ */
309
+ public static function saveAppointmentForm()
310
+ {
311
+ $response = array( 'success' => false );
312
+
313
+ $appointment_id = (int) self::parameter( 'id', 0 );
314
+ $staff_id = (int) self::parameter( 'staff_id', 0 );
315
+ $service_id = (int) self::parameter( 'service_id', -1 );
316
+ $custom_service_name = trim( self::parameter( 'custom_service_name' ) );
317
+ $custom_service_price = trim( self::parameter( 'custom_service_price' ) );
318
+ $location_id = (int) self::parameter( 'location_id', 0 );
319
+ $skip_date = self::parameter( 'skip_date', 0 );
320
+ $start_date = self::parameter( 'start_date' );
321
+ $end_date = self::parameter( 'end_date' );
322
+ $repeat = json_decode( self::parameter( 'repeat', '[]' ), true );
323
+ $schedule = self::parameter( 'schedule', array() );
324
+ $customers = json_decode( self::parameter( 'customers', '[]' ), true );
325
+ $notification = self::parameter( 'notification', 'no' );
326
+ $internal_note = self::parameter( 'internal_note' );
327
+ $created_from = self::parameter( 'created_from' );
328
+
329
+ if ( ! $service_id ) {
330
+ // Custom service.
331
+ $service_id = null;
332
+ }
333
+ if ( $service_id || $custom_service_name == '' ) {
334
+ $custom_service_name = null;
335
+ }
336
+ if ( $service_id || $custom_service_price == '' ) {
337
+ $custom_service_price = null;
338
+ }
339
+ if ( ! $location_id ) {
340
+ $location_id = null;
341
+ }
342
+
343
+ // Check for errors.
344
+ if ( ! $skip_date ) {
345
+ if ( ! $start_date ) {
346
+ $response['errors']['time_interval'] = __( 'Start time must not be empty', 'bookly' );
347
+ } elseif ( ! $end_date ) {
348
+ $response['errors']['time_interval'] = __( 'End time must not be empty', 'bookly' );
349
+ } elseif ( $start_date == $end_date ) {
350
+ $response['errors']['time_interval'] = __( 'End time must not be equal to start time', 'bookly' );
351
+ }
352
+ }
353
+
354
+ if ( $service_id == -1 ) {
355
+ $response['errors']['service_required'] = true;
356
+ } else if ( $service_id === null && $custom_service_name === null ) {
357
+ $response['errors']['custom_service_name_required'] = true;
358
+ }
359
+ $total_number_of_persons = 0;
360
+ $max_extras_duration = 0;
361
+ foreach ( $customers as $i => $customer ) {
362
+ if ( $customer['status'] == Lib\Entities\CustomerAppointment::STATUS_PENDING ||
363
+ $customer['status'] == Lib\Entities\CustomerAppointment::STATUS_APPROVED
364
+ ) {
365
+ $total_number_of_persons += $customer['number_of_persons'];
366
+ if ( $customer['extras_consider_duration'] ) {
367
+ $extras_duration = Lib\Proxy\ServiceExtras::getTotalDuration( $customer['extras'] );
368
+ if ( $extras_duration > $max_extras_duration ) {
369
+ $max_extras_duration = $extras_duration;
370
+ }
371
+ }
372
+ }
373
+ $customers[ $i ]['created_from'] = ( $created_from == 'backend' ) ? 'backend' : 'frontend';
374
+ }
375
+ if ( $service_id ) {
376
+ $staff_service = new Lib\Entities\StaffService();
377
+ $staff_service->loadBy( array(
378
+ 'staff_id' => $staff_id,
379
+ 'service_id' => $service_id,
380
+ 'location_id' => $location_id ?: null,
381
+ ) );
382
+ if ( ! $staff_service->isLoaded() ) {
383
+ $staff_service->loadBy( array(
384
+ 'staff_id' => $staff_id,
385
+ 'service_id' => $service_id,
386
+ 'location_id' => null,
387
+ ) );
388
+ }
389
+ if ( $total_number_of_persons > $staff_service->getCapacityMax() ) {
390
+ $response['errors']['overflow_capacity'] = sprintf(
391
+ __( 'The number of customers should not be more than %d', 'bookly' ),
392
+ $staff_service->getCapacityMax()
393
+ );
394
+ }
395
+ }
396
+
397
+ // If no errors then try to save the appointment.
398
+ if ( ! isset ( $response['errors'] ) ) {
399
+ $duration = Lib\Slots\DatePoint::fromStr( $end_date )->diff( Lib\Slots\DatePoint::fromStr( $start_date ) );
400
+ if ( ! $skip_date && $repeat['enabled'] ) {
401
+ // Series.
402
+ if ( ! empty ( $schedule ) ) {
403
+ /** @var DataHolders\Order[] $orders */
404
+ $orders = array();
405
+
406
+ if ( $service_id ) {
407
+ $service = Lib\Entities\Service::find( $service_id );
408
+ } else {
409
+ $service = new Lib\Entities\Service();
410
+ $service
411
+ ->setTitle( $custom_service_name )
412
+ ->setDuration( $duration )
413
+ ->setPrice( $custom_service_price );
414
+ }
415
+
416
+ foreach ( $customers as $customer ) {
417
+ // Create new series.
418
+ $series = new Lib\Entities\Series();
419
+ $series
420
+ ->setRepeat( self::parameter( 'repeat' ) )
421
+ ->setToken( Lib\Utils\Common::generateToken( get_class( $series ), 'token' ) )
422
+ ->save();
423
+
424
+ // Create order
425
+ if ( $notification != 'no' ) {
426
+ $orders[ $customer['id'] ] = DataHolders\Order::create( Lib\Entities\Customer::find( $customer['id'] ) )
427
+ ->addItem( 0, DataHolders\Series::create( $series ) );
428
+ }
429
+
430
+ foreach ( $schedule as $slot ) {
431
+ $slot = json_decode( $slot, true );
432
+ $start_date = $slot[0][2];
433
+ $end_date = Lib\Slots\DatePoint::fromStr( $start_date )->modify( $duration )->format( 'Y-m-d H:i:s' );
434
+ // Try to find existing appointment
435
+ $appointment = Lib\Entities\Appointment::query( 'a' )
436
+ ->leftJoin( 'CustomerAppointment', 'ca', 'ca.appointment_id = a.id' )
437
+ ->where( 'a.staff_id', $staff_id )
438
+ ->where( 'a.service_id', $service_id )
439
+ ->whereNot( 'ca.status', Lib\Entities\CustomerAppointment::STATUS_CANCELLED )
440
+ ->whereNot( 'ca.status', Lib\Entities\CustomerAppointment::STATUS_REJECTED )
441
+ ->where( 'start_date', $start_date )
442
+ ->findOne();
443
+
444
+ $ca_customers = array();
445
+ if ( ! $appointment ) {
446
+ // Create appointment.
447
+ $appointment = new Lib\Entities\Appointment();
448
+ $appointment
449
+ ->setLocationId( $location_id )
450
+ ->setStaffId( $staff_id )
451
+ ->setServiceId( $service_id )
452
+ ->setCustomServiceName( $custom_service_name )
453
+ ->setCustomServicePrice( $custom_service_price )
454
+ ->setStartDate( $start_date )
455
+ ->setEndDate( $end_date )
456
+ ->setInternalNote( $internal_note )
457
+ ->setExtrasDuration( $max_extras_duration )
458
+ ->save();
459
+ } else {
460
+ foreach ( $appointment->getCustomerAppointments( true ) as $ca ) {
461
+ $ca_customer = $ca->getFields();
462
+ $ca_customer['ca_id'] = $ca->getId();
463
+ $ca_customer['extras'] = json_decode( $ca_customer['extras'], true );
464
+ $ca_customer['custom_fields'] = json_decode( $ca_customer['custom_fields'], true );
465
+ $ca_customers[] = $ca_customer;
466
+ }
467
+ }
468
+
469
+ if ( $appointment->getId() ) {
470
+ // Save customer appointments.
471
+ $ca_list = $appointment->saveCustomerAppointments( array_merge( $ca_customers, array( $customer ) ), $series->getId() );
472
+ // Google Calendar.
473
+ Lib\Proxy\Pro::syncGoogleCalendarEvent( $appointment );
474
+ // Waiting list.
475
+ Lib\Proxy\WaitingList::handleParticipantsChange( $appointment );
476
+
477
+ if ( $notification != 'no' ) {
478
+ foreach ( $ca_list as $ca ) {
479
+ $item = DataHolders\Simple::create( $ca )
480
+ ->setService( $service )
481
+ ->setAppointment( $appointment );
482
+ $orders[ $ca->getCustomerId() ]->getItem( 0 )->addItem( $item );
483
+ }
484
+ }
485
+ }
486
+ }
487
+ if ( $customer['payment_create'] === true ) {
488
+ Proxy\RecurringAppointments::createBackendPayment( $series, $customer );
489
+ }
490
+ }
491
+ if ( $notification != 'no' ) {
492
+ foreach ( $orders as $order ) {
493
+ Lib\Proxy\RecurringAppointments::sendRecurring( $order->getItem( 0 ), $order );
494
+ }
495
+ }
496
+ }
497
+ $response['success'] = true;
498
+ $response['data'] = array( 'staffId' => $staff_id ); // make FullCalendar refetch events
499
+ } else {
500
+ // Single appointment.
501
+ $appointment = new Lib\Entities\Appointment();
502
+ if ( $appointment_id ) {
503
+ // Edit.
504
+ $appointment->load( $appointment_id );
505
+ if ( $appointment->getStaffId() != $staff_id ) {
506
+ $appointment->setStaffAny( 0 );
507
+ }
508
+ }
509
+ $appointment
510
+ ->setLocationId( $location_id )
511
+ ->setStaffId( $staff_id )
512
+ ->setServiceId( $service_id )
513
+ ->setCustomServiceName( $custom_service_name )
514
+ ->setCustomServicePrice( $custom_service_price )
515
+ ->setStartDate( $skip_date ? null : $start_date )
516
+ ->setEndDate( $skip_date ? null : $end_date )
517
+ ->setInternalNote( $internal_note )
518
+ ->setExtrasDuration( $max_extras_duration );
519
+
520
+ if ( $appointment->save() !== false ) {
521
+ // Save customer appointments.
522
+ $ca_status_changed = $appointment->saveCustomerAppointments( $customers );
523
+
524
+ foreach ( $customers as $customer ) {
525
+ if ( $customer['payment_create'] === true && $customer['series_id'] ) {
526
+ Proxy\RecurringAppointments::createBackendPayment( Lib\Entities\Series::find( $customer['series_id'] ), $customer );
527
+ }
528
+ }
529
+
530
+ // Google Calendar.
531
+ Lib\Proxy\Pro::syncGoogleCalendarEvent( $appointment );
532
+ // Waiting list.
533
+ Lib\Proxy\WaitingList::handleParticipantsChange( $appointment );
534
+
535
+ // Send notifications.
536
+ if ( $notification == 'changed_status' ) {
537
+ foreach ( $ca_status_changed as $ca ) {
538
+ Lib\Notifications\Sender::sendSingle( DataHolders\Simple::create( $ca )->setAppointment( $appointment ) );
539
+ }
540
+ } elseif ( $notification == 'all' ) {
541
+ $ca_list = $appointment->getCustomerAppointments( true );
542
+ foreach ( $ca_status_changed as $ca ) {
543
+ // The value "just_created" was initialized for the objects of this array
544
+ Lib\Notifications\Sender::sendSingle( DataHolders\Simple::create( $ca )->setAppointment( $appointment ) );
545
+ unset( $ca_list[ $ca->getId() ] );
546
+ }
547
+ foreach ( $ca_list as $ca ) {
548
+ Lib\Notifications\Sender::sendSingle( DataHolders\Simple::create( $ca )->setAppointment( $appointment ) );
549
+ }
550
+ }
551
+
552
+ $response['success'] = true;
553
+ $response['data'] = self::_getAppointmentForFC( $staff_id, $appointment->getId() );
554
+ } else {
555
+ $response['errors'] = array( 'db' => __( 'Could not save appointment in database.', 'bookly' ) );
556
+ }
557
+ }
558
+ }
559
+ update_user_meta( get_current_user_id(), 'bookly_appointment_form_send_notifications', $notification );
560
+
561
+ wp_send_json( $response );
562
+ }
563
+
564
+ /**
565
+ * Check whether appointment settings produce errors.
566
+ */
567
+ public static function checkAppointmentErrors()
568
+ {
569
+ $start_date = self::parameter( 'start_date' );
570
+ $end_date = self::parameter( 'end_date' );
571
+ $staff_id = (int) self::parameter( 'staff_id' );
572
+ $service_id = (int) self::parameter( 'service_id' );
573
+ $location_id = Lib\Proxy\Locations::prepareStaffScheduleLocationId( self::parameter( 'location_id' ), $staff_id ) ?: null;
574
+ $appointment_id = (int) self::parameter( 'appointment_id' );
575
+ $appointment_duration = strtotime( $end_date ) - strtotime( $start_date );
576
+ $customers = json_decode( self::parameter( 'customers', '[]' ), true );
577
+ $service = Lib\Entities\Service::find( $service_id );
578
+ $service_duration = $service ? $service->getDuration() : 0;
579
+
580
+ $result = array(
581
+ 'date_interval_not_available' => false,
582
+ 'date_interval_warning' => false,
583
+ 'interval_not_in_staff_schedule' => false,
584
+ 'interval_not_in_service_schedule' => false,
585
+ 'staff_reaches_working_time_limit' => false,
586
+ 'customers_appointments_limit' => array(),
587
+ );
588
+
589
+ $max_extras_duration = 0;
590
+ foreach ( $customers as $customer ) {
591
+ if ( in_array( $customer['status'], array( Lib\Entities\CustomerAppointment::STATUS_PENDING, Lib\Entities\CustomerAppointment::STATUS_APPROVED ) ) ) {
592
+ if ( $customer['extras_consider_duration'] ) {
593
+ $extras_duration = Lib\Proxy\ServiceExtras::getTotalDuration( $customer['extras'] );
594
+ if ( $extras_duration > $max_extras_duration ) {
595
+ $max_extras_duration = $extras_duration;
596
+ }
597
+ }
598
+ }
599
+ }
600
+ if ( $start_date && $end_date ) {
601
+ $total_end_date = $end_date;
602
+ if ( $max_extras_duration > 0 ) {
603
+ $total_end_date = date_create( $end_date )->modify( '+' . $max_extras_duration . ' sec' )->format( 'Y-m-d H:i:s' );
604
+ }
605
+ if ( ! self::_dateIntervalIsAvailableForAppointment( $start_date, $total_end_date, $staff_id, $appointment_id ) ) {
606
+ $result['date_interval_not_available'] = true;
607
+ }
608
+
609
+ // Check if selected interval fit into staff schedule.
610
+ $interval_valid = true;
611
+ if ( $staff_id && $start_date ) {
612
+ $staff = Lib\Entities\Staff::find( $staff_id );
613
+
614
+ // Check if interval is suitable for staff's hours limit
615
+ $result['staff_reaches_working_time_limit'] = Lib\Proxy\Pro::getWorkingTimeLimitError( $staff, $start_date, $end_date, $appointment_duration + $max_extras_duration, $appointment_id ) ?: false;
616
+
617
+ if ( $service_duration >= DAY_IN_SECONDS ) {
618
+ // For services with duration 24+ hours check holidays and days off
619
+ for ( $day = 0; $day < $service_duration / DAY_IN_SECONDS; $day ++ ) {
620
+ $work_date = date_create( $start_date )->modify( sprintf( '%s days', $day ) );
621
+ $week_day = $work_date->format( 'w' ) + 1;
622
+ // Check staff schedule for days off
623
+ if ( $staff->isOnHoliday( $work_date ) ||
624
+ ! Lib\Entities\StaffScheduleItem::query()
625
+ ->select( 'id' )
626
+ ->where( 'staff_id', $staff_id )
627
+ ->where( 'location_id', $location_id )
628
+ ->where( 'day_index', $week_day )
629
+ ->whereNot( 'start_time', null )
630
+ ->fetchRow()
631
+ ) {
632
+ $interval_valid = false;
633
+ break;
634
+ }
635
+ }
636
+ } else {
637
+ // Check day before and current day to get night schedule from previous day.
638
+ $interval_valid = false;
639
+ for ( $day = 0; $day <= 1; $day ++ ) {
640
+ $day_start_date = date_create( $start_date )->modify( sprintf( '%s days', $day - 1 ) );
641
+ $day_end_date = date_create( $end_date )->modify( sprintf( '%s days', $day - 1 ) );
642
+ if ( ! $staff->isOnHoliday( $day_start_date ) ) {
643
+ $day_start_hour = ( 1 - $day ) * 24 + $day_start_date->format( 'G' );
644
+ $day_end_hour = ( 1 - $day ) * 24 + $day_end_date->format( 'G' );
645
+ $day_start_time = sprintf( '%02d:%02d:00', $day_start_hour, $day_start_date->format( 'i' ) );
646
+ $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' ) );
647
+
648
+ $special_days = (array) Lib\Proxy\SpecialDays::getSchedule( array( $staff_id ), $day_start_date, $day_start_date );
649
+ if ( ! empty( $special_days ) ) {
650
+ // Check if interval fit into special day schedule.
651
+ $special_day = current( $special_days );
652
+ if ( ( $special_day['start_time'] <= $day_start_time ) && ( $special_day['end_time'] >= $day_end_time ) ) {
653
+ if ( ! ( $special_day['break_start'] && ( $special_day['break_start'] < $day_end_time ) && ( $special_day['break_end'] > $day_start_time ) ) ) {
654
+ $interval_valid = true;
655
+ break;
656
+ }
657
+ }
658
+ } else {
659
+ // Check if interval fit into regular staff working schedule.
660
+ $week_day = $day_start_date->format( 'w' ) + 1;
661
+ $ssi = Lib\Entities\StaffScheduleItem::query()
662
+ ->select( 'id' )
663
+ ->where( 'staff_id', $staff_id )
664
+ ->where( 'location_id', $location_id )
665
+ ->where( 'day_index', $week_day )
666
+ ->whereNot( 'start_time', null )
667
+ ->whereLte( 'start_time', $day_start_time )
668
+ ->whereGte( 'end_time', $day_end_time )
669
+ ->fetchRow();
670
+ if ( $ssi ) {
671
+ // Check if interval not intercept with breaks.
672
+ if ( Lib\Entities\ScheduleItemBreak::query()
673
+ ->where( 'staff_schedule_item_id', $ssi['id'] )
674
+ ->whereLt( 'start_time', $day_end_time )
675
+ ->whereGt( 'end_time', $day_start_time )
676
+ ->count() == 0
677
+ ) {
678
+ $interval_valid = true;
679
+ break;
680
+ }
681
+ }
682
+ }
683
+ }
684
+ }
685
+ }
686
+ }
687
+ if ( ! $interval_valid ) {
688
+ $result['interval_not_in_staff_schedule'] = true;
689
+ }
690
+ if ( $service ) {
691
+ if ( $service_duration >= DAY_IN_SECONDS ) {
692
+ // For services with duration 24+ hours check days off
693
+ $service_schedule = (array) Lib\Proxy\ServiceSchedule::getSchedule( $service_id );
694
+ $interval_valid = true;
695
+
696
+ // Check service schedule and service special days
697
+ for ( $day = 0; $day < $service_duration / DAY_IN_SECONDS; $day ++ ) {
698
+ $work_date = date_create( $start_date )->modify( sprintf( '%s days', $day ) );
699
+ $week_day = $work_date->format( 'w' ) + 1;
700
+ // Check service schedule for days off
701
+ $service_schedule_valid = true;
702
+ if ( Lib\Config::serviceScheduleActive() ) {
703
+ $service_schedule_valid = false;
704
+ foreach ( $service_schedule as $day_schedule ) {
705
+ if ( $day_schedule['day_index'] == $week_day && $day_schedule['start_time'] ) {
706
+ $service_schedule_valid = true;
707
+ break;
708
+ }
709
+ }
710
+ }
711
+ if ( ! $service_schedule_valid ) {
712
+ $interval_valid = false;
713
+ break;
714
+ }
715
+ // Check service special days for days off
716
+ $service_special_days_valid = true;
717
+ if ( Lib\Config::specialDaysActive() ) {
718
+ $special_days = (array) Lib\Proxy\SpecialDays::getServiceSchedule( $service_id, $work_date, $work_date );
719
+ if ( ! empty( $special_days ) ) {
720
+ $service_special_days_valid = false;
721
+ $schedule = current( $special_days );
722
+ if ( $schedule['start_time'] ) {
723
+ $service_special_days_valid = true;
724
+ }
725
+ }
726
+ }
727
+ if ( ! $service_special_days_valid ) {
728
+ $interval_valid = false;
729
+ break;
730
+ }
731
+ }
732
+ if ( ! $interval_valid ) {
733
+ $result['interval_not_in_service_schedule'] = true;
734
+ }
735
+ // Check staff schedule and staff special days
736
+ $interval_valid = true;
737
+ for ( $day = 0; $day < $service_duration / DAY_IN_SECONDS; $day ++ ) {
738
+ $work_date = date_create( $start_date )->modify( sprintf( '%s days', $day ) );
739
+ $week_day = $work_date->format( 'w' ) + 1;
740
+ if ( Lib\Entities\StaffScheduleItem::query()
741
+ ->where( 'staff_id', $staff_id )
742
+ ->where( 'day_index', $week_day )
743
+ ->whereNot( 'start_time', null )
744
+ ->count() == 0
745
+ ) {
746
+ $interval_valid = false;
747
+ break;
748
+ }
749
+ }
750
+ if ( ! $interval_valid ) {
751
+ $result['interval_not_in_staff_schedule'] = true;
752
+ }
753
+ } else {
754
+ // Check if selected interval fit into service schedule.
755
+ $interval_valid = false;
756
+ // Check day before and current day to get night schedule from previous day.
757
+ for ( $day = 0; $day <= 1; $day ++ ) {
758
+ $day_start_date = date_create( $start_date )->modify( sprintf( '%s days', $day - 1 ) );
759
+ $day_end_date = date_create( $end_date )->modify( sprintf( '%s days', $day - 1 ) );
760
+
761
+ $day_start_hour = ( 1 - $day ) * 24 + $day_start_date->format( 'G' );
762
+ $day_end_hour = ( 1 - $day ) * 24 + $day_end_date->format( 'G' );
763
+ $day_start_time = sprintf( '%02d:%02d:00', $day_start_hour, $day_start_date->format( 'i' ) );
764
+ $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' ) );
765
+
766
+ $special_days = (array) Lib\Proxy\SpecialDays::getServiceSchedule( $service_id, $day_start_date, $day_start_date );
767
+ if ( ! empty( $special_days ) ) {
768
+ // Check if interval fit into special day schedule.
769
+ $special_day = current( $special_days );
770
+ if ( ( $special_day['start_time'] <= $day_start_time ) && ( $special_day['end_time'] >= $day_end_time ) ) {
771
+ if ( ! ( $special_day['break_start'] && ( $special_day['break_start'] < $day_end_time ) && ( $special_day['break_end'] > $day_start_time ) ) ) {
772
+ $interval_valid = true;
773
+ break;
774
+ }
775
+ }
776
+ } else {
777
+ // Check if interval fit into service working schedule.
778
+ $schedule = (array) Lib\Proxy\ServiceSchedule::getSchedule( $service_id );
779
+ if ( ! empty ( $schedule ) ) {
780
+ $week_day = $day_start_date->format( 'w' ) + 1;
781
+ foreach ( $schedule as $schedule_day ) {
782
+ if ( $schedule_day['day_index'] == $week_day ) {
783
+ if ( ( $schedule_day['start_time'] <= $day_start_time ) && ( $schedule_day['end_time'] >= $day_end_time ) ) {
784
+ $interval_valid = true;
785
+ if ( $schedule_day['break_start'] && ( $schedule_day['break_start'] < $day_end_time ) && ( $schedule_day['break_end'] > $day_start_time ) ) {
786
+ $interval_valid = false;
787
+ break;
788
+ }
789
+ }
790
+ }
791
+ }
792
+ } else {
793
+ $interval_valid = true;
794
+ break;
795
+ }
796
+ }
797
+ }
798
+ if ( ! $interval_valid ) {
799
+ $result['interval_not_in_service_schedule'] = true;
800
+ }
801
+ // Service duration interval is not equal to.
802
+ $result['date_interval_warning'] = ! ( $appointment_duration >= $service->getMinDuration() && $appointment_duration <= $service->getMaxDuration() && ( $service_duration == 0 || $appointment_duration % $service_duration == 0 ) );
803
+ }
804
+
805
+ // Check customers for appointments limit
806
+ foreach ( $customers as $index => $customer ) {
807
+ if ( $service->appointmentsLimitReached( $customer['id'], array( $start_date ) ) ) {
808
+ $customer_error = Lib\Entities\Customer::find( $customer['id'] );
809
+ $result['customers_appointments_limit'][] = sprintf( __( '%s has reached the limit of bookings for this service', 'bookly' ), $customer_error->getFullName() );
810
+ }
811
+ }
812
+ }
813
+ }
814
+
815
+ wp_send_json( $result );
816
+ }
817
+
818
+ /**
819
+ * Get appointment for FullCalendar.
820
+ *
821
+ * @param integer $staff_id
822
+ * @param int $appointment_id
823
+ * @return array
824
+ */
825
+ private static function _getAppointmentForFC( $staff_id, $appointment_id )
826
+ {
827
+ $query = Lib\Entities\Appointment::query( 'a' )
828
+ ->where( 'a.id', $appointment_id );
829
+
830
+ $appointments = Calendar\Page::buildAppointmentsForFC( $staff_id, $query );
831
+
832
+ return $appointments[0];
833
+ }
834
+
835
+ /**
836
+ * Check whether interval is available for given appointment.
837
+ *
838
+ * @param $start_date
839
+ * @param $end_date
840
+ * @param $staff_id
841
+ * @param $appointment_id
842
+ * @return bool
843
+ */
844
+ private static function _dateIntervalIsAvailableForAppointment( $start_date, $end_date, $staff_id, $appointment_id )
845
+ {
846
+ return Lib\Entities\Appointment::query( 'a' )
847
+ ->whereNot( 'a.id', $appointment_id )
848
+ ->where( 'a.staff_id', $staff_id )
849
+ ->whereLt( 'a.start_date', $end_date )
850
+ ->whereGt( 'a.end_date', $start_date )
851
+ ->count() == 0;
852
+ }
853
  }
backend/components/dialogs/appointment/edit/Dialog.php CHANGED
@@ -1,64 +1,64 @@
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' ),
28
- 'js/moment.min.js' => array( 'jquery' ),
29
- 'js/select2.full.min.js' => array( 'jquery' ),
30
- 'js/help.js' => array( 'jquery' ),
31
- ),
32
- 'frontend' => array(
33
- 'js/spin.min.js' => array( 'jquery' ),
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 ),
45
- 'monthNamesShort' => array_values( $wp_locale->month_abbrev ),
46
- 'monthNames' => array_values( $wp_locale->month ),
47
- 'dayNamesMin' => array_values( $wp_locale->weekday_abbrev ),
48
- 'longDays' => array_values( $wp_locale->weekday ),
49
- 'firstDay' => (int) get_option( 'start_of_week' ),
50
- ),
51
- 'cf_per_service' => (int) Lib\Config::customFieldsPerService(),
52
- 'no_result_found' => __( 'No result found', 'bookly' ),
53
- 'staff_any' => get_option( 'bookly_l10n_option_employee' ),
54
- 'title' => array(
55
- 'edit_appointment' => __( 'Edit appointment', 'bookly' ),
56
- 'new_appointment' => __( 'New appointment', 'bookly' ),
57
- ),
58
- ) );
59
-
60
- Proxy\Shared::enqueueAssets();
61
-
62
- self::renderTemplate( 'edit' );
63
- }
64
  }
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' ),
28
+ 'js/moment.min.js' => array( 'jquery' ),
29
+ 'js/select2.full.min.js' => array( 'jquery' ),
30
+ 'js/help.js' => array( 'jquery' ),
31
+ ),
32
+ 'frontend' => array(
33
+ 'js/spin.min.js' => array( 'jquery' ),
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 ),
45
+ 'monthNamesShort' => array_values( $wp_locale->month_abbrev ),
46
+ 'monthNames' => array_values( $wp_locale->month ),
47
+ 'dayNamesMin' => array_values( $wp_locale->weekday_abbrev ),
48
+ 'longDays' => array_values( $wp_locale->weekday ),
49
+ 'firstDay' => (int) get_option( 'start_of_week' ),
50
+ ),
51
+ 'cf_per_service' => (int) Lib\Config::customFieldsPerService(),
52
+ 'no_result_found' => __( 'No result found', 'bookly' ),
53
+ 'staff_any' => get_option( 'bookly_l10n_option_employee' ),
54
+ 'title' => array(
55
+ 'edit_appointment' => __( 'Edit appointment', 'bookly' ),
56
+ 'new_appointment' => __( 'New appointment', 'bookly' ),
57
+ ),
58
+ ) );
59
+
60
+ Proxy\Shared::enqueueAssets();
61
+
62
+ self::renderTemplate( 'edit' );
63
+ }
64
  }
backend/components/dialogs/appointment/edit/proxy/Pro.php CHANGED
@@ -1,17 +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
  }
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 CHANGED
@@ -1,17 +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
  }
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 CHANGED
@@ -1,19 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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/components/dialogs/appointment/edit/resources/js/ng-appointment.js CHANGED
@@ -1,1341 +1,1372 @@
1
- ;(function() {
2
-
3
- var module = angular.module('appointmentDialog', ['ui.date', 'customerDialog', 'paymentDetailsDialog']);
4
-
5
- /**
6
- * DataSource service.
7
- */
8
- module.factory('dataSource', function($q) {
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 },
40
- weekly : { on : null },
41
- biweekly : { on : null },
42
- monthly : { on : null, day : null, weekday : null },
43
- until : null
44
- },
45
- schedule : {
46
- items : [],
47
- edit : null,
48
- page : null,
49
- another_time : []
50
- },
51
- customers : [],
52
- notification : null,
53
- series_id : null,
54
- expand_customers_list : false
55
- },
56
- l10n : {
57
- staff_any: BooklyL10nAppDialog.staff_any
58
- },
59
- loadData : function() {
60
- var deferred = $q.defer();
61
- if (!ds.loaded) {
62
- jQuery.get(
63
- ajaxurl,
64
- { action : 'bookly_get_data_for_appointment_form', csrf_token : BooklyL10nAppDialog.csrf_token },
65
- function(data) {
66
- ds.loaded = true;
67
- ds.data = data;
68
-
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();
76
- },
77
- 'json'
78
- );
79
- } else {
80
- deferred.resolve();
81
- }
82
-
83
- return deferred.promise;
84
- },
85
- findStaff : function(id) {
86
- var result = null;
87
- jQuery.each(ds.data.staff, function(key, item) {
88
- if (item.id == id) {
89
- result = item;
90
- return false;
91
- }
92
- });
93
- return result;
94
- },
95
- findService : function(staff_id, id) {
96
- var result = null,
97
- staff = ds.findStaff(staff_id);
98
-
99
- if (staff !== null) {
100
- jQuery.each(staff.services, function(key, item) {
101
- if (item.id == id) {
102
- result = item;
103
- return false;
104
- }
105
- });
106
- }
107
- return result;
108
- },
109
- findLocation : function(staff_id, id) {
110
- var result = null,
111
- staff = ds.findStaff(staff_id);
112
-
113
- if (staff !== null) {
114
- jQuery.each(staff.locations, function(key, item) {
115
- if (item.id == id) {
116
- result = item;
117
- return false;
118
- }
119
- });
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
- jQuery.each(time, function(key, item) {
127
- if (item.value >= value) {
128
- result = item;
129
- return false;
130
- }
131
- });
132
- return result;
133
- },
134
- findCustomer : function(id) {
135
- var result = null;
136
- jQuery.each(ds.data.customers, function(key, item) {
137
- if (item.id == id) {
138
- result = item;
139
- return false;
140
- }
141
- });
142
- return result;
143
- },
144
- resetCustomers : function() {
145
- ds.data.customers.forEach(function(customer) {
146
- customer.custom_fields = [];
147
- customer.extras = [];
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.payment_create = false;
155
- customer.payment_price = null;
156
- customer.payment_tax = null;
157
- customer.package_id = null;
158
- customer.ca_id = null;
159
- });
160
- },
161
- getDataForStartTime : function() {
162
- var result = ds.data.start_time.slice();
163
- if (
164
- ds.data.app_start_time &&
165
- result.every(function (item) {return item.value !== ds.data.app_start_time.value;})
166
- ) {
167
- result.push(ds.data.app_start_time);
168
- result.sort(function (a, b) {
169
- return a.value < b.value ? -1 : (a.value > b.value ? 1 : 0);
170
- });
171
- }
172
- return result;
173
- },
174
- getDataForEndTime : function() {
175
- var result = [];
176
- if (ds.form.start_time) {
177
- if (ds.form.service && parseInt(ds.form.service.units_max) > 1) {
178
- var units_min = parseInt(ds.form.service.units_min),
179
- units_max = parseInt(ds.form.service.units_max),
180
- start_time = moment(ds.form.start_time.value, 'HH:mm');
181
- for (var units = units_min; units <= units_max; units++) {
182
- var end_time = moment(start_time).add(units * ds.form.service.duration, 'seconds'),
183
- end_hour = parseInt(moment(end_time).format('HH')) + Math.floor((end_time.diff(start_time)) / 3600 / 24000) * 24;
184
- jQuery.each(ds.data.end_time, function (key, item) {
185
- if (item.value == (end_hour < 10 ? '0' + end_hour : end_hour) + ':' + moment(end_time).format('mm')) {
186
- unit_item = jQuery.extend({}, item);
187
- unit_item.title = item.title + ' (' + units +')';
188
- result.push(unit_item);
189
- }
190
- });
191
- }
192
- } else {
193
- var start_time = ds.form.start_time.value.split(':'),
194
- end = (24 + parseInt(start_time[0])) + ':' + start_time[1];
195
- jQuery.each(ds.data.end_time, function (key, item) {
196
- if (item.value > end) {
197
- return false;
198
- }
199
- if (item.value > ds.form.start_time.value) {
200
- result.push(item);
201
- }
202
- });
203
- if (
204
- ds.data.app_end_time &&
205
- ds.data.app_end_time.value > ds.form.start_time.value &&
206
- result.every(function (item) {
207
- return item.value !== ds.data.app_end_time.value;
208
- })
209
- ) {
210
- result.push(ds.data.app_end_time);
211
- result.sort(function (a, b) {
212
- return a.value < b.value ? -1 : (a.value > b.value ? 1 : 0);
213
- });
214
- }
215
- }
216
- }
217
- return result;
218
- },
219
- setEndTimeBasedOnService : function() {
220
- ds.form.end_time_data = ds.getDataForEndTime();
221
- var d = ds.form.service ? ds.form.service.duration * ds.form.service.units_min : ds.data.time_interval;
222
- if (d < 86400) {
223
- ds.form.end_time = ds.findTime('end', moment(ds.form.start_time.value, 'HH:mm').add(d, 'seconds').format('HH:mm'));
224
- }
225
- },
226
- getStartAndEndDates : function() {
227
- if (ds.form.skip_date) {
228
- return {
229
- start_date: null,
230
- end_date : null
231
- }
232
- }
233
- var start_date = moment(ds.form.date.getTime()),
234
- end_date = moment(ds.form.date.getTime()),
235
- start_time = [0,0],
236
- end_time = [0,0]
237
- ;
238
- if (ds.form.service && ds.form.service.duration >= 86400) {
239
- if (ds.form.end_time) {
240
- var _start_time = ds.form.start_time.value.split(':');
241
- var _end_time = ds.form.end_time.value.split(':');
242
- 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])));
243
- end_date.add(duration, 'seconds');
244
- } else if (ds.form.service && ds.form.service.units_max > 1) {
245
- end_date.add(ds.form.service.duration * ds.form.service.units_min, 'seconds');
246
- } else {
247
- end_date.add(ds.form.service.duration, 'seconds');
248
- }
249
- } else {
250
- start_time = ds.form.start_time.value.split(':');
251
- end_time = ds.form.end_time.value.split(':');
252
- }
253
- start_date.hours(start_time[0]);
254
- start_date.minutes(start_time[1]);
255
- end_date.hours(end_time[0]);
256
- end_date.minutes(end_time[1]);
257
- return {
258
- start_date : start_date.format('YYYY-MM-DD HH:mm:00'),
259
- end_date : end_date.format('YYYY-MM-DD HH:mm:00')
260
- };
261
- },
262
- getTotalNumberOfPersons : function () {
263
- var result = 0;
264
- ds.form.customers.forEach(function (item) {
265
- result += parseInt(item.number_of_persons);
266
- });
267
-
268
- return result;
269
- },
270
- getTotalNumberOfNotCancelledPersons: function (exceptCustomer) {
271
- var result = 0;
272
- ds.form.customers.forEach(function (item) {
273
- if ((!exceptCustomer || item.id != exceptCustomer.id) && item.status != 'cancelled' && item.status != 'rejected' && item.status != 'waitlisted') {
274
- result += parseInt(item.number_of_persons);
275
- }
276
- });
277
-
278
- return result;
279
- },
280
- getTotalNumberOfCancelledPersons: function () {
281
- var result = 0;
282
- ds.form.customers.forEach(function (item) {
283
- if (item.status == 'cancelled' || item.status == 'rejected' || item.status == 'waitlisted') {
284
- result += parseInt(item.number_of_persons);
285
- }
286
- });
287
-
288
- return result;
289
- },
290
- getServiceDuration: function () {
291
- var dates = ds.getStartAndEndDates(),
292
- start_date = moment(dates.start_date),
293
- end_date = moment(dates.end_date)
294
- ;
295
-
296
- return end_date.diff(start_date, 'seconds');
297
- }
298
- };
299
-
300
- return ds;
301
- });
302
-
303
- /**
304
- * Controller for 'create/edit appointment' dialog form.
305
- */
306
- module.controller('appointmentDialogCtrl', function($scope, $element, dataSource) {
307
- // Set up initial data.
308
- $scope.$calendar = null;
309
- // Set up data source.
310
- $scope.dataSource = dataSource;
311
- $scope.form = dataSource.form; // shortcut
312
- // Error messages.
313
- $scope.errors = {};
314
- // Callback to be called after editing appointment.
315
- var callback = null;
316
-
317
- /**
318
- * Prepare the form for new event.
319
- *
320
- * @param int staff_id
321
- * @param moment start_date
322
- * @param function _callback
323
- */
324
- $scope.configureNewForm = function(staff_id, start_date, _callback) {
325
- var weekday = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][start_date.format('d')],
326
- staff = dataSource.findStaff(staff_id),
327
- service = staff && staff.services.length == 2 ? staff.services[1] : null,
328
- location = staff && staff.locations.length == 1 ? staff.locations[0] : null
329
- ;
330
- $scope.dataSource.data.app_start_time = null;
331
- $scope.dataSource.data.app_end_time = null;
332
- jQuery.extend($scope.form, {
333
- screen : 'main',
334
- id : null,
335
- staff : staff,
336
- staff_any : null,
337
- service : service,
338
- custom_service_name : null,
339
- custom_service_price : 0,
340
- location : location,
341
- date : start_date.clone().local().toDate(),
342
- skip_date : null,
343
- start_time : dataSource.findTime('start', start_date.format('HH:mm')),
344
- end_time : null,
345
- end_time_data : [],
346
- series_id : null,
347
- repeat : {
348
- enabled : 0,
349
- repeat : 'daily',
350
- daily : { every: 1 },
351
- weekly : { on : [weekday] },
352
- biweekly : { on : [weekday] },
353
- monthly : { on : 'day', day : start_date.format('D'), weekday : weekday },
354
- until : start_date.clone().add(1, 'month').format('YYYY-MM-DD')
355
- },
356
- schedule : {
357
- items : [],
358
- edit : 0,
359
- page : 0,
360
- another_time : []
361
- },
362
- customers : [],
363
- internal_note : null,
364
- expand_customers_list : false
365
- });
366
- $scope.errors = {};
367
- dataSource.setEndTimeBasedOnService();
368
- callback = _callback;
369
-
370
- $scope.prepareExtras();
371
- $scope.prepareCustomFields();
372
- $scope.dataSource.resetCustomers();
373
- $scope.onRepeatChange();
374
- if (staff) {
375
- $scope.onStaffChange();
376
- }
377
- };
378
-
379
- /**
380
- * Prepare the form for editing an event.
381
- */
382
- $scope.configureEditForm = function(appointment_id, _callback) {
383
- $scope.loading = true;
384
- jQuery.post(
385
- ajaxurl,
386
- {action: 'bookly_get_data_for_appointment', id: appointment_id, csrf_token : BooklyL10nAppDialog.csrf_token},
387
- function(response) {
388
- $scope.$apply(function($scope) {
389
- if (response.success) {
390
- var start_date = response.data.start_date === null ? null : moment(response.data.start_date),
391
- end_date = response.data.start_date === null ? null : moment(response.data.end_date),
392
- staff = $scope.dataSource.findStaff(response.data.staff_id);
393
- $scope.dataSource.data.app_start_time = response.data.start_time;
394
- $scope.dataSource.data.app_end_time = response.data.end_time;
395
- jQuery.extend($scope.form, {
396
- screen : 'main',
397
- id : appointment_id,
398
- staff : staff,
399
- staff_any : response.data.staff_any ? staff : null,
400
- service : $scope.dataSource.findService(response.data.staff_id, response.data.service_id),
401
- custom_service_name : response.data.custom_service_name,
402
- custom_service_price : response.data.custom_service_price,
403
- location : $scope.dataSource.findLocation(response.data.staff_id, response.data.location_id),
404
- skip_date : start_date === null ? 1 : 0,
405
- end_time : null,
406
- end_time_data : [],
407
- repeat : {
408
- enabled : 0,
409
- repeat : 'daily',
410
- daily : {every: 1},
411
- weekly : {on: []},
412
- biweekly: {on: []},
413
- monthly : {on: 'day', day: '1', weekday: 'mon'},
414
- until : start_date === null ? moment().add(1, 'month').format('YYYY-MM-DD') : start_date.clone().add(1, 'month').format('YYYY-MM-DD')
415
- },
416
- schedule : {
417
- items : [],
418
- edit : 0,
419
- page : 0,
420
- another_time: []
421
- },
422
- customers : [],
423
- internal_note : response.data.internal_note,
424
- series_id : response.data.series_id,
425
- expand_customers_list: false
426
- });
427
- $scope.form.end_time_data = $scope.dataSource.getDataForEndTime();
428
- if (start_date !== null) {
429
- $scope.form.date = start_date.clone().local().toDate();
430
- $scope.form.start_time = $scope.dataSource.findTime('start', start_date.format('HH:mm'));
431
- $scope.dataSource.setEndTimeBasedOnService();
432
- $scope.form.end_time = start_date.format('YYYY-MM-DD') == end_date.format('YYYY-MM-DD')
433
- ? $scope.dataSource.findTime('end', end_date.format('HH:mm'))
434
- : $scope.dataSource.findTime('end', (Math.floor((end_date - start_date) / 3600000) + start_date.hour()) + end_date.format(':mm'));
435
- } else {
436
- $scope.form.date = moment().local().toDate();
437
- $scope.form.start_time = $scope.dataSource.findTime('start', moment().format('HH:mm'));
438
- $scope.dataSource.setEndTimeBasedOnService();
439
- }
440
-
441
- $scope.prepareExtras();
442
- $scope.prepareCustomFields();
443
- $scope.dataSource.resetCustomers();
444
- $scope.onLocationChange();
445
- $scope.onRepeatChange();
446
-
447
- var customers_ids = [];
448
- response.data.customers.forEach(function (item, i, arr) {
449
- var customer = $scope.dataSource.findCustomer(item.id),
450
- clone = {};
451
- if (customers_ids.indexOf(item.id) === -1) {
452
- customers_ids.push(item.id);
453
- clone = customer;
454
- } else {
455
- // For Error: ngRepeat:dupes & chosen directive
456
- angular.copy(customer, clone);
457
- }
458
- clone.ca_id = item.ca_id;
459
- clone.package_id = item.package_id;
460
- clone.extras = item.extras;
461
- clone.status = item.status;
462
- clone.custom_fields = item.custom_fields;
463
- clone.files = item.files;
464
- clone.number_of_persons = item.number_of_persons;
465
- clone.timezone = item.timezone;
466
- clone.notes = item.notes;
467
- clone.payment_id = item.payment_id;
468
- clone.payment_type = item.payment_type;
469
- clone.payment_title = item.payment_title;
470
- clone.payment_create = item.payment_create;
471
- clone.payment_price = item.payment_price;
472
- clone.payment_tax = item.payment_tax;
473
- clone.compound_token = item.compound_token;
474
- clone.compound_service = item.compound_service;
475
- $scope.form.customers.push(clone);
476
- });
477
- }
478
- $scope.loading = false;
479
- });
480
- },
481
- 'json'
482
- );
483
- $scope.errors = {};
484
- callback = _callback;
485
- };
486
-
487
- var checkAppointmentErrors = function() {
488
- if ($scope.form.staff) {
489
- var dates = $scope.dataSource.getStartAndEndDates(),
490
- customers = [];
491
-
492
- $scope.form.customers.forEach(function (item, i, arr) {
493
- var customer_extras = {};
494
- if ($scope.form.service) {
495
- jQuery('#bookly-extras .service_' + $scope.form.service.id + ' input.extras-count').each(function () {
496
- var extra_id = jQuery(this).data('id');
497
- if (item.extras[extra_id] !== undefined) {
498
- customer_extras[extra_id] = item.extras[extra_id];
499
- }
500
- });
501
- }
502
- customers.push({
503
- id : item.id,
504
- ca_id : item.ca_id,
505
- custom_fields : item.custom_fields,
506
- extras : customer_extras,
507
- number_of_persons : item.number_of_persons,
508
- timezone : item.timezone,
509
- status : item.status
510
- });
511
- });
512
-
513
- jQuery.post(
514
- ajaxurl,
515
- {
516
- action : 'bookly_check_appointment_errors',
517
- csrf_token : BooklyL10nAppDialog.csrf_token,
518
- start_date : dates.start_date,
519
- end_date : dates.end_date,
520
- appointment_id : $scope.form.id,
521
- customers : JSON.stringify(customers),
522
- staff_id : $scope.form.staff.id,
523
- service_id : $scope.form.service ? $scope.form.service.id : null
524
- },
525
- function (response) {
526
- $scope.$apply(function ($scope) {
527
- angular.forEach(response, function (value, error) {
528
- $scope.errors[error] = value;
529
- });
530
- });
531
- },
532
- 'json'
533
- );
534
- }
535
- };
536
-
537
- $scope.onServiceChange = function() {
538
- $scope.dataSource.setEndTimeBasedOnService();
539
- $scope.prepareExtras();
540
- $scope.prepareCustomFields();
541
- $scope.onLocationChange();
542
- checkAppointmentErrors();
543
- };
544
-
545
- $scope.onLocationChange = function () {
546
- if ($scope.form.staff && $scope.form.service) {
547
- var current_service = $scope.dataSource.findService($scope.form.staff.id, $scope.form.service.id),
548
- location_id = $scope.form.location ? $scope.form.location.id : 0;
549
- if (current_service.locations.hasOwnProperty(location_id)) {
550
- $scope.form.service.capacity_min = current_service.locations[location_id].capacity_min;
551
- $scope.form.service.capacity_max = current_service.locations[location_id].capacity_max;
552
- } else if (current_service.locations.hasOwnProperty(0)) {
553
- $scope.form.service.capacity_min = current_service.locations[0].capacity_min;
554
- $scope.form.service.capacity_max = current_service.locations[0].capacity_max;
555
- } else {
556
- $scope.form.service.capacity_min = 1;
557
- $scope.form.service.capacity_max = 1;
558
- }
559
- checkAppointmentErrors();
560
- }
561
- };
562
-
563
- $scope.onStaffChange = function() {
564
- if ($scope.form.staff.services.length == 2) {
565
- $scope.form.service = $scope.form.staff.services[1];
566
- $scope.onServiceChange();
567
- } else {
568
- $scope.form.service = null;
569
- }
570
- $scope.form.location = $scope.form.staff.locations.length == 1 ? $scope.form.staff.locations[0] : null;
571
- $scope.onLocationChange();
572
- };
573
-
574
- $scope.onStartTimeChange = function() {
575
- $scope.dataSource.setEndTimeBasedOnService();
576
- checkAppointmentErrors();
577
- };
578
-
579
- $scope.onEndTimeChange = function() {
580
- checkAppointmentErrors();
581
- };
582
-
583
- $scope.onDateChange = function() {
584
- checkAppointmentErrors();
585
- $scope.onRepeatChange();
586
- };
587
-
588
- $scope.onCustomersChange = function(old_customers, old_nop) {
589
- if (dataSource.form.service && dataSource.form.customers.length > old_customers.length) {
590
- var ids = jQuery.map(old_customers, function(customer) {
591
- return customer.id;
592
- });
593
- var nop = dataSource.form.service.capacity_min - old_nop;
594
- dataSource.form.customers.some(function (item) {
595
- if (jQuery.inArray(item.id, ids) == -1) {
596
- item.number_of_persons = nop > 0 ? nop : 1;
597
- return true;
598
- }
599
- });
600
- }
601
- $scope.errors.customers_appointments_limit = [];
602
- checkAppointmentErrors();
603
- };
604
-
605
- $scope.onSkipDateChange = function() {
606
- checkAppointmentErrors();
607
- };
608
-
609
- $scope.processForm = function() {
610
- $scope.loading = true;
611
-
612
- $scope.errors = {};
613
-
614
- var dates = $scope.dataSource.getStartAndEndDates(),
615
- schedule = [],
616
- customers = []
617
- ;
618
-
619
- angular.forEach($scope.form.schedule.items, function (item) {
620
- if (!item.deleted) {
621
- schedule.push(item.slots);
622
- }
623
- });
624
-
625
- $scope.form.customers.forEach(function (item, i, arr) {
626
- var customer_extras = {};
627
- if ($scope.form.service) {
628
- jQuery('#bookly-extras .service_' + $scope.form.service.id + ' input.extras-count').each(function () {
629
- var extra_id = jQuery(this).data('id');
630
- if (item.extras[extra_id] !== undefined) {
631
- customer_extras[extra_id] = item.extras[extra_id];
632
- }
633
- });
634
- }
635
- customers.push({
636
- id : item.id,
637
- ca_id : item.ca_id,
638
- custom_fields : item.custom_fields,
639
- extras : customer_extras,
640
- number_of_persons : item.number_of_persons,
641
- timezone : item.timezone,
642
- notes : item.notes,
643
- status : item.status,
644
- payment_id : item.payment_id,
645
- payment_create : item.payment_create,
646
- payment_price : item.payment_price,
647
- payment_tax : item.payment_tax
648
- });
649
- });
650
- jQuery.post(
651
- ajaxurl,
652
- {
653
- action : 'bookly_save_appointment_form',
654
- csrf_token : BooklyL10nAppDialog.csrf_token,
655
- id : $scope.form.id || undefined,
656
- staff_id : $scope.form.staff ? $scope.form.staff.id : undefined,
657
- service_id : $scope.form.service ? $scope.form.service.id : undefined,
658
- custom_service_name : $scope.form.custom_service_name,
659
- custom_service_price : $scope.form.custom_service_price,
660
- location_id : $scope.form.location ? $scope.form.location.id : undefined,
661
- skip_date : $scope.form.skip_date,
662
- start_date : dates.start_date,
663
- end_date : dates.end_date,
664
- repeat : JSON.stringify($scope.form.repeat),
665
- schedule : schedule,
666
- customers : JSON.stringify(customers),
667
- notification : $scope.form.notification,
668
- internal_note : $scope.form.internal_note,
669
- created_from : typeof BooklySCCalendarL10n !== 'undefined' ? 'staff-cabinet' : 'backend'
670
- },
671
- function (response) {
672
- $scope.$apply(function($scope) {
673
- if (response.success) {
674
- if (callback) {
675
- // Call callback.
676
- callback(response.data);
677
- }
678
- // Close the dialog.
679
- $element.children().modal('hide');
680
- } else {
681
- $scope.errors = response.errors;
682
- }
683
- $scope.loading = false;
684
- });
685
- },
686
- 'json'
687
- );
688
- };
689
-
690
- // On 'Cancel' button click.
691
- $scope.closeDialog = function () {
692
- // Close the dialog.
693
- $element.children().modal('hide');
694
- };
695
-
696
- $scope.statusToString = function (status) {
697
- return dataSource.data.status.items[status];
698
- };
699
-
700
- /**************************************************************************************************************
701
- * New customer *
702
- **************************************************************************************************************/
703
-
704
- /**
705
- * Create new customer.
706
- * @param customer
707
- */
708
- $scope.createCustomer = function(customer) {
709
- // Add new customer to the list.
710
- var nop = 1;
711
- if (dataSource.form.service) {
712
- nop = dataSource.form.service.capacity_min - dataSource.getTotalNumberOfNotCancelledPersons();
713
- if (nop < 1) {
714
- nop = 1;
715
- }
716
- }
717
- var new_customer = {
718
- id : customer.id.toString(),
719
- name : customer.full_name,
720
- custom_fields : customer.custom_fields,
721
- extras : customer.extras,
722
- status : customer.status,
723
- timezone : customer.timezone,
724
- number_of_persons : nop,
725
- notes : null,
726
- compound_token : null,
727
- payment_id : null,
728
- payment_type : null,
729
- payment_title : null,
730
- payment_create : false,
731
- payment_price : null,
732
- payment_tax : null
733
- };
734
-
735
- if (customer.email || customer.phone){
736
- new_customer.name += ' (' + [customer.email, customer.phone].filter(Boolean).join(', ') + ')';
737
- }
738
-
739
- dataSource.data.customers.push(new_customer);
740
-
741
- // Make it selected.
742
- if (!dataSource.form.service || dataSource.form.customers.length < dataSource.form.service.capacity_max) {
743
- dataSource.form.customers.push(new_customer);
744
- }
745
- };
746
-
747
- $scope.removeCustomer = function(customer) {
748
- $scope.form.customers.splice($scope.form.customers.indexOf(customer), 1);
749
- checkAppointmentErrors();
750
- };
751
-
752
- $scope.openNewCustomerDialog = function() {
753
- var $dialog = jQuery('#bookly-customer-dialog');
754
- $dialog.modal({show: true});
755
- };
756
-
757
- /**************************************************************************************************************
758
- * Customer Details *
759
- **************************************************************************************************************/
760
-
761
- $scope.editCustomerDetails = function(customer) {
762
- var $dialog = jQuery('#bookly-customer-details-dialog');
763
- $dialog.find('input.bookly-custom-field:text, textarea.bookly-custom-field, select.bookly-custom-field, input.bookly-js-file').val('');
764
- $dialog.find('input.bookly-custom-field:checkbox, input.bookly-custom-field:radio').prop('checked', false);
765
- $dialog.find('#bookly-extras :checkbox').prop('checked', false);
766
-
767
- customer.custom_fields.forEach(function (field) {
768
- var $custom_field = $dialog.find('#bookly-js-custom-fields > *[data-id="' + field.id + '"]');
769
- switch ($custom_field.data('type')) {
770
- case 'checkboxes':
771
- field.value.forEach(function (value) {
772
- $custom_field.find('.bookly-custom-field').filter(function () {
773
- return this.value == value;
774
- }).prop('checked', true);
775
- });
776
- break;
777
- case 'radio-buttons':
778
- $custom_field.find('.bookly-custom-field').filter(function () {
779
- return this.value == field.value;
780
- }).prop('checked', true);
781
- break;
782
- default:
783
- $custom_field.find('.bookly-custom-field').val(field.value);
784
- break;
785
- }
786
- });
787
-
788
- $dialog.find('#bookly-extras .extras-count').val(0);
789
- angular.forEach(customer.extras, function (extra_count, extra_id) {
790
- $dialog.find('#bookly-extras .extras-count[data-id="' + extra_id + '"]').val(extra_count);
791
- });
792
-
793
- // Prepare select for number of persons.
794
- var $number_of_persons = $dialog.find('#bookly-number-of-persons');
795
-
796
- var max = $scope.form.service
797
- ? ($scope.form.service.id
798
- ? parseInt($scope.form.service.capacity_max) - $scope.dataSource.getTotalNumberOfNotCancelledPersons(customer)
799
- : 999)
800
- : 1;
801
- $number_of_persons.empty();
802
- for (var i = 1; i <= max; ++i) {
803
- $number_of_persons.append('<option value="' + i + '">' + i + '</option>');
804
- }
805
- if (customer.number_of_persons > max) {
806
- $number_of_persons.append('<option value="' + customer.number_of_persons + '">' + customer.number_of_persons + '</option>');
807
- }
808
- $number_of_persons.val(customer.number_of_persons);
809
- $dialog.find('#bookly-appointment-status').val(customer.status);
810
- $dialog.find('#bookly-appointment-notes').val(customer.notes);
811
- $dialog.find('#bookly-deposit-due').val(customer.due);
812
- $dialog.find('#bookly-customer-time-zone').val(customer.timezone ? customer.timezone : '');
813
- $scope.edit_customer = customer;
814
-
815
- $dialog.modal({show: true})
816
- .on('hidden.bs.modal', function () {
817
- jQuery('body').addClass('modal-open');
818
- });
819
-
820
- jQuery(document.body).trigger('bookly.edit.customer_details', [$dialog, $scope.edit_customer]);
821
- };
822
-
823
- $scope.prepareExtras = function () {
824
- if ($scope.form.service) {
825
- jQuery('#bookly-extras > *').hide();
826
- var $service_extras = jQuery('#bookly-extras .service_' + $scope.form.service.id);
827
- if ($service_extras.length) {
828
- $service_extras.show();
829
- jQuery('#bookly-extras').show();
830
- } else {
831
- jQuery('#bookly-extras').hide();
832
- }
833
- } else {
834
- jQuery('#bookly-extras').hide();
835
- }
836
- };
837
-
838
- // Hide or unhide custom fields for current service
839
- $scope.prepareCustomFields = function () {
840
- if (BooklyL10nAppDialog.cf_per_service == 1) {
841
- var show = false;
842
- jQuery('#bookly-js-custom-fields div[data-services]').each(function() {
843
- var $this = jQuery(this);
844
- if (dataSource.form.service !== null) {
845
- var services = $this.data('services');
846
- if (services && jQuery.inArray(dataSource.form.service.id, services) > -1) {
847
- $this.show();
848
- show = true;
849
- } else {
850
- $this.hide();
851
- }
852
- } else {
853
- $this.hide();
854
- }
855
- });
856
- if (show) {
857
- jQuery('#bookly-js-custom-fields').show();
858
- } else {
859
- jQuery('#bookly-js-custom-fields').hide();
860
- }
861
- }
862
- };
863
-
864
- $scope.saveCustomFields = function() {
865
- var result = [],
866
- extras = {},
867
- $fields = jQuery('#bookly-js-custom-fields > *'),
868
- $status = jQuery('#bookly-appointment-status'),
869
- $timezone = jQuery('#bookly-customer-time-zone'),
870
- $number_of_persons = jQuery('#bookly-number-of-persons'),
871
- $notes = jQuery('#bookly-appointment-notes'),
872
- $extras = jQuery('#bookly-extras')
873
- ;
874
-
875
- $fields.each(function () {
876
- var $this = jQuery(this),
877
- value;
878
- if ($this.is(':visible')) {
879
- switch ($this.data('type')) {
880
- case 'checkboxes':
881
- value = [];
882
- $this.find('.bookly-custom-field:checked').each(function () {
883
- value.push(this.value);
884
- });
885
- break;
886
- case 'radio-buttons':
887
- value = $this.find('.bookly-custom-field:checked').val();
888
- break;
889
- default:
890
- value = $this.find('.bookly-custom-field').val();
891
- break;
892
- }
893
- result.push({id: $this.data('id'), value: value});
894
- }
895
- });
896
-
897
- if ($scope.form.service) {
898
- $extras.find(' .service_' + $scope.form.service.id + ' input.extras-count').each(function () {
899
- if (this.value > 0) {
900
- extras[jQuery(this).data('id')] = this.value;
901
- }
902
- });
903
- }
904
-
905
- $scope.edit_customer.status = $status.val();
906
- $scope.edit_customer.timezone = $timezone.val();
907
- $scope.edit_customer.number_of_persons = $number_of_persons.val();
908
- $scope.edit_customer.notes = $notes.val();
909
- $scope.edit_customer.custom_fields = result;
910
- $scope.edit_customer.extras = extras;
911
-
912
- jQuery('#bookly-customer-details-dialog').modal('hide');
913
- if ($extras.length > 0) {
914
- // Check if intersection with another appointment exists.
915
- checkAppointmentErrors();
916
- }
917
- };
918
-
919
- /**************************************************************************************************************
920
- * Payment Details *
921
- **************************************************************************************************************/
922
-
923
- $scope.attachPaymentModal = function (customer) {
924
- var $dialog = jQuery('#bookly-payment-attach-modal');
925
- $scope.form.attach = {
926
- customer_id : customer.id,
927
- payment_method: 'create',
928
- payment_price : null,
929
- payment_tax : null,
930
- payment_id : null
931
- };
932
- $dialog.modal({show: true}).on('hidden.bs.modal', function () {
933
- jQuery('body').addClass('modal-open');
934
- });
935
- };
936
-
937
- $scope.attachPayment = function (attach_method, price, tax, payment_id, customer_id) {
938
- var $dialog = jQuery('#bookly-payment-details-modal');
939
- if (attach_method == 'search') {
940
- $dialog.data('payment_id', payment_id).data('payment_bind', true).data('customer_id', customer_id).modal({show: true}).on('hidden.bs.modal', function () {
941
- jQuery('body').addClass('modal-open');
942
- });
943
- } else {
944
- jQuery.each($scope.dataSource.data.customers, function (key, item) {
945
- if (item.id == customer_id) {
946
- item.payment_create = true;
947
- item.payment_price = price;
948
- item.payment_tax = tax;
949
- item.payment_type = 'partial';
950
- }
951
- });
952
- }
953
- };
954
-
955
- $scope.callbackPayment = function (payment_action, payment_id, payment_title, customer_id, payment_type) {
956
- if (payment_action == 'bind') {
957
- // Bind payment
958
- jQuery.each($scope.dataSource.data.customers, function (key, item) {
959
- if (item.id == customer_id) {
960
- item.payment_id = payment_id;
961
- item.payment_type = payment_type;
962
- item.payment_title = payment_title;
963
- }
964
- });
965
- } else {
966
- // Complete payment
967
- jQuery.each($scope.dataSource.data.customers, function (key, item) {
968
- if (item.payment_id == payment_id) {
969
- item.payment_type = 'full';
970
- item.payment_title = payment_title;
971
- }
972
- });
973
- }
974
- };
975
-
976
- /**************************************************************************************************************
977
- * Package Schedule *
978
- **************************************************************************************************************/
979
-
980
- $scope.editPackageSchedule = function(customer) {
981
- jQuery(document.body).trigger('bookly_packages.schedule_dialog', [customer.package_id, function (deleted) {
982
- if (jQuery.inArray(Number(customer.ca_id), deleted) != -1) {
983
- $scope.removeCustomer(customer);
984
- }
985
- if (callback) {
986
- // Call callback.
987
- callback('refresh');
988
- }
989
- }, true]);
990
- };
991
-
992
- /**************************************************************************************************************
993
- * Repeat Times in Recurring Appointments *
994
- **************************************************************************************************************/
995
- $scope.isDateMatchesSelections = function (current_date) {
996
- var current_day = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][current_date.format('d')];
997
- switch ($scope.form.repeat.repeat) {
998
- case 'daily':
999
- 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)) {
1000
- return true;
1001
- }
1002
- break;
1003
- case 'weekly':
1004
- case 'biweekly':
1005
- 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)) {
1006
- return true;
1007
- }
1008
- break;
1009
- case 'monthly':
1010
- switch ($scope.form.repeat.monthly.on) {
1011
- case 'day':
1012
- if (current_date.format('D') == $scope.form.repeat.monthly.day) {
1013
- return true;
1014
- }
1015
- break;
1016
- case 'last':
1017
- if (current_day == $scope.form.repeat.monthly.weekday && current_date.clone().endOf('month').diff(current_date, 'days') < 7) {
1018
- return true;
1019
- }
1020
- break;
1021
- default:
1022
- var month_diff = current_date.diff(current_date.clone().startOf('month'), 'days'),
1023
- weeks = ['first', 'second', 'third', 'fourth'],
1024
- week_number = weeks.indexOf($scope.form.repeat.monthly.on);
1025
-
1026
- if (current_day == $scope.form.repeat.monthly.weekday && month_diff >= week_number * 7 && month_diff < (week_number + 1) * 7) {
1027
- return true;
1028
- }
1029
- }
1030
- break;
1031
- }
1032
-
1033
- return false;
1034
- };
1035
- $scope.onRepeatChange = function () {
1036
- if (jQuery('#bookly-repeat-enabled').length && !$scope.form.skip_date) {
1037
- var number_of_times = 0,
1038
- date_until = moment($scope.form.repeat.until).add(1, 'days'),
1039
- current_date = moment($scope.dataSource.form.date.getTime());
1040
- do {
1041
- if ($scope.isDateMatchesSelections(current_date)) {
1042
- number_of_times++;
1043
- }
1044
- current_date.add(1, 'days');
1045
- } while (current_date.isBefore(date_until));
1046
- $scope.form.repeat.times = number_of_times;
1047
- }
1048
- };
1049
- $scope.onRepeatChangeTimes = function () {
1050
- var number_of_times = 0,
1051
- date_until = moment($scope.dataSource.form.date.getTime()).add(5, 'years'),
1052
- current_date = moment($scope.dataSource.form.date.getTime());
1053
- do {
1054
- if ($scope.isDateMatchesSelections(current_date)) {
1055
- number_of_times++
1056
- }
1057
- current_date.add(1, 'days');
1058
- } while (number_of_times < $scope.form.repeat.times && current_date.isBefore(date_until));
1059
- $scope.form.repeat.until = current_date.subtract(1, 'days').format('YYYY-MM-DD');
1060
- };
1061
-
1062
- /**************************************************************************************************************
1063
- * Schedule of Recurring Appointments *
1064
- **************************************************************************************************************/
1065
-
1066
- $scope.schSchedule = function ($event) {
1067
- var extras = [];
1068
- $scope.form.customers.forEach(function (item, i, arr) {
1069
- extras.push(item.extras);
1070
- });
1071
-
1072
- if (
1073
- ($scope.form.repeat.repeat == 'weekly' || $scope.form.repeat.repeat == 'biweekly') &&
1074
- $scope.form.repeat[$scope.form.repeat.repeat].on.length == 0
1075
- ) {
1076
- $scope.errors.repeat_weekdays_empty = true;
1077
- } else {
1078
- delete $scope.errors.repeat_weekdays_empty;
1079
- var ladda = Ladda.create($event.currentTarget);
1080
- ladda.start();
1081
- var dates = $scope.dataSource.getStartAndEndDates();
1082
- jQuery.post(
1083
- ajaxurl,
1084
- {
1085
- action : 'bookly_recurring_appointments_get_schedule',
1086
- csrf_token : BooklyL10nAppDialog.csrf_token,
1087
- staff_id : $scope.form.staff.id,
1088
- service_id : $scope.form.service.id,
1089
- location_id : $scope.form.location ? $scope.form.location.id : null,
1090
- datetime : dates.start_date,
1091
- until : $scope.form.repeat.until,
1092
- repeat : $scope.form.repeat.repeat,
1093
- params : $scope.form.repeat[$scope.form.repeat.repeat],
1094
- extras : extras,
1095
- duration : $scope.form.service.id ? undefined : $scope.dataSource.getServiceDuration()
1096
- },
1097
- function (response) {
1098
- $scope.$apply(function($scope) {
1099
- $scope.form.schedule.items = response.data;
1100
- $scope.form.schedule.page = 0;
1101
- $scope.form.schedule.another_time = [];
1102
- angular.forEach($scope.form.schedule.items, function (item) {
1103
- if (item.another_time) {
1104
- var page = parseInt( ( item.index - 1 ) / 10 ) + 1;
1105
- if ($scope.form.schedule.another_time.indexOf(page) < 0) {
1106
- $scope.form.schedule.another_time.push(page);
1107
- }
1108
- }
1109
- });
1110
- $scope.form.screen = 'schedule';
1111
- ladda.stop();
1112
- });
1113
- },
1114
- 'json'
1115
- );
1116
- }
1117
- };
1118
- $scope.schFormatDate = function(date) {
1119
- var m = moment(date),
1120
- weekday = m.format('d'),
1121
- month = m.format('M'),
1122
- day = m.format('DD');
1123
-
1124
- return BooklyL10nAppDialog.dateOptions.dayNamesMin[weekday] + ', ' + BooklyL10nAppDialog.dateOptions.monthNamesShort[month-1] + ' ' + day;
1125
- };
1126
- $scope.schFormatTime = function(slots, options) {
1127
- for (var i = 0; i < options.length; ++ i) {
1128
- if (slots == options[i].value) {
1129
- return options[i].title;
1130
- }
1131
- }
1132
- };
1133
- $scope.schFirstPage = function() {
1134
- return $scope.form.schedule.page == 0;
1135
- };
1136
- $scope.schLastPage = function() {
1137
- var lastPageNum = Math.ceil($scope.form.schedule.items.length / 10 - 1);
1138
- return $scope.form.schedule.page == lastPageNum;
1139
- };
1140
- $scope.schNumberOfPages = function() {
1141
- return Math.ceil($scope.form.schedule.items.length / 10);
1142
- };
1143
- $scope.schStartingItem = function() {
1144
- return $scope.form.schedule.page * 10;
1145
- };
1146
- $scope.schPageBack = function() {
1147
- $scope.form.schedule.page = $scope.form.schedule.page - 1;
1148
- };
1149
- $scope.schPageForward = function() {
1150
- $scope.form.schedule.page = $scope.form.schedule.page + 1;
1151
- };
1152
- $scope.schOnWeekdayClick = function (weekday) {
1153
- var idx = $scope.form.repeat.weekly.on.indexOf(weekday);
1154
-
1155
- // is currently selected
1156
- if (idx > -1) {
1157
- $scope.form.repeat.weekly.on.splice(idx, 1);
1158
- }
1159
- // is newly selected
1160
- else {
1161
- $scope.form.repeat.weekly.on.push(weekday);
1162
- }
1163
- // copy weekly to biweekly
1164
- $scope.form.repeat.biweekly.on = $scope.form.repeat.weekly.on.slice();
1165
- $scope.onRepeatChange();
1166
- };
1167
- $scope.schOnDateChange = function(item) {
1168
- var extras = [];
1169
- $scope.form.customers.forEach(function (item, i, arr) {
1170
- extras.push(item.extras);
1171
- });
1172
-
1173
- var exclude = [];
1174
- angular.forEach($scope.form.schedule.items, function (_item) {
1175
- if (item.slots != _item.slots && !_item.deleted) {
1176
- exclude.push(_item.slots);
1177
- }
1178
- });
1179
- jQuery.post(
1180
- ajaxurl,
1181
- {
1182
- action : 'bookly_recurring_appointments_get_schedule',
1183
- csrf_token : BooklyL10nAppDialog.csrf_token,
1184
- staff_id : $scope.form.staff.id,
1185
- service_id : $scope.form.service.id,
1186
- location_id : $scope.form.location ? $scope.form.location.id : null,
1187
- datetime : item.date + ' 00:00',
1188
- until : item.date,
1189
- repeat : 'daily',
1190
- params : {every: 1},
1191
- with_options : 1,
1192
- exclude : exclude,
1193
- extras : extras,
1194
- duration : $scope.form.service.id ? undefined : $scope.dataSource.getServiceDuration()
1195
- },
1196
- function (response) {
1197
- $scope.$apply(function($scope) {
1198
- if (response.data.length) {
1199
- item.options = response.data[0].options;
1200
- var found = false;
1201
- jQuery.each(item.options, function (key, option) {
1202
- if (option.value == item.slots) {
1203
- found = true;
1204
- return false;
1205
- }
1206
- });
1207
- if (!found) {
1208
- jQuery.each(item.options, function (key, option) {
1209
- if (!option.disabled) {
1210
- item.slots = option.value;
1211
- return false;
1212
- }
1213
- });
1214
- }
1215
- } else {
1216
- item.options = [];
1217
- }
1218
- });
1219
- },
1220
- 'json'
1221
- );
1222
- };
1223
- $scope.schIsScheduleEmpty = function () {
1224
- return $scope.form.schedule.items.every(function(item) {
1225
- return item.deleted;
1226
- });
1227
- };
1228
- $scope.schDateOptions = jQuery.extend({}, BooklyL10nAppDialog.dateOptions, {dateFormat: 'D, M dd, yy'});
1229
- $scope.schViewSeries = function () {
1230
- jQuery(document.body).trigger( 'recurring_appointments.series_dialog', [ $scope.form.series_id, function (event) {
1231
- // Switch to the event owner tab.
1232
- jQuery('li[data-staff_id=' + event.staffId + ']').click();
1233
- } ] );
1234
- };
1235
-
1236
- /**
1237
- * Datepicker options.
1238
- */
1239
- $scope.dateOptions = BooklyL10nAppDialog.dateOptions;
1240
- });
1241
-
1242
- /**
1243
- * Directive for slide up/down.
1244
- */
1245
- module.directive('mySlideUp', function() {
1246
- return function(scope, element, attrs) {
1247
- element.hide();
1248
- // watch the expression, and update the UI on change.
1249
- scope.$watch(attrs.mySlideUp, function(value) {
1250
- if (value) {
1251
- element.delay(0).slideDown();
1252
- } else {
1253
- element.slideUp();
1254
- }
1255
- });
1256
- };
1257
- });
1258
-
1259
- /**
1260
- * Directive for Popover jQuery plugin.
1261
- */
1262
- module.directive('popover', function() {
1263
- return function(scope, element, attrs) {
1264
- element.popover({
1265
- trigger : 'hover',
1266
- content : function() { return this.getAttribute('popover'); },
1267
- html : true,
1268
- placement: 'top',
1269
- 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>'
1270
- });
1271
- };
1272
- });
1273
-
1274
- /**
1275
- * Filters for pagination in Schedule.
1276
- */
1277
- module.filter('startFrom', function() {
1278
- return function(input, start){
1279
- start = +start;
1280
- return input.slice(start);
1281
- }
1282
- });
1283
- module.filter('range', function() {
1284
- return function(input, total) {
1285
- total = parseInt(total);
1286
-
1287
- for (var i = 1; i <= total; ++ i) {
1288
- input.push(i);
1289
- }
1290
-
1291
- return input;
1292
- };
1293
- });
1294
-
1295
- jQuery('#bookly-select2').select2({
1296
- width: '100%',
1297
- theme: 'bootstrap',
1298
- allowClear: false,
1299
- language : {
1300
- noResults: function() { return BooklyL10nAppDialog.no_result_found; }
1301
- }
1302
- });
1303
- })();
1304
-
1305
- /**
1306
- * @param int appointment_id
1307
- * @param int staff_id
1308
- * @param moment start_date
1309
- * @param function callback
1310
- */
1311
- var showAppointmentDialog = function (appointment_id, staff_id, start_date, callback) {
1312
- var $dialog = jQuery('#bookly-appointment-dialog');
1313
- var $scope = angular.element($dialog[0]).scope();
1314
- $scope.$apply(function ($scope) {
1315
- $scope.loading = true;
1316
- $dialog
1317
- .find('.modal-title')
1318
- .text(appointment_id ? BooklyL10nAppDialog.title.edit_appointment : BooklyL10nAppDialog.title.new_appointment);
1319
- // Populate data source.
1320
- $scope.dataSource.loadData().then(function() {
1321
- $scope.loading = false;
1322
- if (appointment_id) {
1323
- $scope.configureEditForm(appointment_id, callback);
1324
- } else {
1325
- $scope.configureNewForm(staff_id, start_date, callback);
1326
- }
1327
- });
1328
- });
1329
-
1330
- // hide customer details dialog, if it remained opened.
1331
- if (jQuery('#bookly-customer-details-dialog').hasClass('in')) {
1332
- jQuery('#bookly-customer-details-dialog').modal('hide');
1333
- }
1334
-
1335
- // hide new customer dialog, if it remained opened.
1336
- if (jQuery('#bookly-customer-dialog').hasClass('in')) {
1337
- jQuery('#bookly-customer-dialog').modal('hide');
1338
- }
1339
-
1340
- $dialog.modal('show');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1341
  };
1
+ ;(function() {
2
+
3
+ var module = angular.module('appointmentDialog', ['ui.date', 'customerDialog', 'paymentDetailsDialog']);
4
+
5
+ /**
6
+ * DataSource service.
7
+ */
8
+ module.factory('dataSource', function($q) {
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
+ extras_consider_duration: true,
23
+ extras_multiply_nop : true
24
+ },
25
+ form : {
26
+ screen : null,
27
+ id : null,
28
+ staff : null,
29
+ staff_any : null,
30
+ service : null,
31
+ custom_service_name : null,
32
+ custom_service_price: null,
33
+ location : null,
34
+ skip_date : null,
35
+ date : null,
36
+ start_time : null,
37
+ end_time : null,
38
+ repeat : {
39
+ enabled : null,
40
+ repeat : null,
41
+ daily : { every : null },
42
+ weekly : { on : null },
43
+ biweekly : { on : null },
44
+ monthly : { on : null, day : null, weekday : null },
45
+ until : null
46
+ },
47
+ schedule : {
48
+ items : [],
49
+ edit : null,
50
+ page : null,
51
+ another_time : []
52
+ },
53
+ customers : [],
54
+ notification : null,
55
+ series_id : null,
56
+ expand_customers_list : false
57
+ },
58
+ l10n : {
59
+ staff_any: BooklyL10nAppDialog.staff_any
60
+ },
61
+ loadData : function() {
62
+ var deferred = $q.defer();
63
+ if (!ds.loaded) {
64
+ jQuery.get(
65
+ ajaxurl,
66
+ { action : 'bookly_get_data_for_appointment_form', csrf_token : BooklyL10nAppDialog.csrf_token },
67
+ function(data) {
68
+ ds.loaded = true;
69
+ ds.data = data;
70
+
71
+ if (data.staff.length) {
72
+ ds.form.staff = data.staff[0];
73
+ }
74
+
75
+ ds.form.start_time = data.start_time[0];
76
+ ds.form.end_time = data.end_time[1];
77
+ deferred.resolve();
78
+ },
79
+ 'json'
80
+ );
81
+ } else {
82
+ deferred.resolve();
83
+ }
84
+
85
+ return deferred.promise;
86
+ },
87
+ findStaff : function(id) {
88
+ var result = null;
89
+ jQuery.each(ds.data.staff, function(key, item) {
90
+ if (item.id == id) {
91
+ result = item;
92
+ return false;
93
+ }
94
+ });
95
+ return result;
96
+ },
97
+ findService : function(staff_id, id) {
98
+ var result = null,
99
+ staff = ds.findStaff(staff_id);
100
+
101
+ if (staff !== null) {
102
+ jQuery.each(staff.services, function(key, item) {
103
+ if (item.id == id) {
104
+ result = item;
105
+ return false;
106
+ }
107
+ });
108
+ }
109
+ return result;
110
+ },
111
+ findLocation : function(staff_id, id) {
112
+ var result = null,
113
+ staff = ds.findStaff(staff_id);
114
+
115
+ if (staff !== null) {
116
+ jQuery.each(staff.locations, function(key, item) {
117
+ if (item.id == id) {
118
+ result = item;
119
+ return false;
120
+ }
121
+ });
122
+ }
123
+ return result;
124
+ },
125
+ findTime : function(source, value) {
126
+ var result = null,
127
+ time = source == 'start' ? ds.getDataForStartTime() : ds.form.end_time_data;
128
+ jQuery.each(time, function(key, item) {
129
+ if (item.value >= value) {
130
+ result = item;
131
+ return false;
132
+ }
133
+ });
134
+ return result;
135
+ },
136
+ findCustomer : function(id) {
137
+ var result = null;
138
+ jQuery.each(ds.data.customers, function(key, item) {
139
+ if (item.id == id) {
140
+ result = item;
141
+ return false;
142
+ }
143
+ });
144
+ return result;
145
+ },
146
+ resetCustomers : function() {
147
+ ds.data.customers.forEach(function(customer) {
148
+ customer.custom_fields = [];
149
+ customer.extras = [];
150
+ customer.extras_consider_duration = ds.data.extras_consider_duration;
151
+ customer.extras_multiply_nop = ds.data.extras_multiply_nop;
152
+ customer.number_of_persons = 1;
153
+ customer.notes = null;
154
+ customer.collaborative_token = null;
155
+ customer.collaborative_service = null;
156
+ customer.compound_token = null;
157
+ customer.compound_service = null;
158
+ customer.payment_id = null;
159
+ customer.payment_type = null;
160
+ customer.payment_title = null;
161
+ customer.payment_create = false;
162
+ customer.payment_price = null;
163
+ customer.payment_tax = null;
164
+ customer.package_id = null;
165
+ customer.series_id = null;
166
+ customer.ca_id = null;
167
+ });
168
+ },
169
+ getDataForStartTime : function() {
170
+ var result = ds.data.start_time.slice();
171
+ if (
172
+ ds.data.app_start_time &&
173
+ result.every(function (item) {return item.value !== ds.data.app_start_time.value;})
174
+ ) {
175
+ result.push(ds.data.app_start_time);
176
+ result.sort(function (a, b) {
177
+ return a.value < b.value ? -1 : (a.value > b.value ? 1 : 0);
178
+ });
179
+ }
180
+ return result;
181
+ },
182
+ getDataForEndTime : function() {
183
+ var result = [];
184
+ if (ds.form.start_time) {
185
+ if (ds.form.service && parseInt(ds.form.service.units_max) > 1) {
186
+ var units_min = parseInt(ds.form.service.units_min),
187
+ units_max = parseInt(ds.form.service.units_max),
188
+ start_time = moment(ds.form.start_time.value, 'HH:mm');
189
+ for (var units = units_min; units <= units_max; units++) {
190
+ var end_time = moment(start_time).add(units * ds.form.service.duration, 'seconds'),
191
+ end_hour = parseInt(moment(end_time).format('HH')) + Math.floor((end_time.diff(start_time)) / 3600 / 24000) * 24;
192
+ jQuery.each(ds.data.end_time, function (key, item) {
193
+ if (item.value == (end_hour < 10 ? '0' + end_hour : end_hour) + ':' + moment(end_time).format('mm')) {
194
+ unit_item = jQuery.extend({}, item);
195
+ unit_item.title = item.title + ' (' + units +')';
196
+ result.push(unit_item);
197
+ }
198
+ });
199
+ }
200
+ } else {
201
+ var start_time = ds.form.start_time.value.split(':'),
202
+ end = (24 + parseInt(start_time[0])) + ':' + start_time[1];
203
+ jQuery.each(ds.data.end_time, function (key, item) {
204
+ if (item.value > end) {
205
+ return false;
206
+ }
207
+ if (item.value > ds.form.start_time.value) {
208
+ result.push(item);
209
+ }
210
+ });
211
+ if (
212
+ ds.data.app_end_time &&
213
+ ds.data.app_end_time.value > ds.form.start_time.value &&
214
+ result.every(function (item) {
215
+ return item.value !== ds.data.app_end_time.value;
216
+ })
217
+ ) {
218
+ result.push(ds.data.app_end_time);
219
+ result.sort(function (a, b) {
220
+ return a.value < b.value ? -1 : (a.value > b.value ? 1 : 0);
221
+ });
222
+ }
223
+ }
224
+ }
225
+ return result;
226
+ },
227
+ setEndTimeBasedOnService : function() {
228
+ ds.form.end_time_data = ds.getDataForEndTime();
229
+ var d = ds.form.service ? ds.form.service.duration * ds.form.service.units_min : ds.data.time_interval;
230
+ if (d < 86400) {
231
+ ds.form.end_time = ds.findTime('end', moment(ds.form.start_time.value, 'HH:mm').add(d, 'seconds').format('HH:mm'));
232
+ }
233
+ },
234
+ getStartAndEndDates : function() {
235
+ if (ds.form.skip_date) {
236
+ return {
237
+ start_date: null,
238
+ end_date : null
239
+ }
240
+ }
241
+ var start_date = moment(ds.form.date.getTime()),
242
+ end_date = moment(ds.form.date.getTime()),
243
+ start_time = [0,0],
244
+ end_time = [0,0]
245
+ ;
246
+ if (ds.form.service && ds.form.service.duration >= 86400) {
247
+ if (ds.form.end_time) {
248
+ var _start_time = ds.form.start_time.value.split(':');
249
+ var _end_time = ds.form.end_time.value.split(':');
250
+ 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])));
251
+ end_date.add(duration, 'seconds');
252
+ } else if (ds.form.service && ds.form.service.units_max > 1) {
253
+ end_date.add(ds.form.service.duration * ds.form.service.units_min, 'seconds');
254
+ } else {
255
+ end_date.add(ds.form.service.duration, 'seconds');
256
+ }
257
+ } else {
258
+ start_time = ds.form.start_time.value.split(':');
259
+ end_time = ds.form.end_time.value.split(':');
260
+ }
261
+ start_date.hours(start_time[0]);
262
+ start_date.minutes(start_time[1]);
263
+ end_date.hours(end_time[0]);
264
+ end_date.minutes(end_time[1]);
265
+ return {
266
+ start_date : start_date.format('YYYY-MM-DD HH:mm:00'),
267
+ end_date : end_date.format('YYYY-MM-DD HH:mm:00')
268
+ };
269
+ },
270
+ getTotalNumberOfPersons : function () {
271
+ var result = 0;
272
+ ds.form.customers.forEach(function (item) {
273
+ result += parseInt(item.number_of_persons);
274
+ });
275
+
276
+ return result;
277
+ },
278
+ getTotalNumberOfNotCancelledPersons: function (exceptCustomer) {
279
+ var result = 0;
280
+ ds.form.customers.forEach(function (item) {
281
+ if ((!exceptCustomer || item.id != exceptCustomer.id) && item.status != 'cancelled' && item.status != 'rejected' && item.status != 'waitlisted') {
282
+ result += parseInt(item.number_of_persons);
283
+ }
284
+ });
285
+
286
+ return result;
287
+ },
288
+ getTotalNumberOfCancelledPersons: function () {
289
+ var result = 0;
290
+ ds.form.customers.forEach(function (item) {
291
+ if (item.status == 'cancelled' || item.status == 'rejected' || item.status == 'waitlisted') {
292
+ result += parseInt(item.number_of_persons);
293
+ }
294
+ });
295
+
296
+ return result;
297
+ },
298
+ getServiceDuration: function () {
299
+ var dates = ds.getStartAndEndDates(),
300
+ start_date = moment(dates.start_date),
301
+ end_date = moment(dates.end_date)
302
+ ;
303
+
304
+ return end_date.diff(start_date, 'seconds');
305
+ }
306
+ };
307
+
308
+ return ds;
309
+ });
310
+
311
+ /**
312
+ * Controller for 'create/edit appointment' dialog form.
313
+ */
314
+ module.controller('appointmentDialogCtrl', function($scope, $element, dataSource) {
315
+ // Set up initial data.
316
+ $scope.$calendar = null;
317
+ // Set up data source.
318
+ $scope.dataSource = dataSource;
319
+ $scope.form = dataSource.form; // shortcut
320
+ // Error messages.
321
+ $scope.errors = {};
322
+ // Callback to be called after editing appointment.
323
+ var callback = null;
324
+
325
+ // Hide archived staff
326
+ $scope.filterStaff = function(staff) {
327
+ return (staff.archived == false || $scope.form.staff == staff);
328
+ };
329
+
330
+ /**
331
+ * Prepare the form for new event.
332
+ *
333
+ * @param int staff_id
334
+ * @param moment start_date
335
+ * @param function _callback
336
+ */
337
+ $scope.configureNewForm = function(staff_id, start_date, _callback) {
338
+ var weekday = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][start_date.format('d')],
339
+ staff = dataSource.findStaff(staff_id),
340
+ service = staff && staff.services.length == 2 ? staff.services[1] : null,
341
+ location = staff && staff.locations.length == 1 ? staff.locations[0] : null
342
+ ;
343
+ $scope.dataSource.data.app_start_time = null;
344
+ $scope.dataSource.data.app_end_time = null;
345
+ jQuery.extend($scope.form, {
346
+ screen : 'main',
347
+ id : null,
348
+ staff : staff,
349
+ staff_any : null,
350
+ service : service,
351
+ custom_service_name : null,
352
+ custom_service_price : 0,
353
+ location : location,
354
+ date : start_date.clone().local().toDate(),
355
+ skip_date : null,
356
+ start_time : dataSource.findTime('start', start_date.format('HH:mm')),
357
+ end_time : null,
358
+ end_time_data : [],
359
+ series_id : null,
360
+ repeat : {
361
+ enabled : 0,
362
+ repeat : 'daily',
363
+ daily : { every: 1 },
364
+ weekly : { on : [weekday] },
365
+ biweekly : { on : [weekday] },
366
+ monthly : { on : 'day', day : start_date.format('D'), weekday : weekday },
367
+ until : start_date.clone().add(1, 'month').format('YYYY-MM-DD')
368
+ },
369
+ schedule : {
370
+ items : [],
371
+ edit : 0,
372
+ page : 0,
373
+ another_time : []
374
+ },
375
+ customers : [],
376
+ internal_note : null,
377
+ expand_customers_list : false
378
+ });
379
+ $scope.errors = {};
380
+ dataSource.setEndTimeBasedOnService();
381
+ callback = _callback;
382
+
383
+ $scope.prepareExtras();
384
+ $scope.prepareCustomFields();
385
+ $scope.dataSource.resetCustomers();
386
+ $scope.onRepeatChange();
387
+ if (staff) {
388
+ $scope.onStaffChange();
389
+ }
390
+ };
391
+
392
+ /**
393
+ * Prepare the form for editing an event.
394
+ */
395
+ $scope.configureEditForm = function(appointment_id, _callback) {
396
+ $scope.loading = true;
397
+ jQuery.post(
398
+ ajaxurl,
399
+ {action: 'bookly_get_data_for_appointment', id: appointment_id, csrf_token : BooklyL10nAppDialog.csrf_token},
400
+ function(response) {
401
+ $scope.$apply(function($scope) {
402
+ if (response.success) {
403
+ var start_date = response.data.start_date === null ? null : moment(response.data.start_date),
404
+ end_date = response.data.start_date === null ? null : moment(response.data.end_date),
405
+ staff = $scope.dataSource.findStaff(response.data.staff_id);
406
+ $scope.dataSource.data.app_start_time = response.data.start_time;
407
+ $scope.dataSource.data.app_end_time = response.data.end_time;
408
+ jQuery.extend($scope.form, {
409
+ screen : 'main',
410
+ id : appointment_id,
411
+ staff : staff,
412
+ staff_any : response.data.staff_any ? staff : null,
413
+ service : $scope.dataSource.findService(response.data.staff_id, response.data.service_id),
414
+ custom_service_name : response.data.custom_service_name,
415
+ custom_service_price : response.data.custom_service_price,
416
+ location : $scope.dataSource.findLocation(response.data.staff_id, response.data.location_id),
417
+ skip_date : start_date === null ? 1 : 0,
418
+ end_time : null,
419
+ end_time_data : [],
420
+ repeat : {
421
+ enabled : 0,
422
+ repeat : 'daily',
423
+ daily : {every: 1},
424
+ weekly : {on: []},
425
+ biweekly: {on: []},
426
+ monthly : {on: 'day', day: '1', weekday: 'mon'},
427
+ until : start_date === null ? moment().add(1, 'month').format('YYYY-MM-DD') : start_date.clone().add(1, 'month').format('YYYY-MM-DD')
428
+ },
429
+ schedule : {
430
+ items : [],
431
+ edit : 0,
432
+ page : 0,
433
+ another_time: []
434
+ },
435
+ customers : [],
436
+ internal_note : response.data.internal_note,
437
+ series_id : response.data.series_id,
438
+ expand_customers_list: false
439
+ });
440
+ $scope.form.end_time_data = $scope.dataSource.getDataForEndTime();
441
+ if (start_date !== null) {
442
+ $scope.form.date = start_date.clone().local().toDate();
443
+ $scope.form.start_time = $scope.dataSource.findTime('start', start_date.format('HH:mm'));
444
+ $scope.dataSource.setEndTimeBasedOnService();
445
+ $scope.form.end_time = start_date.format('YYYY-MM-DD') == end_date.format('YYYY-MM-DD')
446
+ ? $scope.dataSource.findTime('end', end_date.format('HH:mm'))
447
+ : $scope.dataSource.findTime('end', (Math.floor((end_date - start_date) / 3600000) + start_date.hour()) + end_date.format(':mm'));
448
+ } else {
449
+ $scope.form.date = moment().local().toDate();
450
+ $scope.form.start_time = $scope.dataSource.findTime('start', moment().format('HH:mm'));
451
+ $scope.dataSource.setEndTimeBasedOnService();
452
+ }
453
+
454
+ $scope.prepareExtras();
455
+ $scope.prepareCustomFields();
456
+ $scope.dataSource.resetCustomers();
457
+ $scope.onLocationChange();
458
+ $scope.onRepeatChange();
459
+
460
+ var customers_ids = [];
461
+ response.data.customers.forEach(function (item, i, arr) {
462
+ var customer = $scope.dataSource.findCustomer(item.id),
463
+ clone = {};
464
+ if (customers_ids.indexOf(item.id) === -1) {
465
+ customers_ids.push(item.id);
466
+ clone = customer;
467
+ } else {
468
+ // For Error: ngRepeat:dupes & chosen directive
469
+ angular.copy(customer, clone);
470
+ }
471
+ clone.ca_id = item.ca_id;
472
+ clone.series_id = item.series_id;
473
+ clone.package_id = item.package_id;
474
+ clone.extras = item.extras;
475
+ clone.extras_multiply_nop = item.extras_multiply_nop;
476
+ clone.extras_consider_duration = item.extras_consider_duration;
477
+ clone.status = item.status;
478
+ clone.custom_fields = item.custom_fields;
479
+ clone.files = item.files;
480
+ clone.number_of_persons = item.number_of_persons;
481
+ clone.timezone = item.timezone;
482
+ clone.notes = item.notes;
483
+ clone.payment_id = item.payment_id;
484
+ clone.payment_type = item.payment_type;
485
+ clone.payment_title = item.payment_title;
486
+ clone.payment_create = item.payment_create;
487
+ clone.payment_price = item.payment_price;
488
+ clone.payment_tax = item.payment_tax;
489
+ clone.collaborative_token = item.collaborative_token;
490
+ clone.collaborative_service = item.collaborative_service;
491
+ clone.collaborative_service = item.collaborative_service;
492
+ clone.compound_token = item.compound_token;
493
+ clone.compound_service = item.compound_service;
494
+ $scope.form.customers.push(clone);
495
+ });
496
+ }
497
+ $scope.loading = false;
498
+ });
499
+ },
500
+ 'json'
501
+ );
502
+ $scope.errors = {};
503
+ callback = _callback;
504
+ };
505
+
506
+ var checkAppointmentErrors = function() {
507
+ if ($scope.form.staff) {
508
+ var dates = $scope.dataSource.getStartAndEndDates(),
509
+ customers = [];
510
+
511
+ $scope.form.customers.forEach(function (item, i, arr) {
512
+ var customer_extras = {};
513
+ if ($scope.form.service) {
514
+ jQuery('#bookly-extras .service_' + $scope.form.service.id + ' input.extras-count').each(function () {
515
+ var extra_id = jQuery(this).data('id');
516
+ if (item.extras[extra_id] !== undefined) {
517
+ customer_extras[extra_id] = item.extras[extra_id];
518
+ }
519
+ });
520
+ }
521
+ customers.push({
522
+ id : item.id,
523
+ ca_id : item.ca_id,
524
+ custom_fields : item.custom_fields,
525
+ extras : customer_extras,
526
+ extras_multiply_nop : item.extras_multiply_nop,
527
+ extras_consider_duration: item.extras_consider_duration,
528
+ number_of_persons : item.number_of_persons,
529
+ timezone : item.timezone,
530
+ status : item.status
531
+ });
532
+ });
533
+
534
+ jQuery.post(
535
+ ajaxurl,
536
+ {
537
+ action : 'bookly_check_appointment_errors',
538
+ csrf_token : BooklyL10nAppDialog.csrf_token,
539
+ start_date : dates.start_date,
540
+ end_date : dates.end_date,
541
+ appointment_id : $scope.form.id,
542
+ customers : JSON.stringify(customers),
543
+ staff_id : $scope.form.staff.id,
544
+ service_id : $scope.form.service ? $scope.form.service.id : null,
545
+ location_id : $scope.form.location ? $scope.form.location.id : null
546
+ },
547
+ function (response) {
548
+ $scope.$apply(function ($scope) {
549
+ angular.forEach(response, function (value, error) {
550
+ $scope.errors[error] = value;
551
+ });
552
+ });
553
+ },
554
+ 'json'
555
+ );
556
+ }
557
+ };
558
+
559
+ $scope.onServiceChange = function() {
560
+ $scope.dataSource.setEndTimeBasedOnService();
561
+ $scope.prepareExtras();
562
+ $scope.prepareCustomFields();
563
+ $scope.onLocationChange();
564
+ checkAppointmentErrors();
565
+ };
566
+
567
+ $scope.onLocationChange = function () {
568
+ if ($scope.form.staff && $scope.form.service) {
569
+ var current_service = $scope.dataSource.findService($scope.form.staff.id, $scope.form.service.id),
570
+ location_id = $scope.form.location ? $scope.form.location.id : 0;
571
+ if (current_service.locations.hasOwnProperty(location_id)) {
572
+ $scope.form.service.capacity_min = current_service.locations[location_id].capacity_min;
573
+ $scope.form.service.capacity_max = current_service.locations[location_id].capacity_max;
574
+ } else if (current_service.locations.hasOwnProperty(0)) {
575
+ $scope.form.service.capacity_min = current_service.locations[0].capacity_min;
576
+ $scope.form.service.capacity_max = current_service.locations[0].capacity_max;
577
+ } else {
578
+ $scope.form.service.capacity_min = 1;
579
+ $scope.form.service.capacity_max = 1;
580
+ }
581
+ checkAppointmentErrors();
582
+ }
583
+ };
584
+
585
+ $scope.onStaffChange = function() {
586
+ if ($scope.form.staff.services.length == 2) {
587
+ $scope.form.service = $scope.form.staff.services[1];
588
+ $scope.onServiceChange();
589
+ } else {
590
+ $scope.form.service = null;
591
+ }
592
+ $scope.form.location = $scope.form.staff.locations.length == 1 ? $scope.form.staff.locations[0] : null;
593
+ $scope.onLocationChange();
594
+ };
595
+
596
+ $scope.onStartTimeChange = function() {
597
+ $scope.dataSource.setEndTimeBasedOnService();
598
+ checkAppointmentErrors();
599
+ };
600
+
601
+ $scope.onEndTimeChange = function() {
602
+ checkAppointmentErrors();
603
+ };
604
+
605
+ $scope.onDateChange = function() {
606
+ checkAppointmentErrors();
607
+ $scope.onRepeatChange();
608
+ };
609
+
610
+ $scope.onCustomersChange = function(old_customers, old_nop) {
611
+ if (dataSource.form.service && dataSource.form.customers.length > old_customers.length) {
612
+ var ids = jQuery.map(old_customers, function(customer) {
613
+ return customer.id;
614
+ });
615
+ var nop = dataSource.form.service.capacity_min - old_nop;
616
+ dataSource.form.customers.some(function (item) {
617
+ if (jQuery.inArray(item.id, ids) == -1) {
618
+ item.number_of_persons = nop > 0 ? nop : 1;
619
+ return true;
620
+ }
621
+ });
622
+ }
623
+ $scope.errors.customers_appointments_limit = [];
624
+ checkAppointmentErrors();
625
+ };
626
+
627
+ $scope.onSkipDateChange = function() {
628
+ checkAppointmentErrors();
629
+ };
630
+
631
+ $scope.processForm = function() {
632
+ $scope.loading = true;
633
+
634
+ $scope.errors = {};
635
+
636
+ var dates = $scope.dataSource.getStartAndEndDates(),
637
+ schedule = [],
638
+ customers = []
639
+ ;
640
+
641
+ angular.forEach($scope.form.schedule.items, function (item) {
642
+ if (!item.deleted) {
643
+ schedule.push(item.slots);
644
+ }
645
+ });
646
+
647
+ $scope.form.customers.forEach(function (item, i, arr) {
648
+ var customer_extras = {};
649
+ if ($scope.form.service) {
650
+ jQuery('#bookly-extras .service_' + $scope.form.service.id + ' input.extras-count').each(function () {
651
+ var extra_id = jQuery(this).data('id');
652
+ if (item.extras[extra_id] !== undefined) {
653
+ customer_extras[extra_id] = item.extras[extra_id];
654
+ }
655
+ });
656
+ }
657
+ customers.push({
658
+ id : item.id,
659
+ ca_id : item.ca_id,
660
+ series_id : item.series_id,
661
+ custom_fields : item.custom_fields,
662
+ extras : customer_extras,
663
+ extras_multiply_nop : item.extras_multiply_nop,
664
+ extras_consider_duration : item.extras_consider_duration,
665
+ number_of_persons : item.number_of_persons,
666
+ timezone : item.timezone,
667
+ notes : item.notes,
668
+ status : item.status,
669
+ payment_id : item.payment_id,
670
+ payment_create : item.payment_create,
671
+ payment_price : item.payment_price,
672
+ payment_tax : item.payment_tax
673
+ });
674
+ });
675
+ jQuery.post(
676
+ ajaxurl,
677
+ {
678
+ action : 'bookly_save_appointment_form',
679
+ csrf_token : BooklyL10nAppDialog.csrf_token,
680
+ id : $scope.form.id || undefined,
681
+ staff_id : $scope.form.staff ? $scope.form.staff.id : undefined,
682
+ service_id : $scope.form.service ? $scope.form.service.id : undefined,
683
+ custom_service_name : $scope.form.custom_service_name,
684
+ custom_service_price : $scope.form.custom_service_price,
685
+ location_id : $scope.form.location ? $scope.form.location.id : undefined,
686
+ skip_date : $scope.form.skip_date,
687
+ start_date : dates.start_date,
688
+ end_date : dates.end_date,
689
+ repeat : JSON.stringify($scope.form.repeat),
690
+ schedule : schedule,
691
+ customers : JSON.stringify(customers),
692
+ notification : $scope.form.notification,
693
+ internal_note : $scope.form.internal_note,
694
+ created_from : typeof BooklySCCalendarL10n !== 'undefined' ? 'staff-cabinet' : 'backend'
695
+ },
696
+ function (response) {
697
+ $scope.$apply(function($scope) {
698
+ if (response.success) {
699
+ if (callback) {
700
+ // Call callback.
701
+ callback(response.data);
702
+ }
703
+ // Close the dialog.
704
+ $element.children().modal('hide');
705
+ } else {
706
+ $scope.errors = response.errors;
707
+ }
708
+ $scope.loading = false;
709
+ });
710
+ },
711
+ 'json'
712
+ );
713
+ };
714
+
715
+ // On 'Cancel' button click.
716
+ $scope.closeDialog = function () {
717
+ // Close the dialog.
718
+ $element.children().modal('hide');
719
+ };
720
+
721
+ $scope.statusToString = function (status) {
722
+ return dataSource.data.status.items[status];
723
+ };
724
+
725
+ /**************************************************************************************************************
726
+ * New customer *
727
+ **************************************************************************************************************/
728
+
729
+ /**
730
+ * Create new customer.
731
+ * @param customer
732
+ */
733
+ $scope.createCustomer = function(customer) {
734
+ // Add new customer to the list.
735
+ var nop = 1;
736
+ if (dataSource.form.service) {
737
+ nop = dataSource.form.service.capacity_min - dataSource.getTotalNumberOfNotCancelledPersons();
738
+ if (nop < 1) {
739
+ nop = 1;
740
+ }
741
+ }
742
+ var new_customer = {
743
+ id : customer.id.toString(),
744
+ name : customer.full_name,
745
+ custom_fields : customer.custom_fields,
746
+ extras : customer.extras,
747
+ extras_consider_duration : dataSource.data.extras_consider_duration,
748
+ extras_multiply_nop : dataSource.data.extras_multiply_nop,
749
+ status : customer.status,
750
+ timezone : customer.timezone,
751
+ number_of_persons : nop,
752
+ notes : null,
753
+ collaborative_token : null,
754
+ collaborative_service : null,
755
+ compound_token : null,
756
+ compound_service : null,
757
+ payment_id : null,
758
+ payment_type : null,
759
+ payment_title : null,
760
+ payment_create : false,
761
+ payment_price : null,
762
+ payment_tax : null
763
+ };
764
+
765
+ if (customer.email || customer.phone){
766
+ new_customer.name += ' (' + [customer.email, customer.phone].filter(Boolean).join(', ') + ')';
767
+ }
768
+
769
+ dataSource.data.customers.push(new_customer);
770
+
771
+ // Make it selected.
772
+ if (!dataSource.form.service || dataSource.form.customers.length < dataSource.form.service.capacity_max) {
773
+ dataSource.form.customers.push(new_customer);
774
+ }
775
+ };
776
+
777
+ $scope.removeCustomer = function(customer) {
778
+ $scope.form.customers.splice($scope.form.customers.indexOf(customer), 1);
779
+ checkAppointmentErrors();
780
+ };
781
+
782
+ $scope.openNewCustomerDialog = function() {
783
+ var $dialog = jQuery('#bookly-customer-dialog');
784
+ $dialog.modal({show: true});
785
+ };
786
+
787
+ /**************************************************************************************************************
788
+ * Customer Details *
789
+ **************************************************************************************************************/
790
+
791
+ $scope.editCustomerDetails = function(customer) {
792
+ var $dialog = jQuery('#bookly-customer-details-dialog');
793
+ $dialog.find('input.bookly-custom-field:text, textarea.bookly-custom-field, select.bookly-custom-field, input.bookly-js-file').val('');
794
+ $dialog.find('input.bookly-custom-field:checkbox, input.bookly-custom-field:radio').prop('checked', false);
795
+ $dialog.find('#bookly-extras :checkbox').prop('checked', false);
796
+
797
+ customer.custom_fields.forEach(function (field) {
798
+ var $custom_field = $dialog.find('#bookly-js-custom-fields > *[data-id="' + field.id + '"]');
799
+ switch ($custom_field.data('type')) {
800
+ case 'checkboxes':
801
+ field.value.forEach(function (value) {
802
+ $custom_field.find('.bookly-custom-field').filter(function () {
803
+ return this.value == value;
804
+ }).prop('checked', true);
805
+ });
806
+ break;
807
+ case 'radio-buttons':
808
+ $custom_field.find('.bookly-custom-field').filter(function () {
809
+ return this.value == field.value;
810
+ }).prop('checked', true);
811
+ break;
812
+ default:
813
+ $custom_field.find('.bookly-custom-field').val(field.value);
814
+ break;
815
+ }
816
+ });
817
+
818
+ $dialog.find('#bookly-extras .extras-count').val(0);
819
+ angular.forEach(customer.extras, function (extra_count, extra_id) {
820
+ $dialog.find('#bookly-extras .extras-count[data-id="' + extra_id + '"]').val(extra_count);
821
+ });
822
+
823
+ // Prepare select for number of persons.
824
+ var $number_of_persons = $dialog.find('#bookly-number-of-persons');
825
+
826
+ var max = $scope.form.service
827
+ ? ($scope.form.service.id
828
+ ? parseInt($scope.form.service.capacity_max) - $scope.dataSource.getTotalNumberOfNotCancelledPersons(customer)
829
+ : 999)
830
+ : 1;
831
+ $number_of_persons.empty();
832
+ for (var i = 1; i <= max; ++i) {
833
+ $number_of_persons.append('<option value="' + i + '">' + i + '</option>');
834
+ }
835
+ if (customer.number_of_persons > max) {
836
+ $number_of_persons.append('<option value="' + customer.number_of_persons + '">' + customer.number_of_persons + '</option>');
837
+ }
838
+ $number_of_persons.val(customer.number_of_persons);
839
+ $dialog.find('#bookly-appointment-status').val(customer.status);
840
+ $dialog.find('#bookly-appointment-notes').val(customer.notes);
841
+ $dialog.find('#bookly-deposit-due').val(customer.due);
842
+ $dialog.find('#bookly-customer-time-zone').val(customer.timezone ? customer.timezone : '');
843
+ $scope.edit_customer = customer;
844
+
845
+ $dialog.modal({show: true})
846
+ .on('hidden.bs.modal', function () {
847
+ jQuery('body').addClass('modal-open');
848
+ });
849
+
850
+ jQuery(document.body).trigger('bookly.edit.customer_details', [$dialog, $scope.edit_customer]);
851
+ };
852
+
853
+ $scope.prepareExtras = function () {
854
+ if ($scope.form.service) {
855
+ jQuery('#bookly-extras > *').hide();
856
+ var $service_extras = jQuery('#bookly-extras .service_' + $scope.form.service.id);
857
+ if ($service_extras.length) {
858
+ $service_extras.show();
859
+ jQuery('#bookly-extras').show();
860
+ } else {
861
+ jQuery('#bookly-extras').hide();
862
+ }
863
+ } else {
864
+ jQuery('#bookly-extras').hide();
865
+ }
866
+ };
867
+
868
+ // Hide or unhide custom fields for current service
869
+ $scope.prepareCustomFields = function () {
870
+ if (BooklyL10nAppDialog.cf_per_service == 1) {
871
+ var show = false;
872
+ jQuery('#bookly-js-custom-fields div[data-services]').each(function() {
873
+ var $this = jQuery(this);
874
+ if (dataSource.form.service !== null) {
875
+ var services = $this.data('services');
876
+ if (services && jQuery.inArray(dataSource.form.service.id, services) > -1) {
877
+ $this.show();
878
+ show = true;
879
+ } else {
880
+ $this.hide();
881
+ }
882
+ } else {
883
+ $this.hide();
884
+ }
885
+ });
886
+ if (show) {
887
+ jQuery('#bookly-js-custom-fields').show();
888
+ } else {
889
+ jQuery('#bookly-js-custom-fields').hide();
890
+ }
891
+ }
892
+ };
893
+
894
+ $scope.saveCustomFields = function() {
895
+ var result = [],
896
+ extras = {},
897
+ $fields = jQuery('#bookly-js-custom-fields > *'),
898
+ $status = jQuery('#bookly-appointment-status'),
899
+ $timezone = jQuery('#bookly-customer-time-zone'),
900
+ $number_of_persons = jQuery('#bookly-number-of-persons'),
901
+ $notes = jQuery('#bookly-appointment-notes'),
902
+ $extras = jQuery('#bookly-extras')
903
+ ;
904
+
905
+ $fields.each(function () {
906
+ var $this = jQuery(this),
907
+ value;
908
+ if ($this.is(':visible')) {
909
+ switch ($this.data('type')) {
910
+ case 'checkboxes':
911
+ value = [];
912
+ $this.find('.bookly-custom-field:checked').each(function () {
913
+ value.push(this.value);
914
+ });
915
+ break;
916
+ case 'radio-buttons':
917
+ value = $this.find('.bookly-custom-field:checked').val();
918
+ break;
919
+ default:
920
+ value = $this.find('.bookly-custom-field').val();
921
+ break;
922
+ }
923
+ result.push({id: $this.data('id'), value: value});
924
+ }
925
+ });
926
+
927
+ if ($scope.form.service) {
928
+ $extras.find(' .service_' + $scope.form.service.id + ' input.extras-count').each(function () {
929
+ if (this.value > 0) {
930
+ extras[jQuery(this).data('id')] = this.value;
931
+ }
932
+ });
933
+ }
934
+
935
+ $scope.edit_customer.status = $status.val();
936
+ $scope.edit_customer.timezone = $timezone.val();
937
+ $scope.edit_customer.number_of_persons = $number_of_persons.val();
938
+ $scope.edit_customer.notes = $notes.val();
939
+ $scope.edit_customer.custom_fields = result;
940
+ $scope.edit_customer.extras = extras;
941
+
942
+ jQuery('#bookly-customer-details-dialog').modal('hide');
943
+ if ($extras.length > 0) {
944
+ // Check if intersection with another appointment exists.
945
+ checkAppointmentErrors();
946
+ }
947
+ };
948
+
949
+ /**************************************************************************************************************
950
+ * Payment Details *
951
+ **************************************************************************************************************/
952
+
953
+ $scope.attachPaymentModal = function (customer) {
954
+ var $dialog = jQuery('#bookly-payment-attach-modal');
955
+ $scope.form.attach = {
956
+ customer_id : customer.id,
957
+ payment_method: 'create',
958
+ payment_price : null,
959
+ payment_tax : null,
960
+ payment_id : null
961
+ };
962
+ $dialog.modal({show: true}).on('hidden.bs.modal', function () {
963
+ jQuery('body').addClass('modal-open');
964
+ });
965
+ };
966
+
967
+ $scope.attachPayment = function (attach_method, price, tax, payment_id, customer_id) {
968
+ var $dialog = jQuery('#bookly-payment-details-modal');
969
+ if (attach_method == 'search') {
970
+ $dialog.data('payment_id', payment_id).data('payment_bind', true).data('customer_id', customer_id).modal({show: true}).on('hidden.bs.modal', function () {
971
+ jQuery('body').addClass('modal-open');
972
+ });
973
+ } else {
974
+ jQuery.each($scope.dataSource.data.customers, function (key, item) {
975
+ if (item.id == customer_id) {
976
+ item.payment_create = true;
977
+ item.payment_price = price;
978
+ item.payment_tax = tax;
979
+ item.payment_type = 'partial';
980
+ }
981
+ });
982
+ }
983
+ };
984
+
985
+ $scope.callbackPayment = function (payment_action, payment_id, payment_title, customer_id, payment_type) {
986
+ if (payment_action == 'bind') {
987
+ // Bind payment
988
+ jQuery.each($scope.dataSource.data.customers, function (key, item) {
989
+ if (item.id == customer_id) {
990
+ item.payment_id = payment_id;
991
+ item.payment_type = payment_type;
992
+ item.payment_title = payment_title;
993
+ }
994
+ });
995
+ } else {
996
+ // Complete payment
997
+ jQuery.each($scope.dataSource.data.customers, function (key, item) {
998
+ if (item.payment_id == payment_id) {
999
+ item.payment_type = 'full';
1000
+ item.payment_title = payment_title;
1001
+ }
1002
+ });
1003
+ }
1004
+ };
1005
+
1006
+ /**************************************************************************************************************
1007
+ * Package Schedule *
1008
+ **************************************************************************************************************/
1009
+
1010
+ $scope.editPackageSchedule = function(customer) {
1011
+ jQuery(document.body).trigger('bookly_packages.schedule_dialog', [customer.package_id, function (deleted) {
1012
+ if (jQuery.inArray(Number(customer.ca_id), deleted) != -1) {
1013
+ $scope.removeCustomer(customer);
1014
+ }
1015
+ if (callback) {
1016
+ // Call callback.
1017
+ callback('refresh');
1018
+ }
1019
+ }, true]);
1020
+ };
1021
+
1022
+ /**************************************************************************************************************
1023
+ * Repeat Times in Recurring Appointments *
1024
+ **************************************************************************************************************/
1025
+ $scope.isDateMatchesSelections = function (current_date) {
1026
+ var current_day = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][current_date.format('d')];
1027
+ switch ($scope.form.repeat.repeat) {
1028
+ case 'daily':
1029
+ 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)) {
1030
+ return true;
1031
+ }
1032
+ break;
1033
+ case 'weekly':
1034
+ case 'biweekly':
1035
+ 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)) {
1036
+ return true;
1037
+ }
1038
+ break;
1039
+ case 'monthly':
1040
+ switch ($scope.form.repeat.monthly.on) {
1041
+ case 'day':
1042
+ if (current_date.format('D') == $scope.form.repeat.monthly.day) {
1043
+ return true;
1044
+ }
1045
+ break;
1046
+ case 'last':
1047
+ if (current_day == $scope.form.repeat.monthly.weekday && current_date.clone().endOf('month').diff(current_date, 'days') < 7) {
1048
+ return true;
1049
+ }
1050
+ break;
1051
+ default:
1052
+ var month_diff = current_date.diff(current_date.clone().startOf('month'), 'days'),
1053
+ weeks = ['first', 'second', 'third', 'fourth'],
1054
+ week_number = weeks.indexOf($scope.form.repeat.monthly.on);
1055
+
1056
+ if (current_day == $scope.form.repeat.monthly.weekday && month_diff >= week_number * 7 && month_diff < (week_number + 1) * 7) {
1057
+ return true;
1058
+ }
1059
+ }
1060
+ break;
1061
+ }
1062
+
1063
+ return false;
1064
+ };
1065
+ $scope.onRepeatChange = function () {
1066
+ if (jQuery('#bookly-repeat-enabled').length && !$scope.form.skip_date) {
1067
+ var number_of_times = 0,
1068
+ date_until = moment($scope.form.repeat.until).add(1, 'days'),
1069
+ current_date = moment($scope.dataSource.form.date.getTime());
1070
+ do {
1071
+ if ($scope.isDateMatchesSelections(current_date)) {
1072
+ number_of_times++;
1073
+ }
1074
+ current_date.add(1, 'days');
1075
+ } while (current_date.isBefore(date_until));
1076
+ $scope.form.repeat.times = number_of_times;
1077
+ }
1078
+ };
1079
+ $scope.onRepeatChangeTimes = function () {
1080
+ var number_of_times = 0,
1081
+ date_until = moment($scope.dataSource.form.date.getTime()).add(5, 'years'),
1082
+ current_date = moment($scope.dataSource.form.date.getTime());
1083
+ do {
1084
+ if ($scope.isDateMatchesSelections(current_date)) {
1085
+ number_of_times++
1086
+ }
1087
+ current_date.add(1, 'days');
1088
+ } while (number_of_times < $scope.form.repeat.times && current_date.isBefore(date_until));
1089
+ $scope.form.repeat.until = current_date.subtract(1, 'days').format('YYYY-MM-DD');
1090
+ };
1091
+
1092
+ /**************************************************************************************************************
1093
+ * Schedule of Recurring Appointments *
1094
+ **************************************************************************************************************/
1095
+
1096
+ $scope.schSchedule = function ($event) {
1097
+ var extras = [];
1098
+ $scope.form.customers.forEach(function (item, i, arr) {
1099
+ extras.push(item.extras);
1100
+ });
1101
+
1102
+ if (
1103
+ ($scope.form.repeat.repeat == 'weekly' || $scope.form.repeat.repeat == 'biweekly') &&
1104
+ $scope.form.repeat[$scope.form.repeat.repeat].on.length == 0
1105
+ ) {
1106
+ $scope.errors.repeat_weekdays_empty = true;
1107
+ } else {
1108
+ delete $scope.errors.repeat_weekdays_empty;
1109
+ var ladda = Ladda.create($event.currentTarget);
1110
+ ladda.start();
1111
+ var dates = $scope.dataSource.getStartAndEndDates();
1112
+ jQuery.post(
1113
+ ajaxurl,
1114
+ {
1115
+ action : 'bookly_recurring_appointments_get_schedule',
1116
+ csrf_token : BooklyL10nAppDialog.csrf_token,
1117
+ staff_id : $scope.form.staff.id,
1118
+ service_id : $scope.form.service.id,
1119
+ location_id : $scope.form.location ? $scope.form.location.id : null,
1120
+ datetime : dates.start_date,
1121
+ until : $scope.form.repeat.until,
1122
+ repeat : $scope.form.repeat.repeat,
1123
+ params : $scope.form.repeat[$scope.form.repeat.repeat],
1124
+ extras : extras,
1125
+ nop : $scope.dataSource.getTotalNumberOfPersons(),
1126
+ duration : $scope.form.service.id ? undefined : $scope.dataSource.getServiceDuration()
1127
+ },
1128
+ function (response) {
1129
+ $scope.$apply(function($scope) {
1130
+ $scope.form.schedule.items = response.data;
1131
+ $scope.form.schedule.page = 0;
1132
+ $scope.form.schedule.another_time = [];
1133
+ angular.forEach($scope.form.schedule.items, function (item) {
1134
+ if (item.another_time) {
1135
+ var page = parseInt( ( item.index - 1 ) / 10 ) + 1;
1136
+ if ($scope.form.schedule.another_time.indexOf(page) < 0) {
1137
+ $scope.form.schedule.another_time.push(page);
1138
+ }
1139
+ }
1140
+ });
1141
+ $scope.form.screen = 'schedule';
1142
+ ladda.stop();
1143
+ });
1144
+ },
1145
+ 'json'
1146
+ );
1147
+ }
1148
+ };
1149
+ $scope.schFormatDate = function(date) {
1150
+ var m = moment(date),
1151
+ weekday = m.format('d'),
1152
+ month = m.format('M'),
1153
+ day = m.format('DD');
1154
+
1155
+ return BooklyL10nAppDialog.dateOptions.dayNamesMin[weekday] + ', ' + BooklyL10nAppDialog.dateOptions.monthNamesShort[month-1] + ' ' + day;
1156
+ };
1157
+ $scope.schFormatTime = function(slots, options) {
1158
+ for (var i = 0; i < options.length; ++ i) {
1159
+ if (slots == options[i].value) {
1160
+ return options[i].title;
1161
+ }
1162
+ }
1163
+ };
1164
+ $scope.schFirstPage = function() {
1165
+ return $scope.form.schedule.page == 0;
1166
+ };
1167
+ $scope.schLastPage = function() {
1168
+ var lastPageNum = Math.ceil($scope.form.schedule.items.length / 10 - 1);
1169
+ return $scope.form.schedule.page == lastPageNum;
1170
+ };
1171
+ $scope.schNumberOfPages = function() {
1172
+ return Math.ceil($scope.form.schedule.items.length / 10);
1173
+ };
1174
+ $scope.schStartingItem = function() {
1175
+ return $scope.form.schedule.page * 10;
1176
+ };
1177
+ $scope.schPageBack = function() {
1178
+ $scope.form.schedule.page = $scope.form.schedule.page - 1;
1179
+ };
1180
+ $scope.schPageForward = function() {
1181
+ $scope.form.schedule.page = $scope.form.schedule.page + 1;
1182
+ };
1183
+ $scope.schOnWeekdayClick = function (weekday) {
1184
+ var idx = $scope.form.repeat.weekly.on.indexOf(weekday);
1185
+
1186
+ // is currently selected
1187
+ if (idx > -1) {
1188
+ $scope.form.repeat.weekly.on.splice(idx, 1);
1189
+ }
1190
+ // is newly selected
1191
+ else {
1192
+ $scope.form.repeat.weekly.on.push(weekday);
1193
+ }
1194
+ // copy weekly to biweekly
1195
+ $scope.form.repeat.biweekly.on = $scope.form.repeat.weekly.on.slice();
1196
+ $scope.onRepeatChange();
1197
+ };
1198
+ $scope.schOnDateChange = function(item) {
1199
+ var extras = [];
1200
+ $scope.form.customers.forEach(function (item, i, arr) {
1201
+ extras.push(item.extras);
1202
+ });
1203
+
1204
+ var exclude = [];
1205
+ angular.forEach($scope.form.schedule.items, function (_item) {
1206
+ if (item.slots != _item.slots && !_item.deleted) {
1207
+ exclude.push(_item.slots);
1208
+ }
1209
+ });
1210
+ jQuery.post(
1211
+ ajaxurl,
1212
+ {
1213
+ action : 'bookly_recurring_appointments_get_schedule',
1214
+ csrf_token : BooklyL10nAppDialog.csrf_token,
1215
+ staff_id : $scope.form.staff.id,
1216
+ service_id : $scope.form.service.id,
1217
+ location_id : $scope.form.location ? $scope.form.location.id : null,
1218
+ datetime : item.date + ' 00:00',
1219
+ until : item.date,
1220
+ repeat : 'daily',
1221
+ params : {every: 1},
1222
+ with_options : 1,
1223
+ exclude : exclude,
1224
+ extras : extras,
1225
+ duration : $scope.form.service.id ? undefined : $scope.dataSource.getServiceDuration()
1226
+ },
1227
+ function (response) {
1228
+ $scope.$apply(function($scope) {
1229
+ if (response.data.length) {
1230
+ item.options = response.data[0].options;
1231
+ var found = false;
1232
+ jQuery.each(item.options, function (key, option) {
1233
+ if (option.value == item.slots) {
1234
+ found = true;
1235
+ return false;
1236
+ }
1237
+ });
1238
+ if (!found) {
1239
+ jQuery.each(item.options, function (key, option) {
1240
+ if (!option.disabled) {
1241
+ item.slots = option.value;
1242
+ return false;
1243
+ }
1244
+ });
1245
+ }
1246
+ } else {
1247
+ item.options = [];
1248
+ }
1249
+ });
1250
+ },
1251
+ 'json'
1252
+ );
1253
+ };
1254
+ $scope.schIsScheduleEmpty = function () {
1255
+ return $scope.form.schedule.items.every(function(item) {
1256
+ return item.deleted;
1257
+ });
1258
+ };
1259
+ $scope.schDateOptions = jQuery.extend({}, BooklyL10nAppDialog.dateOptions, {dateFormat: 'D, M dd, yy'});
1260
+ $scope.schViewSeries = function ( customer ) {
1261
+ jQuery(document.body).trigger( 'recurring_appointments.series_dialog', [ customer.series_id, function (event) {
1262
+ // Switch to the event owner tab.
1263
+ jQuery('li[data-staff_id=' + event.staffId + ']').click();
1264
+ } ] );
1265
+ };
1266
+
1267
+ /**
1268
+ * Datepicker options.
1269
+ */
1270
+ $scope.dateOptions = BooklyL10nAppDialog.dateOptions;
1271
+ });
1272
+
1273
+ /**
1274
+ * Directive for slide up/down.
1275
+ */
1276
+ module.directive('mySlideUp', function() {
1277
+ return function(scope, element, attrs) {
1278
+ element.hide();
1279
+ // watch the expression, and update the UI on change.
1280
+ scope.$watch(attrs.mySlideUp, function(value) {
1281
+ if (value) {
1282
+ element.delay(0).slideDown();
1283
+ } else {
1284
+ element.slideUp();
1285
+ }
1286
+ });
1287
+ };
1288
+ });
1289
+
1290
+ /**
1291
+ * Directive for Popover jQuery plugin.
1292
+ */
1293
+ module.directive('popover', function() {
1294
+ return function(scope, element, attrs) {
1295
+ element.popover({
1296
+ trigger : 'hover',
1297
+ content : function() { return this.getAttribute('popover'); },
1298
+ html : true,
1299
+ placement: 'top',
1300
+ 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>'
1301
+ });
1302
+ };
1303
+ });
1304
+
1305
+ /**
1306
+ * Filters for pagination in Schedule.
1307
+ */
1308
+ module.filter('startFrom', function() {
1309
+ return function(input, start){
1310
+ start = +start;
1311
+ return input.slice(start);
1312
+ }
1313
+ });
1314
+ module.filter('range', function() {
1315
+ return function(input, total) {
1316
+ total = parseInt(total);
1317
+
1318
+ for (var i = 1; i <= total; ++ i) {
1319
+ input.push(i);
1320
+ }
1321
+
1322
+ return input;
1323
+ };
1324
+ });
1325
+
1326
+ jQuery('#bookly-select2').select2({
1327
+ width: '100%',
1328
+ theme: 'bootstrap',
1329
+ allowClear: false,
1330
+ language : {
1331
+ noResults: function() { return BooklyL10nAppDialog.no_result_found; }
1332
+ }
1333
+ });
1334
+ })();
1335
+
1336
+ /**
1337
+ * @param int appointment_id
1338
+ * @param int staff_id
1339
+ * @param moment start_date
1340
+ * @param function callback
1341
+ */
1342
+ var showAppointmentDialog = function (appointment_id, staff_id, start_date, callback) {
1343
+ var $dialog = jQuery('#bookly-appointment-dialog');
1344
+ var $scope = angular.element($dialog[0]).scope();
1345
+ $scope.$apply(function ($scope) {
1346
+ $scope.loading = true;
1347
+ $dialog
1348
+ .find('.modal-title')
1349
+ .text(appointment_id ? BooklyL10nAppDialog.title.edit_appointment : BooklyL10nAppDialog.title.new_appointment);
1350
+ // Populate data source.
1351
+ $scope.dataSource.loadData().then(function() {
1352
+ $scope.loading = false;
1353
+ if (appointment_id) {
1354
+ $scope.configureEditForm(appointment_id, callback);
1355
+ } else {
1356
+ $scope.configureNewForm(staff_id, start_date, callback);
1357
+ }
1358
+ });
1359
+ });
1360
+
1361
+ // hide customer details dialog, if it remained opened.
1362
+ if (jQuery('#bookly-customer-details-dialog').hasClass('in')) {
1363
+ jQuery('#bookly-customer-details-dialog').modal('hide');
1364
+ }
1365
+
1366
+ // hide new customer dialog, if it remained opened.
1367
+ if (jQuery('#bookly-customer-dialog').hasClass('in')) {
1368
+ jQuery('#bookly-customer-dialog').modal('hide');
1369
+ }
1370
+
1371
+ $dialog.modal('show');
1372
  };
backend/components/dialogs/appointment/edit/templates/edit.php CHANGED
@@ -1,241 +1,253 @@
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">
11
- <div class="modal-dialog">
12
- <div class="modal-content">
13
- <form ng-submit=processForm()>
14
- <div class="modal-header">
15
- <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
16
- <div class="modal-title h2"><?php _e( 'New appointment', 'bookly' ) ?></div>
17
- </div>
18
- <div ng-show=loading class="modal-body">
19
- <div class="bookly-loading"></div>
20
- </div>
21
- <div ng-hide="loading || form.screen != 'main'" class="modal-body">
22
- <div class=form-group>
23
- <label for="bookly-provider"><?php _e( 'Provider', 'bookly' ) ?></label>
24
- <select id="bookly-provider" class="form-control" ng-model="form.staff" ng-options="s.full_name + (form.staff_any == s ? ' (' + dataSource.l10n.staff_any + ')' : '') for s in dataSource.data.staff" ng-change="onStaffChange()"></select>
25
- </div>
26
-
27
- <div class=form-group>
28
- <label for="bookly-service"><?php _e( 'Service', 'bookly' ) ?></label>
29
- <select id="bookly-service" class="form-control" ng-model="form.service"
30
- ng-options="s.title for s in form.staff.services" ng-change="onServiceChange()">
31
- <option value=""><?php _e( '-- Select a service --', 'bookly' ) ?></option>
32
- </select>
33
- <p class="text-danger" my-slide-up="errors.service_required">
34
- <?php _e( 'Please select a service', 'bookly' ) ?>
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>
102
- <span ng-show="form.service && form.service.id" title="<?php esc_attr_e( 'Selected / maximum', 'bookly' ) ?>">
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}}
110
- </p>
111
- <ul class="bookly-flexbox">
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>
180
- </ul>
181
- <span class="btn btn-default" ng-show="form.customers.length > 5 && !form.expand_customers_list" ng-click="form.expand_customers_list = !form.expand_customers_list" style="width: 100%; line-height: 0; padding-top: 0; padding-bottom: 8px; margin-bottom: 10px;" role="button">...</span>
182
- <div <?php if ( ! Config::waitingListActive() ): ?>ng-show="!form.service || dataSource.getTotalNumberOfNotCancelledPersons() < form.service.capacity_max"<?php endif ?>>
183
- <div class="form-group">
184
- <div class="input-group">
185
- <select id="bookly-select2" multiple data-placeholder="<?php esc_attr_e( '-- Search customers --', 'bookly' ) ?>"
186
- class="form-control"
187
- ng-model="form.customers" ng-options="c.name for c in dataSource.data.customers"
188
- ng-change="onCustomersChange({{form.customers}}, {{dataSource.getTotalNumberOfNotCancelledPersons()}})">
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>
196
- </div>
197
- </div>
198
- </div>
199
- <p class="text-danger" my-slide-up="errors.overflow_capacity" ng-bind="errors.overflow_capacity"></p>
200
- <p class="text-success" my-slide-up="errors.customers_appointments_limit" ng-repeat="customer_error in errors.customers_appointments_limit">
201
- {{customer_error}}
202
- </p>
203
- </div>
204
-
205
- <div class=form-group>
206
- <label for="bookly-notification"><?php _e( 'Send notifications', 'bookly' ) ?></label>
207
- <p class="help-block"><?php is_admin() ?
208
- _e( 'If email or SMS notifications are enabled and you want customers or staff member to be notified about this appointment after saving, select appropriate option before clicking Save. With "If status changed" the notifications are sent to those customers whose status has just been changed. With "To all customers" the notifications are sent to everyone in the list.', 'bookly' ) :
209
- _e( 'If email or SMS notifications are enabled and you want customers or yourself to be notified about this appointment after saving, select appropriate option before clicking Save. With "If status changed" the notifications are sent to those customers whose status has just been changed. With "To all customers" the notifications are sent to everyone in the list.', 'bookly' ) ?></p>
210
- <select class="form-control" style="margin-top: 0" ng-model=form.notification id="bookly-notification" ng-init="form.notification = '<?php echo get_user_meta( get_current_user_id(), 'bookly_appointment_form_send_notifications', true ) ?>' || 'no'" >
211
- <option value="no"><?php _e( 'Don\'t send', 'bookly' ) ?></option>
212
- <option value="changed_status"><?php _e( 'If status changed', 'bookly' ) ?></option>
213
- <option value="all"><?php _e( 'To all customers', 'bookly' ) ?></option>
214
- </select>
215
- </div>
216
-
217
- <div class=form-group>
218
- <label for="bookly-internal-note"><?php _e( 'Internal note', 'bookly' ) ?></label>
219
- <textarea class="form-control" ng-model=form.internal_note id="bookly-internal-note"></textarea>
220
- </div>
221
- </div>
222
- <?php Proxy\RecurringAppointments::renderSchedule() ?>
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>
 
 
 
 
 
 
 
 
 
 
 
 
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">
11
+ <div class="modal-dialog">
12
+ <div class="modal-content">
13
+ <form ng-submit=processForm()>
14
+ <div class="modal-header">
15
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
16
+ <div class="modal-title h2"><?php esc_html_e( 'New appointment', 'bookly' ) ?></div>
17
+ </div>
18
+ <div ng-show=loading class="modal-body">
19
+ <div class="bookly-loading"></div>
20
+ </div>
21
+ <div ng-hide="loading || form.screen != 'main'" class="modal-body">
22
+ <div class=form-group>
23
+ <label for="bookly-provider"><?php esc_html_e( 'Provider', 'bookly' ) ?></label>
24
+ <select id="bookly-provider" class="form-control" ng-model="form.staff" ng-options="s.full_name + (form.staff_any == s ? ' (' + dataSource.l10n.staff_any + ')' : '') for s in dataSource.data.staff | filter:filterStaff" ng-change="onStaffChange()"></select>
25
+ </div>
26
+
27
+ <div class=form-group>
28
+ <label for="bookly-service"><?php esc_html_e( 'Service', 'bookly' ) ?></label>
29
+ <select id="bookly-service" class="form-control" ng-model="form.service"
30
+ ng-options="s.title for s in form.staff.services" ng-change="onServiceChange()">
31
+ <option value=""><?php esc_html_e( '-- Select a service --', 'bookly' ) ?></option>
32
+ </select>
33
+ <p class="text-danger" my-slide-up="errors.service_required">
34
+ <?php esc_html_e( 'Please select a service', 'bookly' ) ?>
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 esc_html_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 esc_html_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 esc_html_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 esc_html_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 esc_html_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 esc_html_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 esc_html_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 esc_html_e( 'Selected period doesn\'t match service schedule', 'bookly' ) ?>
94
+ </p>
95
+ <p class="text-success" my-slide-up=errors.staff_reaches_working_time_limit id=staff_reaches_working_time_limit_msg>
96
+ <?php is_admin() ?
97
+ esc_html_e( 'Booking exceeds the working hours limit for staff member', 'bookly' ) :
98
+ esc_html_e( 'Booking exceeds your working hours limit', 'bookly' ) ?>
99
+ </p>
100
+ </div>
101
+
102
+ <?php Proxy\RecurringAppointments::renderSubForm() ?>
103
+ </div>
104
+
105
+ <div class=form-group>
106
+ <label for="bookly-select2"><?php esc_html_e( 'Customers', 'bookly' ) ?></label>
107
+ <span ng-show="form.service && form.service.id" title="<?php esc_attr_e( 'Selected / maximum', 'bookly' ) ?>">
108
+ ({{dataSource.getTotalNumberOfPersons()}}/{{form.service.capacity_max}})
109
+ </span>
110
+ <span ng-show="form.customers.length > 5" ng-click="form.expand_customers_list = !form.expand_customers_list" role="button">
111
+ <i class="fa fa-fw" ng-class="{'fa-angle-down':!form.expand_customers_list, 'fa-angle-up':form.expand_customers_list}"></i>
112
+ </span>
113
+ <p class="text-success" ng-show=form.service my-slide-up="form.service.capacity_min > 1 && form.service.capacity_min > dataSource.getTotalNumberOfPersons()">
114
+ <?php esc_html_e( 'Minimum capacity', 'bookly' ) ?>: {{form.service.capacity_min}}
115
+ </p>
116
+ <ul class="bookly-flexbox">
117
+ <li ng-repeat="customer in form.customers" class="bookly-flex-row" ng-hide="$index > 4 && !form.expand_customers_list">
118
+ <div class="bookly-flex-cell-sm">
119
+ <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>
120
+ </div>
121
+ <div class="bookly-flex-cell-sm text-right text-nowrap bookly-padding-bottom-sm">
122
+ <?php Proxy\Shared::renderAppointmentDialogCustomersList() ?>
123
+ <span class="dropdown">
124
+ <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)}}">
125
+ <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>
126
+ <span class="caret"></span>
127
+ </button>
128
+ <ul class="dropdown-menu">
129
+ <li>
130
+ <a href ng-click="customer.status = 'pending'">
131
+ <span class="fa fa-fw fa-clock"></span>
132
+ <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_PENDING ) ) ?>
133
+ </a>
134
+ </li>
135
+ <li>
136
+ <a href ng-click="customer.status = 'approved'">
137
+ <span class="fa fa-fw fa-check"></span>
138
+ <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_APPROVED ) ) ?>
139
+ </a>
140
+ </li>
141
+ <li>
142
+ <a href ng-click="customer.status = 'cancelled'">
143
+ <span class="fa fa-fw fa-times"></span>
144
+ <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_CANCELLED ) ) ?>
145
+ </a>
146
+ </li>
147
+ <li>
148
+ <a href ng-click="customer.status = 'rejected'">
149
+ <span class="fa fa-fw fa-times-circle"></span>
150
+ <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_REJECTED ) ) ?>
151
+ </a>
152
+ </li>
153
+ <?php if ( Config::waitingListActive() ): ?>
154
+ <li>
155
+ <a href ng-click="customer.status = 'waitlisted'">
156
+ <span class="fa fa-fw fa-list-alt"></span>
157
+ <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_WAITLISTED ) ) ?>
158
+ </a>
159
+ </li>
160
+ <?php endif ?>
161
+ <?php if ( Config::tasksActive() ): ?>
162
+ <li>
163
+ <a href ng-click="customer.status = 'done'">
164
+ <span class="fa fa-fw fa-check-circle"></span>
165
+ <?php echo esc_html( CustomerAppointment::statusToString( CustomerAppointment::STATUS_DONE ) ) ?>
166
+ </a>
167
+ </li>
168
+ <?php endif ?>
169
+ </ul>
170
+ </span>
171
+ <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">
172
+ <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>
173
+ </button>
174
+
175
+ <?php Proxy\Pro::renderAttachPaymentButton() ?>
176
+
177
+ <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>
178
+ <?php if ( Config::packagesActive() ) : ?>
179
+ <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' ) ?>">
180
+ <span class="fa fa-fw fa-calendar-alt"></span>
181
+ </button>
182
+ <?php endif ?>
183
+ <?php if ( Config::recurringAppointmentsActive() ) : ?>
184
+ <button type="button" class="btn btn-sm btn-default bookly-margin-left-xs" ng-click="schViewSeries(customer)" ng-show="customer.series_id" popover="<?php esc_attr_e( 'View series', 'bookly' ) ?>">
185
+ <span class="fa fa-fw fa-link"></span>
186
+ </button>
187
+ <?php endif ?>
188
+ <a ng-click="removeCustomer(customer)" class="fa fa-fw fa-trash-alt text-danger bookly-vertical-middle" href="#"
189
+ popover="<?php esc_attr_e( 'Remove customer', 'bookly' ) ?>"></a>
190
+ </div>
191
+ </li>
192
+ </ul>
193
+ <span class="btn btn-default" ng-show="form.customers.length > 5 && !form.expand_customers_list" ng-click="form.expand_customers_list = !form.expand_customers_list" style="width: 100%; line-height: 0; padding-top: 0; padding-bottom: 8px; margin-bottom: 10px;" role="button">...</span>
194
+ <div <?php if ( ! Config::waitingListActive() ): ?>ng-show="!form.service || dataSource.getTotalNumberOfNotCancelledPersons() < form.service.capacity_max"<?php endif ?>>
195
+ <div class="form-group">
196
+ <div class="input-group">
197
+ <select id="bookly-select2" multiple data-placeholder="<?php esc_attr_e( '-- Search customers --', 'bookly' ) ?>"
198
+ class="form-control"
199
+ ng-model="form.customers" ng-options="c.name for c in dataSource.data.customers"
200
+ ng-change="onCustomersChange({{form.customers}}, {{dataSource.getTotalNumberOfNotCancelledPersons()}})">
201
+ </select>
202
+ <span class="input-group-btn">
203
+ <a class="btn btn-success" ng-click="openNewCustomerDialog()">
204
+ <i class="fa fa-fw fa-plus"></i>
205
+ <?php esc_html_e( 'New customer', 'bookly' ) ?>
206
+ </a>
207
+ </span>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ <p class="text-danger" my-slide-up="errors.overflow_capacity" ng-bind="errors.overflow_capacity"></p>
212
+ <p class="text-success" my-slide-up="errors.customers_appointments_limit" ng-repeat="customer_error in errors.customers_appointments_limit">
213
+ {{customer_error}}
214
+ </p>
215
+ </div>
216
+
217
+ <div class=form-group>
218
+ <label for="bookly-notification"><?php esc_html_e( 'Send notifications', 'bookly' ) ?></label>
219
+ <p class="help-block"><?php is_admin() ?
220
+ esc_html_e( 'If email or SMS notifications are enabled and you want customers or staff member to be notified about this appointment after saving, select appropriate option before clicking Save. With "If status changed" the notifications are sent to those customers whose status has just been changed. With "To all customers" the notifications are sent to everyone in the list.', 'bookly' ) :
221
+ esc_html_e( 'If email or SMS notifications are enabled and you want customers or yourself to be notified about this appointment after saving, select appropriate option before clicking Save. With "If status changed" the notifications are sent to those customers whose status has just been changed. With "To all customers" the notifications are sent to everyone in the list.', 'bookly' ) ?></p>
222
+ <select class="form-control" style="margin-top: 0" ng-model=form.notification id="bookly-notification" ng-init="form.notification = '<?php echo get_user_meta( get_current_user_id(), 'bookly_appointment_form_send_notifications', true ) ?>' || 'no'" >
223
+ <option value="no"><?php esc_html_e( 'Don\'t send', 'bookly' ) ?></option>
224
+ <option value="changed_status"><?php esc_html_e( 'If status changed', 'bookly' ) ?></option>
225
+ <option value="all"><?php esc_html_e( 'To all customers', 'bookly' ) ?></option>
226
+ </select>
227
+ </div>
228
+
229
+ <div class=form-group>
230
+ <label for="bookly-internal-note"><?php esc_html_e( 'Internal note', 'bookly' ) ?></label>
231
+ <textarea class="form-control" ng-model=form.internal_note id="bookly-internal-note"></textarea>
232
+ </div>
233
+ </div>
234
+ <?php Proxy\RecurringAppointments::renderSchedule() ?>
235
+ <div class="modal-footer">
236
+ <div ng-hide=loading>
237
+ <?php Proxy\Shared::renderAppointmentDialogFooter() ?>
238
+ <?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' => '' ) ) ?>
239
+ <?php Buttons::renderCustom( null, 'btn-lg btn-default', __( 'Cancel', 'bookly' ), array( 'ng-click' => 'closeDialog()', 'data-dismiss' => 'modal' ) ) ?>
240
+ </div>
241
+ </div>
242
+ </form>
243
+ </div>
244
+ </div>
245
+ </div>
246
+ <div customer-dialog=createCustomer(customer)></div>
247
+ <div payment-details-dialog="callbackPayment(payment_action, payment_id, payment_title, customer_id, payment_type)"></div>
248
+
249
+ <?php Dialogs\Appointment\CustomerDetails\Dialog::render() ?>
250
+ <?php AttachPaymentProxy\Pro::renderAttachPaymentDialog() ?>
251
+ <?php Dialogs\Customer\Edit\Dialog::render() ?>
252
+ <?php Dialogs\Payment\Dialog::render() ?>
253
+ </div>
backend/components/dialogs/common/CascadeDelete.php CHANGED
@@ -1,30 +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
  }
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 CHANGED
@@ -1,30 +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
  }
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 CHANGED
@@ -1,21 +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>
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 CHANGED
@@ -1,21 +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>
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/components/dialogs/customer/delete/Ajax.php ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Delete;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Ajax
8
+ * @package Bookly\Backend\Components\Dialogs\Customer\Delete
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
+ * Delete customers.
22
+ */
23
+ public static function deleteCustomers()
24
+ {
25
+ $customer_ids = array_unique( (array) self::parameter( 'customers' ) );
26
+ if ( $customer_ids ) {
27
+ update_user_meta(
28
+ get_current_user_id(),
29
+ 'bookly_delete_customers_options',
30
+ self::parameter( 'remember' ) ? array( 'with_wp_users' => self::parameter( 'with_wp_users' ), 'with_events' => self::parameter( 'with_events' ) ) : ''
31
+ );
32
+ /** @var Lib\Entities\Customer[] $customers */
33
+ $customers = Lib\Entities\Customer::query()
34
+ ->whereIn( 'id', $customer_ids )
35
+ ->indexBy( 'id' )
36
+ ->find();
37
+
38
+ $appointments = Lib\Entities\Appointment::query( 'a' )
39
+ ->select( 'a.id, ca.id as ca_id, ca.customer_id, a.internal_note' )
40
+ ->leftJoin( 'CustomerAppointment', 'ca', 'a.id = ca.appointment_id' )
41
+ ->whereIn( 'ca.customer_id', $customer_ids )
42
+ ->fetchArray();
43
+
44
+ if ( $appointments ) {
45
+ /** @var Lib\Entities\CustomerAppointment[] $ca_list */
46
+ $ca_list = Lib\Entities\CustomerAppointment::query()
47
+ ->whereIn( 'id', array_map( function ( $appointment ) { return $appointment['ca_id']; }, $appointments ) )
48
+ ->indexBy( 'id' )
49
+ ->find();
50
+
51
+ $info = sprintf( '%s, %s: ',
52
+ Lib\Utils\DateTime::formatDateTime( Lib\Slots\DatePoint::now()->format( 'Y-m-d H:i:s' ) ),
53
+ __( 'Deleted Customer', 'bookly' ) );
54
+ foreach ( $appointments as $appointment ) {
55
+ $note = Lib\Query::escape( $info . $customers[ $appointment['customer_id'] ]->getFullName() );
56
+ Lib\Entities\Appointment::query( 'a' )
57
+ ->update()
58
+ ->setRaw( 'internal_note = CONCAT_WS(%s,internal_note,%s)', array( $appointment['internal_note'] == '' ? '' : PHP_EOL, $note ) )
59
+ ->where( 'id', $appointment['id'] )
60
+ ->execute();
61
+ $ca_list[ $appointment['ca_id'] ]->delete();
62
+ }
63
+ }
64
+ foreach ( $customers as $customer ) {
65
+ $customer->deleteWithWPUser( (bool) self::parameter( 'with_wp_users' ) );
66
+ }
67
+ }
68
+
69
+ wp_send_json_success();
70
+ }
71
+
72
+ public static function checkCustomers()
73
+ {
74
+ $customer_ids = array_unique( (array) self::parameter( 'customers' ) );
75
+ $events = Lib\Entities\Appointment::query( 'a' )
76
+ ->leftJoin( 'CustomerAppointment', 'ca', 'a.id = ca.appointment_id' )
77
+ ->whereIn( 'ca.customer_id', $customer_ids )
78
+ ->whereIn( 'ca.status', array( Lib\Entities\CustomerAppointment::STATUS_APPROVED, Lib\Entities\CustomerAppointment::STATUS_PENDING ) )
79
+ ->whereGte( 'a.start_date', current_time( 'mysql' ) )
80
+ ->count() > 0;
81
+ $tasks = Lib\Entities\Appointment::query( 'a' )
82
+ ->leftJoin( 'CustomerAppointment', 'ca', 'a.id = ca.appointment_id' )
83
+ ->whereIn( 'ca.customer_id', $customer_ids )
84
+ ->whereIn( 'ca.status', array( Lib\Entities\CustomerAppointment::STATUS_APPROVED, Lib\Entities\CustomerAppointment::STATUS_PENDING ) )
85
+ ->where( 'a.start_date', null )
86
+ ->count() > 0;
87
+ $meta = get_user_meta( get_current_user_id(), 'bookly_delete_customers_options', true );
88
+ if ( $meta != '' ) {
89
+ $remember = true;
90
+ $with_events = isset( $meta['with_events'] ) && $meta['with_events'];
91
+ $with_wp_users = isset( $meta['with_wp_users'] ) && $meta['with_wp_users'];
92
+
93
+ } else {
94
+ $remember = false;
95
+ $with_events = false;
96
+ $with_wp_users = false;
97
+ }
98
+ wp_send_json_success( array(
99
+ 'with_events' => $with_events,
100
+ 'with_wp_users' => $with_wp_users,
101
+ 'remember' => $remember,
102
+ 'exists_events' => $events || $tasks,
103
+ ) );
104
+ }
105
+
106
+ /**
107
+ * Check if the current user has access to the action.
108
+ *
109
+ * @param string $action
110
+ * @return bool
111
+ */
112
+ protected static function hasAccess( $action )
113
+ {
114
+ if ( parent::hasAccess( $action ) ) {
115
+ if ( ! Lib\Utils\Common::isCurrentUserAdmin() ) {
116
+ switch ( $action ) {
117
+ case 'deleteCustomers':
118
+ case 'checkCustomers':
119
+ return Lib\Entities\Staff::query()
120
+ ->where( 'wp_user_id', get_current_user_id() )
121
+ ->count() > 0;
122
+ }
123
+ } else {
124
+ return true;
125
+ }
126
+ }
127
+
128
+ return false;
129
+ }
130
+ }
backend/components/dialogs/customer/delete/Dialog.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Delete;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Backend\Components\Controls\Buttons;
6
+
7
+ /**
8
+ * Class Dialog
9
+ * @package Bookly\Backend\Components\Dialogs\Customer\Delete
10
+ */
11
+ class Dialog extends Lib\Base\Component
12
+ {
13
+ /**
14
+ * Render customer dialog.
15
+ */
16
+ public static function render()
17
+ {
18
+ self::enqueueScripts( array(
19
+ 'module' => array( 'js/delete-customers.js' => array( 'jquery' ), )
20
+ ) );
21
+
22
+ wp_localize_script( 'bookly-delete-customers.js', 'BooklyL10nCustomerDelete', array(
23
+ 'csrfToken' => Lib\Utils\Common::getCsrfToken(),
24
+ ) );
25
+
26
+ static::renderTemplate( 'dialog' );
27
+ }
28
+
29
+ /**
30
+ * Render delete button on page (sub Customers table)
31
+ */
32
+ public static function renderDeleteButton()
33
+ {
34
+ Buttons::renderDelete();
35
+ }
36
+ }
backend/components/dialogs/customer/delete/resources/js/delete-customers.js ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(function ($) {
2
+
3
+ var
4
+ $customersList = $('#bookly-customers-list'),
5
+ $initDeletingButton = $('#bookly-delete'),
6
+ $deleteDialog = $('#bookly-delete-dialog'),
7
+ $deleteButton = $('.bookly-js-delete', $deleteDialog),
8
+ $rememberCheckbox = $('.bookly-js-remember-choice-checkbox', $deleteDialog),
9
+ $deleteEventsCheckbox = $('.bookly-js-delete-with-events-checkbox', $deleteDialog),
10
+ $deleteWPUserCheckbox = $('.bookly-js-delete-with-wp-user-checkbox', $deleteDialog)
11
+ ;
12
+
13
+ /**
14
+ * Delete customers.
15
+ */
16
+ $initDeletingButton.on('click', function () {
17
+ var ladda = Ladda.create(this),
18
+ customers = [],
19
+ $checkboxes = $customersList.find('tbody input:checked');
20
+
21
+ ladda.start();
22
+ $checkboxes.each(function () {
23
+ customers.push(this.value);
24
+ });
25
+
26
+ if (customers.length > 0) {
27
+ $.ajax({
28
+ url : ajaxurl,
29
+ type : 'POST',
30
+ data : {
31
+ action : 'bookly_check_customers',
32
+ csrf_token: BooklyL10nCustomerDelete.csrfToken,
33
+ customers : customers
34
+ },
35
+ dataType: 'json',
36
+ success : function (response) {
37
+ ladda.stop();
38
+ $('.bookly-js-delete-with-events', $deleteDialog).toggle(response.data.exists_events);
39
+ $('.bookly-js-delete-without-events', $deleteDialog).toggle(!response.data.exists_events);
40
+
41
+ $deleteButton.prop('disabled', response.data.exists_events && !response.data.with_events);
42
+
43
+ $deleteEventsCheckbox.prop('checked', response.data.with_events);
44
+ $deleteWPUserCheckbox.prop('checked', response.data.with_wp_users);
45
+ $rememberCheckbox.prop('checked', response.data.remember);
46
+ $deleteDialog.modal('show');
47
+ }
48
+ });
49
+ } else {
50
+ setTimeout(function () {
51
+ ladda.stop();
52
+ }, 200);
53
+ }
54
+ });
55
+
56
+ $deleteEventsCheckbox.on('change', function () {
57
+ $deleteButton.prop('disabled', !$deleteEventsCheckbox.prop('checked'));
58
+ });
59
+
60
+ $deleteButton.on('click', function () {
61
+ var ladda = Ladda.create(this),
62
+ customers = [],
63
+ $checkboxes = $customersList.find('tbody input:checked')
64
+ ;
65
+
66
+ ladda.start();
67
+ $checkboxes.each(function () {
68
+ customers.push(this.value);
69
+ });
70
+
71
+ $.ajax({
72
+ url : ajaxurl,
73
+ type : 'POST',
74
+ data: {
75
+ action : 'bookly_delete_customers',
76
+ csrf_token : BooklyL10nCustomerDelete.csrfToken,
77
+ customers : customers,
78
+ with_wp_users: $deleteWPUserCheckbox.prop('checked') ? 1 : 0,
79
+ with_events : $deleteEventsCheckbox.prop('checked') ? 1 : 0,
80
+ remember : $rememberCheckbox.prop('checked') ? 1 : 0
81
+ },
82
+ dataType: 'json',
83
+ success : function (response) {
84
+ ladda.stop();
85
+ $deleteDialog.modal('hide');
86
+ if (response.success) {
87
+ $customersList.DataTable().ajax.reload(null, false);
88
+ } else {
89
+ alert(response.data.message);
90
+ }
91
+ }
92
+ });
93
+ });
94
+ });
backend/components/dialogs/customer/delete/templates/dialog.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ ?>
4
+ <form id="bookly-delete-dialog" class="modal fade" tabindex=-1>
5
+ <div class="modal-dialog">
6
+ <div class="modal-content">
7
+ <div class="modal-header">
8
+ <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
9
+ <div class="modal-title h2"><?php esc_html_e( 'Delete customers', 'bookly' ) ?></div>
10
+ </div>
11
+ <div class="modal-body">
12
+ <p class="bookly-js-delete-with-events"><?php esc_html_e( 'You are going to delete customers with existing bookings. Notifications will not be sent to them.', 'bookly' ) ?></p>
13
+ <p class="bookly-js-delete-without-events"><?php esc_html_e( 'You are going to delete customers, are you sure?', 'bookly' ) ?></p>
14
+ <div class="bookly-js-delete-with-events bookly-margin-bottom-sm collapse">
15
+ <div class="checkbox">
16
+ <label>
17
+ <input class="bookly-js-delete-with-events-checkbox" type="checkbox"/><?php esc_html_e( 'Delete customers with existing bookings', 'bookly' ) ?>
18
+ </label>
19
+ </div>
20
+ </div>
21
+ <div>
22
+ <div class="checkbox">
23
+ <label>
24
+ <input class="bookly-js-delete-with-wp-user-checkbox" type="checkbox"/><?php esc_html_e( 'Delete customers\' WordPress accounts if there are any', 'bookly' ) ?>
25
+ </label>
26
+ </div>
27
+ </div>
28
+ <div style="margin-top: 80px;">
29
+ <div class="checkbox">
30
+ <label>
31
+ <input class="bookly-js-remember-choice-checkbox" type="checkbox"/><?php esc_html_e( 'Remember my choice', 'bookly' ) ?>
32
+ </label>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ <div class="modal-footer">
37
+ <?php Buttons::renderCustom( null, 'btn-danger ladda-button bookly-js-delete btn-lg', esc_html__( 'Delete', 'bookly' ) ) ?>
38
+ <?php Buttons::renderCustom( null, 'btn-default btn-lg', esc_html__( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </form>
backend/components/dialogs/customer/{EditAjax.php → edit/Ajax.php} RENAMED
@@ -1,103 +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
  }
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Edit;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Ajax
8
+ * @package Bookly\Backend\Components\Dialogs\Customer\Edit
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
+ * 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/components/dialogs/customer/{Edit.php → edit/Dialog.php} RENAMED
@@ -1,61 +1,63 @@
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' ),
30
- ),
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
  }
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Edit;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Dialog
8
+ * @package Bookly\Backend\Components\Dialogs\Customer\Edit
9
+ */
10
+ class Dialog extends Lib\Base\Component
11
+ {
12
+ /**
13
+ * Render customer 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', ),
21
+ 'frontend' => get_option( 'bookly_cst_phone_default_country' ) == 'disabled'
22
+ ? array()
23
+ : array( 'css/intlTelInput.css' ),
24
+ ) );
25
+
26
+ self::enqueueScripts( array(
27
+ 'backend' => array(
28
+ 'js/angular.min.js' => array( 'jquery' ),
29
+ 'js/select2.full.min.js' => array( 'jquery' ),
30
+ 'js/angular-ui-date-0.0.8.js' => array( 'bookly-angular.min.js', 'jquery-ui-datepicker' ),
31
+ ),
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.js' => array( 'bookly-angular.min.js' ), )
36
+ ) );
37
+
38
+ wp_localize_script( 'bookly-ng-customer.js', 'BooklyL10nCustDialog', array(
39
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken(),
40
+ 'first_last_name' => (int) Lib\Config::showFirstLastName(),
41
+ 'default_status' => get_option( 'bookly_gen_default_appointment_status' ),
42
+ 'intlTelInput' => array(
43
+ 'enabled' => get_option( 'bookly_cst_phone_default_country' ) != 'disabled',
44
+ 'utils' => is_rtl() ? '' : plugins_url( 'intlTelInput.utils.js', Lib\Plugin::getDirectory() . '/frontend/resources/js/intlTelInput.utils.js' ),
45
+ 'country' => get_option( 'bookly_cst_phone_default_country' ),
46
+ ),
47
+ 'dateOptions' => array(
48
+ 'dateFormat' => Lib\Utils\DateTime::convertFormat( 'date', Lib\Utils\DateTime::FORMAT_JQUERY_DATEPICKER ),
49
+ 'monthNamesShort' => array_values( $wp_locale->month_abbrev ),
50
+ 'monthNames' => array_values( $wp_locale->month ),
51
+ 'dayNamesMin' => array_values( $wp_locale->weekday_abbrev ),
52
+ 'longDays' => array_values( $wp_locale->weekday ),
53
+ 'firstDay' => (int) get_option( 'start_of_week' ),
54
+ 'yearRange' => sprintf( '%s:%s', date_create()->modify( '-100 years' )->format( 'Y' ), date( 'Y' ) ),
55
+ 'changeYear' => true,
56
+ ),
57
+ 'infoFields' => (array) Lib\Proxy\CustomerInformation::getFieldsWhichMayHaveData(),
58
+ 'noResultFound' => __( 'No result found', 'bookly' ),
59
+ ) );
60
+
61
+ static::renderTemplate( 'edit' );
62
+ }
63
  }
backend/components/dialogs/customer/{forms → edit/forms}/Customer.php RENAMED
@@ -1,36 +1,36 @@
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
- {
12
- protected static $entity_class = 'Customer';
13
-
14
- public function configure()
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
- }
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Edit\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
+ {
12
+ protected static $entity_class = 'Customer';
13
+
14
+ public function configure()
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/edit/proxy/CustomerGroups.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Edit\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CustomerGroups
8
+ * @package Bookly\Backend\Components\Dialogs\Customer\Edit\Proxy
9
+ *
10
+ * @method static void renderCustomerDialog() Render 'Customer Group' row in edit customer dialog.
11
+ */
12
+ abstract class CustomerGroups extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/components/dialogs/customer/edit/proxy/CustomerInformation.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Edit\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class CustomerInformation
8
+ * @package Bookly\Backend\Components\Dialogs\Customer\Edit\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/edit/proxy/Pro.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Edit\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Pro
8
+ * @package Bookly\Backend\Components\Dialogs\Customer\Edit\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/components/dialogs/customer/{resources → edit/resources}/js/ng-customer.js RENAMED
@@ -1,197 +1,215 @@
1
- ;(function() {
2
-
3
- angular.module('customerDialog', ['ui.date']).directive('customerDialog', function() {
4
- return {
5
- restrict : 'A',
6
- replace : true,
7
- scope : {
8
- callback : '&customerDialog',
9
- form : '=customer'
10
- },
11
- templateUrl : 'bookly-customer-dialog.tpl',
12
- // The linking function will add behavior to the template.
13
- link: function(scope, element, attrs) {
14
- // Init properties.
15
- // 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 = {
43
- name: {required: false}
44
- };
45
- scope.$watch('form', function(newValue, oldValue) {
46
- if (newValue.name) {
47
- scope.errors.name.required = false;
48
- }
49
- });
50
- // Loading indicator.
51
- scope.loading = false;
52
-
53
- // Init intlTelInput.
54
- if (BooklyL10nCustDialog.intlTelInput.enabled) {
55
- element.find('#phone').intlTelInput({
56
- preferredCountries: [BooklyL10nCustDialog.intlTelInput.country],
57
- initialCountry: BooklyL10nCustDialog.intlTelInput.country,
58
- geoIpLookup: function (callback) {
59
- jQuery.get('https://ipinfo.io', function() {}, 'jsonp').always(function(resp) {
60
- var countryCode = (resp && resp.country) ? resp.country : '';
61
- callback(countryCode);
62
- });
63
- },
64
- utilsScript: BooklyL10nCustDialog.intlTelInput.utils
65
- });
66
- }
67
-
68
- // Do stuff on modal hide.
69
- element.on('hidden.bs.modal', function () {
70
- // Fix scroll issues when another modal is shown.
71
- if (jQuery('.modal-backdrop').length) {
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
- */
112
- scope.processForm = function() {
113
- scope.errors = {};
114
- scope.loading = true;
115
- scope.form.phone = BooklyL10nCustDialog.intlTelInput.enabled
116
- ? element.find('#phone').intlTelInput('getNumber')
117
- : element.find('#phone').val();
118
- jQuery.ajax({
119
- url : ajaxurl,
120
- type : 'POST',
121
- data : jQuery.extend({ action : 'bookly_save_customer', csrf_token : BooklyL10nCustDialog.csrf_token }, scope.form),
122
- dataType : 'json',
123
- success : function ( response ) {
124
- scope.$apply(function(scope) {
125
- if (response.success) {
126
- response.customer.custom_fields = [];
127
- response.customer.extras = [];
128
- response.customer.status = BooklyL10nCustDialog.default_status;
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');
153
- } else {
154
- // Set errors.
155
- jQuery.each(response.errors, function(field, errors) {
156
- scope.errors[field] = {};
157
- jQuery.each(errors, function(key, error) {
158
- scope.errors[field][error] = true;
159
- });
160
- });
161
- }
162
- scope.loading = false;
163
- });
164
- },
165
- error : function() {
166
- scope.$apply(function(scope) {
167
- scope.loading = false;
168
- });
169
- }
170
- });
171
- };
172
-
173
- /**
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
- });
196
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  })();
1
+ ;(function() {
2
+
3
+ angular.module('customerDialog', ['ui.date']).directive('customerDialog', function() {
4
+ return {
5
+ restrict : 'A',
6
+ replace : true,
7
+ scope : {
8
+ callback : '&customerDialog',
9
+ form : '=customer'
10
+ },
11
+ templateUrl : 'bookly-customer-dialog.tpl',
12
+ // The linking function will add behavior to the template.
13
+ link: function(scope, element, attrs) {
14
+ // Init properties.
15
+ // 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 = {
43
+ name: {required: false}
44
+ };
45
+ scope.$watch('form', function(newValue, oldValue) {
46
+ if (newValue.name) {
47
+ scope.errors.name.required = false;
48
+ }
49
+ });
50
+ // Loading indicator.
51
+ scope.loading = false;
52
+
53
+ // Init intlTelInput.
54
+ if (BooklyL10nCustDialog.intlTelInput.enabled) {
55
+ element.find('#phone').intlTelInput({
56
+ preferredCountries: [BooklyL10nCustDialog.intlTelInput.country],
57
+ initialCountry: BooklyL10nCustDialog.intlTelInput.country,
58
+ geoIpLookup: function (callback) {
59
+ jQuery.get('https://ipinfo.io', function() {}, 'jsonp').always(function(resp) {
60
+ var countryCode = (resp && resp.country) ? resp.country : '';
61
+ callback(countryCode);
62
+ });
63
+ },
64
+ utilsScript: BooklyL10nCustDialog.intlTelInput.utils
65
+ });
66
+ }
67
+
68
+ // Init select2 for wp_users.
69
+ jQuery('#wp_user')
70
+ .on('select2:unselecting', function(e) {
71
+ e.preventDefault();
72
+ jQuery(this).val(null).trigger('change');
73
+ })
74
+ .select2({
75
+ width: '100%',
76
+ theme: 'bootstrap',
77
+ allowClear: true,
78
+ dropdownParent: jQuery('#bookly-customer-dialog'),
79
+ language: {
80
+ noResults: function () {
81
+ return BooklyL10nCustDialog.noResultFound;
82
+ }
83
+ }
84
+ });
85
+
86
+ // Do stuff on modal hide.
87
+ element.on('hidden.bs.modal', function () {
88
+ // Fix scroll issues when another modal is shown.
89
+ if (jQuery('.modal-backdrop').length) {
90
+ jQuery('body').addClass('modal-open');
91
+ }
92
+ });
93
+ scope.changeWpUser = function () {
94
+ var $user = jQuery('#wp_user option:selected'),
95
+ email = $user.attr('data-email') != undefined ? $user.attr('data-email') : '',
96
+ first_name = $user.attr('data-first-name') != undefined ? $user.attr('data-first-name') : '',
97
+ last_name = $user.attr('data-last-name') != undefined ? $user.attr('data-last-name') : '',
98
+ phone = $user.attr('data-phone') != undefined ? $user.attr('data-phone') : '',
99
+ display_name = $user.text().trim();
100
+ if (BooklyL10nCustDialog.first_last_name == 1) {
101
+ if (!first_name.length && !last_name.length) {
102
+ var name_parts = display_name.split(' ');
103
+ first_name = name_parts[0];
104
+ name_parts.splice(0, 1);
105
+ last_name = name_parts.join(' ');
106
+ }
107
+ if (first_name.length) {
108
+ scope.form.first_name = first_name;
109
+ }
110
+ if (last_name.length) {
111
+ scope.form.last_name = last_name;
112
+ }
113
+ } else {
114
+ if (first_name.length || last_name.length) {
115
+ scope.form.full_name = (first_name + ' ' + last_name).trim();
116
+ } else {
117
+ scope.form.full_name = display_name;
118
+ }
119
+ }
120
+ if (email.length) {
121
+ scope.form.email = email;
122
+ }
123
+ if (phone.length) {
124
+ scope.form.phone = phone;
125
+ }
126
+ };
127
+ /**
128
+ * Send form to server.
129
+ */
130
+ scope.processForm = function() {
131
+ scope.errors = {};
132
+ scope.loading = true;
133
+ scope.form.phone = BooklyL10nCustDialog.intlTelInput.enabled
134
+ ? element.find('#phone').intlTelInput('getNumber')
135
+ : element.find('#phone').val();
136
+ jQuery.ajax({
137
+ url : ajaxurl,
138
+ type : 'POST',
139
+ data : jQuery.extend({ action : 'bookly_save_customer', csrf_token : BooklyL10nCustDialog.csrf_token }, scope.form),
140
+ dataType : 'json',
141
+ success : function ( response ) {
142
+ scope.$apply(function(scope) {
143
+ if (response.success) {
144
+ response.customer.custom_fields = [];
145
+ response.customer.extras = [];
146
+ response.customer.status = BooklyL10nCustDialog.default_status;
147
+ // Send new customer to the parent scope.
148
+ scope.callback({customer : response.customer});
149
+ scope.form = {
150
+ id : '',
151
+ wp_user_id : '',
152
+ group_id : '',
153
+ full_name : '',
154
+ first_name : '',
155
+ last_name : '',
156
+ phone : '',
157
+ email : '',
158
+ country : '',
159
+ state : '',
160
+ postcode : '',
161
+ city : '',
162
+ street : '',
163
+ street_number : '',
164
+ additional_address : '',
165
+ info_fields : [],
166
+ notes : '',
167
+ birthday : ''
168
+ };
169
+ // Close the dialog.
170
+ element.modal('hide');
171
+ } else {
172
+ // Set errors.
173
+ jQuery.each(response.errors, function(field, errors) {
174
+ scope.errors[field] = {};
175
+ jQuery.each(errors, function(key, error) {
176
+ scope.errors[field][error] = true;
177
+ });
178
+ });
179
+ }
180
+ scope.loading = false;
181
+ });
182
+ },
183
+ error : function() {
184
+ scope.$apply(function(scope) {
185
+ scope.loading = false;
186
+ });
187
+ }
188
+ });
189
+ };
190
+
191
+ /**
192
+ * Datepicker options.
193
+ */
194
+ scope.dateOptions = BooklyL10nCustDialog.dateOptions;
195
+
196
+ /**
197
+ * Toggle checkbox info field.
198
+ */
199
+ scope.toggleCheckbox = function (i, value) {
200
+ var idx = scope.form.info_fields[i].value.indexOf(value);
201
+
202
+ // Is currently selected.
203
+ if (idx > -1) {
204
+ scope.form.info_fields[i].value.splice(idx, 1);
205
+ }
206
+ // Is newly selected.
207
+ else {
208
+ scope.form.info_fields[i].value.push(value);
209
+ }
210
+ };
211
+ }
212
+ };
213
+ });
214
+
215
  })();
backend/components/dialogs/customer/{templates → edit/templates}/edit.php RENAMED
@@ -1,84 +1,83 @@
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">
8
- <div class="modal-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 ?>
28
- </select>
29
- </div>
30
-
31
- <?php if ( Config::showFirstLastName() ) : ?>
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>
82
- </div>
83
- </div>
84
  </script>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Backend\Components\Controls\Buttons;
3
+ use Bookly\Backend\Components\Dialogs\Customer\Edit\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">
8
+ <div class="modal-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
+ <?php foreach ( get_users( array( 'fields' => array( 'ID', 'display_name', 'user_email' ), 'orderby' => 'display_name' ) ) as $wp_user ) : ?>
22
+ <?php $user_data = get_userdata( $wp_user->ID ) ?>
23
+ <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 ) ) ?>">
24
+ <?php echo $wp_user->display_name ?>
25
+ </option>
26
+ <?php endforeach ?>
27
+ </select>
28
+ </div>
29
+
30
+ <?php if ( Config::showFirstLastName() ) : ?>
31
+ <div class="form-group">
32
+ <div class="row">
33
+ <div class="col-sm-6">
34
+ <label for="first_name"><?php esc_html_e( 'First name', 'bookly' ) ?></label>
35
+ <input class="form-control" type="text" ng-model="form.first_name" id="first_name" />
36
+ <span style="font-size: 11px;color: red" ng-show="errors.first_name.required"><?php esc_html_e( 'Required', 'bookly' ) ?></span>
37
+ </div>
38
+ <div class="col-sm-6">
39
+ <label for="last_name"><?php esc_html_e( 'Last name', 'bookly' ) ?></label>
40
+ <input class="form-control" type="text" ng-model="form.last_name" id="last_name" />
41
+ <span style="font-size: 11px;color: red" ng-show="errors.last_name.required"><?php esc_html_e( 'Required', 'bookly' ) ?></span>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ <?php else : ?>
46
+ <div class="form-group">
47
+ <label for="full_name"><?php esc_html_e( 'Name', 'bookly' ) ?></label>
48
+ <input class="form-control" type="text" ng-model="form.full_name" id="full_name" />
49
+ <span style="font-size: 11px;color: red" ng-show="errors.full_name.required"><?php esc_html_e( 'Required', 'bookly' ) ?></span>
50
+ </div>
51
+ <?php endif ?>
52
+
53
+ <div class="form-group">
54
+ <label for="phone"><?php esc_html_e( 'Phone', 'bookly' ) ?></label>
55
+ <input class="form-control" type="text" ng-model=form.phone id="phone" />
56
+ </div>
57
+
58
+ <div class="form-group">
59
+ <label for="email"><?php esc_html_e( 'Email', 'bookly' ) ?></label>
60
+ <input class="form-control" type="text" ng-model=form.email id="email" />
61
+ </div>
62
+
63
+ <?php Proxy\Pro::renderCustomerDialogBirthday() ?>
64
+ <?php Proxy\Pro::renderCustomerDialogAddress() ?>
65
+ <?php Proxy\CustomerInformation::renderCustomerDialog() ?>
66
+ <?php Proxy\CustomerGroups::renderCustomerDialog() ?>
67
+
68
+ <div class="form-group">
69
+ <label for="notes"><?php esc_html_e( 'Notes', 'bookly' ) ?></label>
70
+ <textarea class="form-control" ng-model=form.notes id="notes"></textarea>
71
+ </div>
72
+
73
+ </div>
74
+ <div class="modal-footer">
75
+ <div ng-hide=loading>
76
+ <?php Buttons::renderCustom( null, 'btn-success btn-lg', null, array( 'ng-click' => 'processForm()' ) ) ?>
77
+ <?php Buttons::renderCustom( null, 'btn-default btn-lg', esc_html__( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>
 
83
  </script>
backend/components/dialogs/customer/proxy/CustomerGroups.php CHANGED
@@ -1,16 +1,14 @@
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
  }
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * @since Bookly 16.2
8
+ * @deprecated
9
+ * Proxy file was moved
10
+ */
11
+ abstract class CustomerGroups extends Lib\Base\Proxy
12
+ {
13
+
 
 
14
  }
backend/components/dialogs/customer/proxy/CustomerInformation.php CHANGED
@@ -1,16 +1,14 @@
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
  }
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * @since Bookly 16.2
8
+ * @deprecated
9
+ * Proxy file was moved
10
+ */
11
+ abstract class CustomerInformation extends Lib\Base\Proxy
12
+ {
13
+
 
 
14
  }
backend/components/dialogs/customer/proxy/Pro.php CHANGED
@@ -1,16 +1,14 @@
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
  }
1
+ <?php
2
+ namespace Bookly\Backend\Components\Dialogs\Customer\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * @since Bookly 16.2
8
+ * @deprecated
9
+ * Proxy file was moved
10
+ */
11
+ abstract class Pro extends Lib\Base\Proxy
12
+ {
13
+
 
 
14
  }
backend/components/dialogs/payment/Ajax.php CHANGED
@@ -1,105 +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
  }
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 CHANGED
@@ -1,32 +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
  }
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 CHANGED
@@ -1,151 +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
  })();
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 CHANGED
@@ -1,217 +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 ?>
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 ( $payment['extras_multiply_nop'] && $item['number_of_persons'] > 1 ) echo $item['number_of_persons'] . '&nbsp;&times;&nbsp;' ?><?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/components/dialogs/payment/templates/dialog.php CHANGED
@@ -1,21 +1,21 @@
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">
7
- <div class="modal-content">
8
- <div class="modal-header">
9
- <button type="button" class="close" data-dismiss="modal" aria-label="<?php esc_attr_e( 'Close', 'bookly' ) ?>"><span aria-hidden="true">&times;</span></button>
10
- <div class="modal-title h2"><?php _e( 'Payment', 'bookly' ) ?></div>
11
- </div>
12
- <div class="modal-body">
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>
20
- </div>
21
  </script>
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">
7
+ <div class="modal-content">
8
+ <div class="modal-header">
9
+ <button type="button" class="close" data-dismiss="modal" aria-label="<?php esc_attr_e( 'Close', 'bookly' ) ?>"><span aria-hidden="true">&times;</span></button>
10
+ <div class="modal-title h2"><?php _e( 'Payment', 'bookly' ) ?></div>
11
+ </div>
12
+ <div class="modal-body">
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>
20
+ </div>
21
  </script>
backend/components/dialogs/special_price/proxy/SpecialHours.php CHANGED
@@ -1,14 +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
  }
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 CHANGED
@@ -1,47 +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
  }
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 CHANGED
@@ -1,41 +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
  }
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 CHANGED
@@ -1,19 +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
  }
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 CHANGED
@@ -1,27 +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
  }
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 CHANGED
@@ -1,21 +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
  }
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 CHANGED
@@ -1,48 +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
  }
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 CHANGED
@@ -1,40 +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
  }
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 CHANGED
@@ -1,38 +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
  }
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 CHANGED
@@ -1,36 +1,46 @@
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
  }
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
+ $state = 'invalid';
19
+ if ( is_email( $email ) ) {
20
+ $state = Lib\API::registerSubscriber( $email );
21
+ }
22
+
23
+ switch ( $state ) {
24
+ case 'success':
25
+ wp_send_json_success( array( 'message' => __( 'Please, check your email to confirm the subscription. Thank you!', 'bookly' ) ) );
26
+ break;
27
+ case 'exists':
28
+ wp_send_json_success( array( 'message' => __( 'Given email address is already subscribed, thank you!', 'bookly' ) ) );
29
+ break;
30
+ case 'invalid':
31
+ default:
32
+ wp_send_json_error( array( 'message' => __( 'This email address is not valid.', 'bookly' ) ) );
33
+ break;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Dismiss subscribe notice.
39
+ */
40
+ public static function dismissSubscribeNotice()
41
+ {
42
+ update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_subscribe_notice', 1 );
43
+
44
+ wp_send_json_success();
45
+ }
46
  }
backend/components/notices/proxy/Pro.php CHANGED
@@ -1,15 +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
  }
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/components/notices/resources/css/bootstrap-stars.css CHANGED
@@ -1,38 +1,38 @@
1
- .br-theme-bootstrap-stars .br-widget {
2
- height: 28px;
3
- white-space: nowrap;
4
- }
5
- .br-theme-bootstrap-stars .br-widget a {
6
- font: normal normal normal 30px/1 'Glyphicons Halflings';
7
- text-rendering: auto;
8
- -webkit-font-smoothing: antialiased;
9
- text-decoration: none;
10
- margin-right: 2px;
11
- }
12
- .br-theme-bootstrap-stars .br-widget a:after {
13
- content: '\e006';
14
- color: #d2d2d2;
15
- }
16
- .br-theme-bootstrap-stars .br-widget a.br-active:after {
17
- color: #EDB867;
18
- }
19
- .br-theme-bootstrap-stars .br-widget a.br-selected:after {
20
- color: #EDB867;
21
- }
22
- .br-theme-bootstrap-stars .br-widget .br-current-rating {
23
- display: none;
24
- }
25
- .br-theme-bootstrap-stars .br-readonly a {
26
- cursor: default;
27
- }
28
- @media print {
29
- .br-theme-bootstrap-stars .br-widget a:after {
30
- content: '\e007';
31
- color: black;
32
- }
33
- .br-theme-bootstrap-stars .br-widget a.br-active:after,
34
- .br-theme-bootstrap-stars .br-widget a.br-selected:after {
35
- content: '\e006';
36
- color: black;
37
- }
38
- }
1
+ .br-theme-bootstrap-stars .br-widget {
2
+ height: 28px;
3
+ white-space: nowrap;
4
+ }
5
+ .br-theme-bootstrap-stars .br-widget a {
6
+ font: normal normal normal 30px/1 'Glyphicons Halflings';
7
+ text-rendering: auto;
8
+ -webkit-font-smoothing: antialiased;
9
+ text-decoration: none;
10
+ margin-right: 2px;
11
+ }
12
+ .br-theme-bootstrap-stars .br-widget a:after {
13
+ content: '\e006';
14
+ color: #d2d2d2;
15
+ }
16
+ .br-theme-bootstrap-stars .br-widget a.br-active:after {
17
+ color: #EDB867;
18
+ }
19
+ .br-theme-bootstrap-stars .br-widget a.br-selected:after {
20
+ color: #EDB867;
21
+ }
22
+ .br-theme-bootstrap-stars .br-widget .br-current-rating {
23
+ display: none;
24
+ }
25
+ .br-theme-bootstrap-stars .br-readonly a {
26
+ cursor: default;
27
+ }
28
+ @media print {
29
+ .br-theme-bootstrap-stars .br-widget a:after {
30
+ content: '\e007';
31
+ color: black;
32
+ }
33
+ .br-theme-bootstrap-stars .br-widget a.br-active:after,
34
+ .br-theme-bootstrap-stars .br-widget a.br-selected:after {
35
+ content: '\e006';
36
+ color: black;
37
+ }
38
+ }
backend/components/notices/resources/js/collect-stats.js CHANGED
@@ -1,9 +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
  });
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/components/notices/resources/js/lite-rebranding.js CHANGED
@@ -1,6 +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
  });
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/components/notices/resources/js/nps.js CHANGED
@@ -1,60 +1,60 @@
1
- jQuery(function ($) {
2
- var $alert = $('#bookly-nps-notice'),
3
- $quiz = $('#bookly-nps-quiz'),
4
- $stars = $('#bookly-nps-stars'),
5
- $msg = $('#bookly-nps-msg'),
6
- $email = $('#bookly-nps-email'),
7
- $form = $('#bookly-nps-form'),
8
- $thanks = $('#bookly-nps-thanks')
9
- ;
10
-
11
- // Init stars.
12
- $stars.barrating({
13
- theme: 'bootstrap-stars',
14
- allowEmpty: false,
15
- onSelect: function (value, text, event) {
16
- if (value <= 7) {
17
- $form.show();
18
- } else {
19
- $.post(ajaxurl, {action: 'bookly_nps_send', csrf_token : SupportL10n.csrf_token, rate: value});
20
- $quiz.hide();
21
- $form.hide();
22
- $thanks.show();
23
- }
24
- }
25
- });
26
-
27
- $('#bookly-nps-btn').on('click', function () {
28
- $alert.find('.form-group').removeClass('has-error');
29
- if ($msg.val() == '') {
30
- $msg.closest('.form-group').addClass('has-error');
31
- } else {
32
- var ladda = Ladda.create(this);
33
- ladda.start();
34
- $.post(
35
- ajaxurl,
36
- {
37
- action : 'bookly_nps_send',
38
- csrf_token : SupportL10n.csrf_token,
39
- rate : $stars.val(),
40
- msg : $msg.val(),
41
- email : $email.val()
42
- },
43
- function (response) {
44
- ladda.stop();
45
- if (response.success) {
46
- $alert.alert('close');
47
- booklyAlert({success : [response.data.message]});
48
- }
49
- }
50
- );
51
- }
52
- });
53
-
54
- $alert.on('close.bs.alert', function () {
55
- $.post(ajaxurl, {action: 'bookly_dismiss_nps_notice', csrf_token : SupportL10n.csrf_token}, function () {
56
- // Indicator for Selenium that request has completed.
57
- $('.bookly-js-nps-notice').remove();
58
- });
59
- });
60
  });
1
+ jQuery(function ($) {
2
+ var $alert = $('#bookly-nps-notice'),
3
+ $quiz = $('#bookly-nps-quiz'),
4
+ $stars = $('#bookly-nps-stars'),
5
+ $msg = $('#bookly-nps-msg'),
6
+ $email = $('#bookly-nps-email'),
7
+ $form = $('#bookly-nps-form'),
8
+ $thanks = $('#bookly-nps-thanks')
9
+ ;
10
+
11
+ // Init stars.
12
+ $stars.barrating({
13
+ theme: 'bootstrap-stars',
14
+ allowEmpty: false,
15
+ onSelect: function (value, text, event) {
16
+ if (value <= 7) {
17
+ $form.show();
18
+ } else {
19
+ $.post(ajaxurl, {action: 'bookly_nps_send', csrf_token : SupportL10n.csrf_token, rate: value});
20
+ $quiz.hide();
21
+ $form.hide();
22
+ $thanks.show();
23
+ }
24
+ }
25
+ });
26
+
27
+ $('#bookly-nps-btn').on('click', function () {
28
+ $alert.find('.form-group').removeClass('has-error');
29
+ if ($msg.val() == '') {
30
+ $msg.closest('.form-group').addClass('has-error');
31
+ } else {
32
+ var ladda = Ladda.create(this);
33
+ ladda.start();
34
+ $.post(
35
+ ajaxurl,
36
+ {
37
+ action : 'bookly_nps_send',
38
+ csrf_token : SupportL10n.csrf_token,
39
+ rate : $stars.val(),
40
+ msg : $msg.val(),
41
+ email : $email.val()
42
+ },
43
+ function (response) {
44
+ ladda.stop();
45
+ if (response.success) {
46
+ $alert.alert('close');
47
+ booklyAlert({success : [response.data.message]});
48
+ }
49
+ }
50
+ );
51
+ }
52
+ });
53
+
54
+ $alert.on('close.bs.alert', function () {
55
+ $.post(ajaxurl, {action: 'bookly_dismiss_nps_notice', csrf_token : SupportL10n.csrf_token}, function () {
56
+ // Indicator for Selenium that request has completed.
57
+ $('.bookly-js-nps-notice').remove();
58
+ });
59
+ });
60
  });
backend/components/notices/resources/js/subscribe.js CHANGED
@@ -1,24 +1,24 @@
1
- jQuery(function ($) {
2
- var $alert = $('#bookly-subscribe-notice');
3
- $('#bookly-subscribe-btn').on('click', function () {
4
- $alert.find('.input-group').removeClass('has-error');
5
- var ladda = Ladda.create(this);
6
- ladda.start();
7
- $.post(ajaxurl, {action: 'bookly_subscribe', csrf_token : SupportL10n.csrf_token, email: $('#bookly-subscribe-email').val()}, function (response) {
8
- ladda.stop();
9
- if (response.success) {
10
- $alert.alert('close');
11
- booklyAlert({success : [response.data.message]});
12
- } else {
13
- $alert.find('.input-group').addClass('has-error');
14
- booklyAlert({error : [response.data.message]});
15
- }
16
- });
17
- });
18
- $alert.on('close.bs.alert', function () {
19
- $.post(ajaxurl, {action: 'bookly_dismiss_subscribe_notice', csrf_token : SupportL10n.csrf_token}, function () {
20
- // Indicator for Selenium that request has completed.
21
- $('.bookly-js-subscribe-notice').remove();
22
- });
23
- });
24
  });
1
+ jQuery(function ($) {
2
+ var $alert = $('#bookly-subscribe-notice');
3
+ $('#bookly-subscribe-btn').on('click', function () {
4
+ $alert.find('.input-group').removeClass('has-error');
5
+ var ladda = Ladda.create(this);
6
+ ladda.start();
7
+ $.post(ajaxurl, {action: 'bookly_subscribe', csrf_token : SupportL10n.csrf_token, email: $('#bookly-subscribe-email').val()}, function (response) {
8
+ ladda.stop();
9
+ if (response.success) {
10
+ $alert.alert('close');
11
+ booklyAlert({success: [response.data.message]});
12
+ } else {
13
+ $alert.find('.input-group').addClass('has-error');
14
+ booklyAlert({error : [response.data.message]});
15
+ }
16
+ });
17
+ });
18
+ $alert.on('close.bs.alert', function () {
19
+ $.post(ajaxurl, {action: 'bookly_dismiss_subscribe_notice', csrf_token : SupportL10n.csrf_token}, function () {
20
+ // Indicator for Selenium that request has completed.
21
+ $alert.remove();
22
+ });
23
+ });
24
  });
backend/components/notices/templates/collect_stats.php CHANGED
@@ -1,23 +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>
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 CHANGED
@@ -1,3 +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>
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 CHANGED
@@ -1,12 +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>
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/components/notices/templates/nps.php CHANGED
@@ -1,41 +1,41 @@
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">
15
- <option value=""></option>
16
- <?php for ( $i = 1; $i <= 10; ++ $i ): ?>
17
- <option value="<?php echo $i ?>"><?php echo $i ?></option>
18
- <?php endfor ?>
19
- </select>
20
- </div>
21
- <div id="bookly-nps-form" class="bookly-margin-top-lg" style="max-width:400px;display:none;">
22
- <div class="form-group">
23
- <label for="bookly-nps-msg" class="control-label"><?php _e( 'What do you think should be improved?', 'bookly' ) ?></label>
24
- <textarea id="bookly-nps-msg" class="form-control"></textarea>
25
- </div>
26
- <div class="form-group">
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>
39
- </div>
40
- </div>
41
  </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 esc_html_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">
15
+ <option value=""></option>
16
+ <?php for ( $i = 1; $i <= 10; ++ $i ): ?>
17
+ <option value="<?php echo $i ?>"><?php echo $i ?></option>
18
+ <?php endfor ?>
19
+ </select>
20
+ </div>
21
+ <div id="bookly-nps-form" class="bookly-margin-top-lg" style="max-width:400px;display:none;">
22
+ <div class="form-group">
23
+ <label for="bookly-nps-msg" class="control-label"><?php esc_html_e( 'What do you think should be improved?', 'bookly' ) ?></label>
24
+ <textarea id="bookly-nps-msg" class="form-control"></textarea>
25
+ </div>
26
+ <div class="form-group">
27
+ <label for="bookly-nps-email" class="control-label"><?php esc_html_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::REVIEWS_PAGE, 'nps' )
36
+ ) ?>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </div>
41
  </div>
backend/components/notices/templates/subscribe.php CHANGED
@@ -1,21 +1,21 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <div id="bookly-tbs" class="wrap bookly-js-subscribe-notice">
3
- <div id="bookly-subscribe-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">&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>
11
- <input type="text" id="bookly-subscribe-email" class="form-control" />
12
- <span class="input-group-btn">
13
- <button type="button" id="bookly-subscribe-btn" class="btn btn-info ladda-button" data-spinner-size="30" data-style="zoom-in">
14
- <span class="ladda-label"><?php _e( 'Send', 'bookly' ) ?></span>
15
- </button>
16
- </span>
17
- </div>
18
- </div>
19
- </div>
20
- </div>
21
  </div>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <div id="bookly-tbs" class="wrap bookly-js-subscribe-notice">
3
+ <div id="bookly-subscribe-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">&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>
11
+ <input type="text" id="bookly-subscribe-email" class="form-control" />
12
+ <span class="input-group-btn">
13
+ <button type="button" id="bookly-subscribe-btn" class="btn btn-info ladda-button" data-spinner-size="30" data-style="zoom-in">
14
+ <span class="ladda-label"><?php _e( 'Send', 'bookly' ) ?></span>
15
+ </button>
16
+ </span>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </div>
21
  </div>
backend/components/settings/Image.php CHANGED
@@ -1,30 +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
  }
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 CHANGED
@@ -1,76 +1,99 @@
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
  }
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
+ * Render text area input.
58
+ *
59
+ * @param string $option_name
60
+ * @param string $label
61
+ * @param string|null $help
62
+ * @param int $rows
63
+ */
64
+ public static function renderTextArea( $option_name, $label, $help = null, $rows = 9 )
65
+ {
66
+ $control = strtr(
67
+ '<textarea id="{name}" name="{name}" class="form-control" rows="{rows}" placeholder="{placeholder}">{value}</textarea>',
68
+ array(
69
+ '{name}' => esc_attr( $option_name ),
70
+ '{value}' => esc_textarea( get_option( $option_name ) ),
71
+ '{rows}' => $rows,
72
+ '{placeholder}' => esc_attr__( 'Enter a value', 'bookly' ),
73
+ )
74
+ );
75
+
76
+ echo self::buildControl( $option_name, $label, $help, $control );
77
+ }
78
+
79
+ /**
80
+ * Build setting control.
81
+ *
82
+ * @param string $option_name
83
+ * @param string $label
84
+ * @param string $help
85
+ * @param string $control_html
86
+ * @return string
87
+ */
88
+ public static function buildControl( $option_name, $label, $help, $control_html )
89
+ {
90
+ return strtr(
91
+ '<div class="form-group">{label}{help}{control}</div>',
92
+ array(
93
+ '{label}' => $label != '' ? sprintf( '<label for="%s">%s</label>', $option_name, $label ) : '',
94
+ '{help}' => $help != '' ? sprintf( '<p class="help-block">%s</p>', $help ) : '',
95
+ '{control}' => $control_html,
96
+ )
97
+ );
98
+ }
99
  }
backend/components/settings/Menu.php CHANGED
@@ -1,23 +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
  }
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 CHANGED
@@ -1,41 +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
  }
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 CHANGED
@@ -1,80 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,48 +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>
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 CHANGED
@@ -1,18 +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>
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/sms/custom/Notification.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Components\Sms\Custom;
3
+
4
+ use Bookly\Lib as BooklyLib;
5
+
6
+ /**
7
+ * Class Notification
8
+ * @package Bookly\Backend\Components\Sms\Custom
9
+ */
10
+ class Notification extends BooklyLib\Base\Component
11
+ {
12
+ /**
13
+ * Render custom notification
14
+ *
15
+ * @param \Bookly\Backend\Modules\Notifications\Forms\Notifications $form
16
+ * @param array $notification
17
+ * @param bool $echo
18
+ * @return string|void
19
+ */
20
+ public static function render( $form, $notification, $echo = true )
21
+ {
22
+ $unique = self::getFromCache( 'unique', 1 );
23
+
24
+ return self::renderTemplate( 'layout', compact( 'form', 'notification', 'unique' ), $echo );
25
+ }
26
+
27
+ /**
28
+ * Render settings for notification.
29
+ *
30
+ * @param string $set
31
+ * @param integer $id
32
+ * @param array $settings
33
+ */
34
+ public static function renderSet( $set, $id, $settings )
35
+ {
36
+ $statuses = BooklyLib\Entities\CustomerAppointment::getStatuses();
37
+ if ( ! self::hasInCache( 'service_dropdown_data' ) ) {
38
+ $service_dropdown_data = BooklyLib\Utils\Common::getServiceDataForDropDown( 's.type <> "package"' );
39
+ self::putInCache( 'service_dropdown_data', $service_dropdown_data );
40
+ }
41
+
42
+ $service_dropdown_data = self::getFromCache( 'service_dropdown_data' );
43
+ $unique = self::getFromCache( 'unique', 2 );
44
+ self::renderTemplate( $set, compact( 'id', 'settings', 'statuses', 'service_dropdown_data', 'unique' ) );
45
+ }
46
+ }
backend/components/sms/custom/templates/after_event.php ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Lib\Entities\CustomerAppointment;
3
+ use Bookly\Lib\DataHolders\Notification\Settings;
4
+
5
+ $set = Settings::SET_AFTER_EVENT;
6
+ $name = 'notification[' . $id . '][settings][' . $set . ']';
7
+ ?>
8
+ <div class="bookly-js-settings bookly-js-<?php echo $set ?>">
9
+ <div class="row">
10
+ <div class="col-md-3">
11
+ <div class="form-group">
12
+ <label for="notification_<?php echo ++ $unique ?>_status_1" class="bookly-js-with"><?php esc_attr_e( 'With status', 'bookly' ) ?></label>
13
+ <label for="notification_<?php echo $unique ?>_status_1" class="bookly-js-to"><?php esc_attr_e( 'To', 'bookly' ) ?></label>
14
+ <select class="form-control" name="<?php echo $name ?>[status]" id="notification_<?php echo $unique ?>_status_1">
15
+ <option value="any"><?php esc_attr_e( 'Any', 'bookly' ) ?></option>
16
+ <?php foreach ( $statuses as $status ) : ?>
17
+ <option value="<?php echo $status ?>" <?php selected( $settings['status'] == $status ) ?>><?php echo CustomerAppointment::statusToString( $status ) ?></option>
18
+ <?php endforeach ?>
19
+ </select>
20
+ </div>
21
+ </div>
22
+ <div class="col-md-5">
23
+ <label><?php esc_attr_e( 'For service', 'bookly' ) ?></label>
24
+ <div class="form-inline">
25
+ <input type="hidden" name="<?php echo $name ?>[services][any]" value="0">
26
+ <input type="checkbox" name="<?php echo $name ?>[services][any]" value="1" <?php checked( @$settings['services']['any'] ) ?>> <span class="bookly-checkbox-text"><?php esc_html_e( 'Any', 'bookly' ) ?></span>
27
+ <ul class="bookly-js-services"
28
+ data-icon-class="glyphicon glyphicon-tag"
29
+ data-txt-select-all="<?php esc_attr_e( 'All services', 'bookly' ) ?>"
30
+ data-txt-all-selected="<?php esc_attr_e( 'All services', 'bookly' ) ?>"
31
+ data-txt-nothing-selected="<?php esc_attr_e( 'No service selected', 'bookly' ) ?>"
32
+ >
33
+ <?php foreach ( $service_dropdown_data as $category_id => $category ): ?>
34
+ <li<?php if ( ! $category_id ) : ?> data-flatten-if-single<?php endif ?>><?php echo esc_html( $category['name'] ) ?>
35
+ <ul>
36
+ <?php foreach ( $category['items'] as $service ) : ?>
37
+ <li
38
+ data-input-name="<?php echo $name ?>[services][ids][]"
39
+ data-value="<?php echo $service['id'] ?>"
40
+ data-selected="<?php echo (int) isset( $settings['services']['ids'] ) && in_array( $service['id'], @$settings['services']['ids'] ) ?>"
41
+ >
42
+ <?php echo esc_html( $service['title'] ) ?>
43
+ </li>
44
+ <?php endforeach ?>
45
+ </ul>
46
+ </li>
47
+ <?php endforeach ?>
48
+ </ul>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ <div class="row">
53
+ <div class="col-md-6 bookly-margin-left-sm bookly-margin-bottom-sm">
54
+ <label for="notification_<?php echo ++ $unique ?>_send_1"><?php esc_attr_e( 'Send', 'bookly' ) ?></label>
55
+ <div class="form-inline bookly-margin-bottom-sm">
56
+ <div class="form-group">
57
+ <label><input type="radio" name="<?php echo $name ?>[option]" value="1" checked></label> <?php esc_attr_e( 'Instantly', 'bookly' ) ?>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="form-inline bookly-margin-bottom-sm">
62
+ <div class="form-group">
63
+ <label><input type="radio" name="<?php echo $name ?>[option]" value="2" <?php checked( @$settings['option'] == 2 ) ?>></label>
64
+ <select class="form-control" name="<?php echo $name ?>[offset_hours]">
65
+ <?php foreach ( array_merge( range( 1, 24 ), range( 48, 336, 24 ), array( 504, 672 ) ) as $hour ) : ?>
66
+ <option value="<?php echo $hour ?>" <?php selected( @$settings['offset_hours'], $hour ) ?>><?php echo \Bookly\Lib\Utils\DateTime::secondsToInterval( $hour * HOUR_IN_SECONDS ) ?>&nbsp;<?php esc_attr_e( 'after', 'bookly' ) ?></option>
67
+ <?php endforeach ?>
68
+ </select>
69
+ <input type="hidden" name="<?php echo $name ?>[perform]" value="after">
70
+ </div>
71
+ </div>
72
+
73
+ <div class="form-inline">
74
+ <div class="form-group">
75
+ <label><input type="radio" name="<?php echo $name ?>[option]" value="3" <?php checked( @$settings['option'] == 3 ) ?>></label>
76
+ <select class="form-control" name="<?php echo $name ?>[offset_bidirectional_hours]">
77
+ <option value="0"><?php esc_attr_e( 'on the same day', 'bookly' ) ?></option>
78
+ <?php foreach ( array_merge( range( 24, 336, 24 ), array( 504, 672 ) ) as $hour ) : ?>
79
+ <option value="<?php echo $hour ?>" <?php selected( @$settings['offset_bidirectional_hours'], $hour ) ?>><?php echo \Bookly\Lib\Utils\DateTime::secondsToInterval( $hour * HOUR_IN_SECONDS ) ?>&nbsp;<?php esc_attr_e( 'after', 'bookly' ) ?></option>
80
+ <?php endforeach ?>
81
+ </select>
82
+ <?php esc_attr_e( 'at', 'bookly' ) ?>
83
+ <select class="form-control" name="<?php echo $name ?>[at_hour]">
84
+ <?php foreach ( range( 0, 23 ) as $hour ) : ?>
85
+ <option value="<?php echo $hour ?>" <?php selected( @$settings['at_hour'], $hour ) ?>><?php echo \Bookly\Lib\Utils\DateTime::buildTimeString( $hour * HOUR_IN_SECONDS, false ) ?></option>
86
+ <?php endforeach ?>
87
+ </select>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ <?php $self::putInCache( 'unique', ++$unique ) ?>
backend/components/sms/custom/templates/existing_event_with_date.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Lib\DataHolders\Notification\Settings;
3
+
4
+ $set = Settings::SET_EXISTING_EVENT_WITH_DATE;
5
+ $name = 'notification[' . $id . '][settings][' . $set . ']';
6
+ ?>
7
+ <div class="bookly-js-settings bookly-js-<?php echo $set ?>">
8
+ <div class="col-md-6">
9
+ <label for="notification_<?php echo ++$unique ?>_send_2"><?php esc_attr_e( 'Send', 'bookly' ) ?></label>
10
+ <div class="form-inline">
11
+ <div class="form-group">
12
+ <select class="form-control" name="<?php echo $name ?>[offset_bidirectional_hours]" id="notification_<?php echo $unique ?>_send_2">
13
+ <?php foreach ( array_merge( array( -672, -504 ), range( -336, -24, 24 ) ) as $hour ) : ?>
14
+ <option value="<?php echo $hour ?>" <?php selected( @$settings['offset_bidirectional_hours'], $hour ) ?>><?php echo \Bookly\Lib\Utils\DateTime::secondsToInterval( abs( $hour ) * HOUR_IN_SECONDS ) ?>&nbsp;<?php esc_attr_e( 'before', 'bookly' ) ?></option>
15
+ <?php endforeach ?>
16
+ <option value="0" <?php selected( @$settings['offset_bidirectional_hours'], 0 ) ?>><?php esc_attr_e( 'on the same day', 'bookly' ) ?></option>
17
+ <?php foreach ( array_merge( range( 24, 336, 24 ), array( 504, 672 ) ) as $hour ) : ?>
18
+ <option value="<?php echo $hour ?>" <?php selected( @$settings['offset_bidirectional_hours'], $hour ) ?>><?php echo \Bookly\Lib\Utils\DateTime::secondsToInterval( $hour * HOUR_IN_SECONDS ) ?>&nbsp;<?php esc_attr_e( 'after', 'bookly' ) ?></option>
19
+ <?php endforeach ?>
20
+ </select>
21
+ <?php esc_attr_e( 'at', 'bookly' ) ?>
22
+ <select class="form-control" name="<?php echo $name ?>[at_hour]">
23
+ <?php foreach ( range( 0, 23 ) as $hour ) : ?>
24
+ <option value="<?php echo $hour ?>" <?php selected( @$settings['at_hour'], $hour ) ?>><?php echo \Bookly\Lib\Utils\DateTime::buildTimeString( $hour * HOUR_IN_SECONDS, false ) ?></option>
25
+ <?php endforeach ?>
26
+ </select>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ <?php $self::putInCache( 'unique', ++$unique ) ?>
backend/components/sms/custom/templates/existing_event_with_date_and_time.php ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Lib\Entities\CustomerAppointment;
3
+ use Bookly\Lib\DataHolders\Notification\Settings;
4
+
5
+ $set = Settings::SET_EXISTING_EVENT_WITH_DATE_AND_TIME;
6
+ $name = 'notification[' . $id . '][settings][' . $set . ']';
7
+ ?>
8
+ <div class="bookly-js-settings bookly-js-<?php echo $set ?>">
9
+ <div class="row">
10
+ <div class="col-md-3">
11
+ <div class="form-group">
12
+ <label for="notification_<?php echo ++ $unique ?>_status_1"><?php esc_attr_e( 'With status', 'bookly' ) ?></label>
13
+ <select class="form-control" name="<?php echo $name ?>[status]" id="notification_<?php echo $unique ?>_status_1">
14
+ <option value="any"><?php esc_attr_e( 'Any', 'bookly' ) ?></option>
15
+ <?php foreach ( $statuses as $status ) : ?>
16
+ <option value="<?php echo $status ?>" <?php selected( $settings['status'] == $status ) ?>><?php echo CustomerAppointment::statusToString( $status ) ?></option>
17
+ <?php endforeach ?>
18
+ </select>
19
+ </div>
20
+ </div>
21
+ <div class="col-md-5">
22
+ <label><?php esc_attr_e( 'For service', 'bookly' ) ?></label>
23
+ <div class="form-inline">
24
+ <input type="hidden" name="<?php echo $name ?>[services][any]" value="0">
25
+ <input type="checkbox" name="<?php echo $name ?>[services][any]" value="1" <?php checked( @$settings['services']['any'] ) ?>> <span class="bookly-checkbox-text"><?php esc_html_e( 'Any', 'bookly' ) ?></span>
26
+ <ul class="bookly-js-services"
27
+ data-icon-class="glyphicon glyphicon-tag"
28
+ data-txt-select-all="<?php esc_attr_e( 'All services', 'bookly' ) ?>"
29
+ data-txt-all-selected="<?php esc_attr_e( 'All services', 'bookly' ) ?>"
30
+ data-txt-nothing-selected="<?php esc_attr_e( 'No service selected', 'bookly' ) ?>"
31
+ >
32
+ <?php foreach ( $service_dropdown_data as $category_id => $category ): ?>
33
+ <li<?php if ( ! $category_id ) : ?> data-flatten-if-single<?php endif ?>><?php echo esc_html( $category['name'] ) ?>
34
+ <ul>
35
+ <?php foreach ( $category['items'] as $service ) : ?>
36
+ <li
37
+ data-input-name="<?php echo $name ?>[services][ids][]"
38
+ data-value="<?php echo $service['id'] ?>"
39
+ data-selected="<?php echo (int) isset( $settings['services']['ids'] ) && in_array( $service['id'], @$settings['services']['ids'] ) ?>"
40
+ >
41
+ <?php echo esc_html( $service['title'] ) ?>
42
+ </li>
43
+ <?php endforeach ?>
44
+ </ul>
45
+ </li>
46
+ <?php endforeach ?>
47
+ </ul>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ <div class="row">
52
+ <div class="col-md-6 bookly-margin-left-sm bookly-margin-bottom-sm">
53
+ <label for="notification_<?php echo ++ $unique ?>_send_1"><?php esc_attr_e( 'Send', 'bookly' ) ?></label>
54
+ <div class="form-inline bookly-margin-bottom-sm">
55
+ <div class="form-group">
56
+ <label><input type="radio" name="<?php echo $name ?>[option]" value="1" checked id="notification_<?php echo $unique ?>_send_1"></label>
57
+ <select class="form-control" name="<?php echo $name ?>[offset_hours]">
58
+ <?php foreach ( array_merge( range( 1, 24 ), range( 48, 336, 24 ), array( 504, 672 ) ) as $hour ) : ?>
59
+ <option value="<?php echo $hour ?>" <?php selected( @$settings['offset_hours'], $hour ) ?>><?php echo \Bookly\Lib\Utils\DateTime::secondsToInterval( $hour * HOUR_IN_SECONDS ) ?></option>
60
+ <?php endforeach ?>
61
+ <option value="43200" <?php selected( @$settings['offset_hours'], 43200 ) ?>>30 <?php esc_attr_e( 'days', 'bookly' ) ?></option>
62
+ </select>
63
+ <select class="form-control" name="<?php echo $name ?>[perform]">
64
+ <option value="before"><?php esc_attr_e( 'before', 'bookly' ) ?></option>
65
+ <option value="after"<?php selected( @$settings['perform'] == 'after' ) ?>><?php esc_attr_e( 'after', 'bookly' ) ?></option>
66
+ </select>
67
+ </div>
68
+ </div>
69
+
70
+ <div class="form-inline">
71
+ <div class="form-group">
72
+ <label><input type="radio" name="<?php echo $name ?>[option]" value="2" <?php checked( @$settings['option'] == 2 ) ?>></label>
73
+ <select class="form-control" name="<?php echo $name ?>[offset_bidirectional_hours]">
74
+ <?php foreach ( array_merge( array( - 672, - 504 ), range( - 336, - 24, 24 ) ) as $hour ) : ?>
75
+ <option value="<?php echo $hour ?>" <?php selected( @$settings['offset_bidirectional_hours'], $hour ) ?>><?php echo \Bookly\Lib\Utils\DateTime::secondsToInterval( abs( $hour ) * HOUR_IN_SECONDS ) ?>&nbsp;<?php esc_attr_e( 'before', 'bookly' ) ?></option>
76
+ <?php endforeach ?>
77
+ <option value="0" <?php selected( @$settings['offset_bidirectional_hours'], 0 ) ?>><?php esc_attr_e( 'on the same day', 'bookly' ) ?></option>
78
+ <?php foreach ( array_merge( range( 24, 336, 24 ), array( 504, 672 ) ) as $hour ) : ?>
79
+ <option value="<?php echo $hour ?>" <?php selected( @$settings['offset_bidirectional_hours'], $hour ) ?>><?php echo \Bookly\Lib\Utils\DateTime::secondsToInterval( $hour * HOUR_IN_SECONDS ) ?>&nbsp;<?php esc_attr_e( 'after', 'bookly' ) ?></option>
80
+ <?php endforeach ?>
81
+ </select>
82
+ <?php esc_attr_e( 'at', 'bookly' ) ?>
83
+ <select class="form-control" name="<?php echo $name ?>[at_hour]">
84
+ <?php foreach ( range( 0, 23 ) as $hour ) : ?>
85
+ <option value="<?php echo $hour ?>" <?php selected( @$settings['at_hour'], $hour ) ?>><?php echo \Bookly\Lib\Utils\DateTime::buildTimeString( $hour * HOUR_IN_SECONDS, false ) ?></option>
86
+ <?php endforeach ?>
87
+ </select>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ <?php $self::putInCache( 'unique', ++$unique ) ?>
backend/components/sms/custom/templates/existing_event_with_date_before.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ use Bookly\Lib\DataHolders\Notification\Settings;
3
+
4
+ $set = Settings::SET_EXISTING_EVENT_WITH_DATE_BEFORE;
5
+ $name = 'notification[' . $id . '][settings][' . $set . ']';
6
+ ?>
7
+ <div class="bookly-js-settings bookly-js-<?php echo $set ?>">
8
+ <div class="col-md-6">
9
+ <label for="notification_<?php echo ++$unique ?>_send_2"><?php esc_attr_e( 'Send', 'bookly' ) ?></label>
10
+ <div class="form-inline">
11
+ <div class="form-group">
12
+ <select class="form-control" name="<?php echo $name ?>[offset_bidirectional_hours]" id="notification_<?php echo $unique ?>_send_2">
13
+ <?php foreach ( array_merge( array( -672, -504 ), range( -336, -24, 24 ) ) as $hour ) : ?>
14
+ <option value="<?php echo $hour ?>" <?php selected( @$settings['offset_bidirectional_hours'], $hour ) ?>><?php echo \Bookly\Lib\Utils\DateTime::secondsToInterval( abs( $hour ) * HOUR_IN_SECONDS ) ?>&nbsp;<?php esc_attr_e( 'before', 'bookly' ) ?></option>
15
+ <?php endforeach ?>
16
+ <option value="0" <?php selected( @$settings['offset_bidirectional_hours'], 0 ) ?>><?php esc_attr_e( 'on the same day', 'bookly' ) ?></option>
17
+ </select>
18
+ <?php esc_attr_e( 'at', 'bookly' ) ?>
19
+ <select class="form-control" name="<?php echo $name ?>[at_hour]">
20
+ <?php foreach ( range( 0, 23 ) as $hour ) : ?>
21
+ <option value="<?php echo $hour ?>" <?php selected( @$settings['at_hour'], $hour ) ?>><?php echo \Bookly\Lib\Utils\DateTime::buildTimeString( $hour * HOUR_IN_SECONDS, false ) ?></option>
22
+ <?php endforeach ?>
23
+ </select>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ <?php $self::putInCache( 'unique', ++$unique ) ?>
backend/components/sms/custom/templates/layout.php ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2
+ /** @var Bookly\Backend\Modules\Notifications\Forms\Notifications $form */
3
+ use Bookly\Lib\DataHolders\Notification\Settings;
4
+ use Bookly\Lib\Entities\Notification;
5
+ use Bookly\Backend\Modules\Notifications\Proxy as NotificationProxy;
6
+ use Bookly\Backend\Components\Sms\Custom;
7
+
8
+ $id = $notification['id'];
9
+ $notification_settings = (array) json_decode( $notification['settings'], true );
10
+ ?>
11
+ <div class="panel panel-default bookly-js-collapse">
12
+ <div class="panel-heading" role="tab">
13
+ <div class="checkbox bookly-margin-remove">
14
+ <label>
15
+ <input name="notification[<?php echo $id ?>][active]" value="0" type="checkbox" checked="checked" class="hidden">
16
+ <input id="<?php echo $id ?>_active" name="notification[<?php echo $id ?>][active]" value="1" type="checkbox" <?php checked( $notification['active'] ) ?>>
17
+ <a href="#collapse_<?php echo $id ?>" class="collapsed panel-title" role="button" data-toggle="collapse" data-parent="#bookly-js-custom-notifications">
18
+ <?php echo $notification['subject'] ?: __( 'Custom notification', 'bookly' ) ?>
19
+ </a>
20
+ </label>
21
+ <button type="button" class="pull-right btn btn-link bookly-js-delete" style="margin-top: -5px" data-notification_id="<?php echo $id ?>" title="<?php esc_attr_e( 'Delete', 'bookly' ) ?>" data-style="zoom-in" data-spinner-size="20" data-spinner-color="#333">
22
+ <span class="ladda-label"><i class="glyphicon glyphicon-trash text-danger"></i></span>
23
+ </button>
24
+ </div>
25
+ </div>
26
+ <div id="collapse_<?php echo $id ?>" class="panel-collapse collapse">
27
+ <div class="panel-body">
28
+ <div class="row">
29
+ <div class="col-md-3">
30
+ <div class="form-group">
31
+ <label for="notification_<?php echo $unique ?>_type"><?php esc_attr_e( 'Type', 'bookly' ) ?></label>
32
+ <select class="form-control" name="notification[<?php echo $id ?>][type]" id="notification_<?php echo $unique ?>_type">
33
+ <optgroup label="<?php esc_attr_e( 'Event notification', 'bookly' ) ?>">
34
+ <option
35
+ value="<?php echo Notification::TYPE_CUSTOMER_APPOINTMENT_STATUS_CHANGED ?>"
36
+ data-set="<?php echo Settings::SET_AFTER_EVENT ?>"
37
+ data-to='["customer","staff","admin"]'
38
+ data-attach-show='["ics","invoice"]'
39
+ <?php selected( $notification['type'], Notification::TYPE_CUSTOMER_APPOINTMENT_STATUS_CHANGED ) ?>><?php esc_attr_e( 'Status changed', 'bookly' ) ?></option>
40
+ <option
41
+ value="<?php echo Notification::TYPE_CUSTOMER_APPOINTMENT_CREATED ?>"
42
+ data-set="<?php echo Settings::SET_AFTER_EVENT ?>"
43
+ data-to='["customer","staff","admin"]'
44
+ data-attach-show='["ics","invoice"]'
45
+ <?php selected( $notification['type'], Notification::TYPE_CUSTOMER_APPOINTMENT_CREATED ) ?> ><?php esc_attr_e( 'New booking', 'bookly' ) ?></option>
46
+ </optgroup>
47
+ <optgroup label="<?php esc_attr_e( 'Reminder notification', 'bookly' ) ?>">
48
+ <option
49
+ value="<?php echo Notification::TYPE_APPOINTMENT_START_TIME ?>"
50
+ data-set="<?php echo Settings::SET_EXISTING_EVENT_WITH_DATE_AND_TIME ?>"
51
+ data-to='["customer","staff","admin"]'
52
+ data-attach-show='["ics"]'
53
+ <?php selected( $notification['type'], Notification::TYPE_APPOINTMENT_START_TIME ) ?>><?php esc_attr_e( 'Appointment date and time', 'bookly' ) ?></option>
54
+ <option
55
+ value="<?php echo Notification::TYPE_CUSTOMER_BIRTHDAY ?>"
56
+ data-set="<?php echo Settings::SET_EXISTING_EVENT_WITH_DATE ?>"
57
+ data-to='["customer"]'
58
+ data-attach-show='[]'
59
+ <?php selected( $notification['type'], Notification::TYPE_CUSTOMER_BIRTHDAY ) ?>><?php esc_attr_e( 'Customer\'s birthday', 'bookly' ) ?></option>
60
+ <option
61
+ value="<?php echo Notification::TYPE_LAST_CUSTOMER_APPOINTMENT ?>"
62
+ data-set="<?php echo Settings::SET_EXISTING_EVENT_WITH_DATE_AND_TIME ?>"
63
+ data-to='["customer","staff","admin"]'
64
+ data-attach-show='["ics"]'
65
+ <?php selected( $notification['type'], Notification::TYPE_LAST_CUSTOMER_APPOINTMENT ) ?>><?php esc_attr_e( 'Last client\'s appointment', 'bookly' ) ?></option>
66
+ <option
67
+ value="<?php echo Notification::TYPE_STAFF_DAY_AGENDA ?>"
68
+ data-set="<?php echo Settings::SET_EXISTING_EVENT_WITH_DATE_BEFORE ?>"
69
+ data-to='["staff","admin"]'
70
+ data-attach-show='[]'
71
+ <?php selected( $notification['type'], Notification::TYPE_STAFF_DAY_AGENDA ) ?>><?php esc_attr_e( 'Full day agenda', 'bookly' ) ?></option>
72
+ </optgroup>
73
+ </select>
74
+ </div>
75
+ </div>
76
+ <?php foreach ( array( Settings::SET_EXISTING_EVENT_WITH_DATE_AND_TIME, Settings::SET_EXISTING_EVENT_WITH_DATE, Settings::SET_EXISTING_EVENT_WITH_DATE_BEFORE, Settings::SET_AFTER_EVENT ) as $set ) {
77
+ $settings = array_key_exists( $set, $notification_settings ) ? $notification_settings[ $set ] : array();
78
+ Custom\Notification::renderSet( $set, $id, $settings );
79
+ }
80
+ $unique = $self::getFromCache( 'unique' ) ?>
81
+ </div>
82
+
83
+ <div class="row">
84
+ <div class="col-md-6">
85
+ <div class="form-group">
86
+ <label for="notification_<?php echo ++$unique ?>_subject"><?php esc_attr_e( 'Subject', 'bookly' ) ?></label>
87
+ <input type="text" class="form-control" id="notification_<?php echo $unique ?>_subject" name="notification[<?php echo $id ?>][subject]" value="<?php echo esc_attr( $notification['subject'] ) ?>" />
88
+ </div>
89
+ </div>
90
+ </div>
91
+
92
+ <div class="row">
93
+ <div class="col-md-4">
94
+ <div class="form-group">
95
+ <label><?php esc_attr_e( 'Recipient', 'bookly' ) ?></label>
96
+ <br>
97
+ <label class="checkbox-inline">
98
+ <input type="hidden" name="notification[<?php echo $id ?>][to_customer]" value="0">
99
+ <input type="checkbox" name="notification[<?php echo $id ?>][to_customer]" value="1"<?php checked( $notification['to_customer'] ) ?> /> <?php esc_attr_e( 'Client', 'bookly' ) ?>
100
+ </label>
101
+ <label class="checkbox-inline">
102
+ <input type="hidden" name="notification[<?php echo $id ?>][to_staff]" value="0">
103
+ <input type="checkbox" name="notification[<?php echo $id ?>][to_staff]" value="1"<?php checked( $notification['to_staff'] ) ?> /> <?php esc_attr_e( 'Staff', 'bookly' ) ?>
104
+ </label>
105
+ <label class="checkbox-inline">
106
+ <input type="hidden" name="notification[<?php echo $id ?>][to_admin]" value="0">
107
+ <input type="checkbox" name="notification[<?php echo $id ?>][to_admin]" value="1"<?php checked( $notification['to_admin'] ) ?> /> <?php esc_attr_e( 'Administrators', 'bookly' ) ?>
108
+ </label>
109
+ </div>
110
+ </div>
111
+ </div>
112
+
113
+ <?php $form->renderEditor( $id ) ?>
114
+
115
+ <?php NotificationProxy\Invoices::renderAttach( $notification ) ?>
116
+ <div class="form-group">
117
+ <label><?php esc_attr_e( 'Codes', 'bookly' ) ?></label>
118
+ <?php foreach ( Notification::getCustomNotificationTypes() as $notification_type ) :
119
+ $form->renderCodes( $notification_type );
120
+ endforeach ?>
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ <?php $self::putInCache( 'unique', ++$unique ) ?>
backend/components/support/Buttons.php CHANGED
@@ -1,74 +1,75 @@
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
  }
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
+ self::enqueueStyles( array(
22
+ 'backend' => array( 'css/fontawesome-all.min.css' ),
23
+ 'frontend' => array( 'css/ladda.min.css', ),
24
+ ) );
25
+
26
+ self::enqueueScripts( array(
27
+ 'backend' => array( 'js/alert.js' => array( 'jquery' ), ),
28
+ 'frontend' => array(
29
+ 'js/spin.min.js' => array( 'jquery' ),
30
+ 'js/ladda.min.js' => array( 'jquery' ),
31
+ ),
32
+ 'module' => array( 'js/support.js' => array( 'bookly-alert.js', 'bookly-ladda.min.js', ), ),
33
+ ) );
34
+
35
+ wp_localize_script( 'bookly-support.js', 'SupportL10n', array(
36
+ 'csrf_token' => Lib\Utils\Common::getCsrfToken()
37
+ ) );
38
+
39
+ // Documentation link.
40
+ $doc_link = 'http://api.booking-wp-plugin.com/go/' . $page_slug;
41
+
42
+ $days_in_use = (int) ( ( time() - Lib\Plugin::getInstallationTime() ) / DAY_IN_SECONDS );
43
+
44
+ // Whether to show contact us notice or not.
45
+ $show_contact_us_notice = $days_in_use < 7 &&
46
+ ! get_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_contact_us_notice', true ) &&
47
+ ! Notices\CollectStats::needShowCollectStatNotice();
48
+
49
+ // Whether to show feedback notice.
50
+ $show_feedback_notice = $days_in_use >= 7 &&
51
+ ! get_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_feedback_notice', true ) &&
52
+ ! get_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'contact_us_btn_clicked', true );
53
+
54
+ $current_user = wp_get_current_user();
55
+
56
+ $messages = Lib\Entities\Message::query( 'm' )
57
+ ->select( 'm.created, m.subject, m.seen' )
58
+ ->sortBy( 'm.seen, m.message_id' )
59
+ ->order( 'DESC' )
60
+ ->limit( 10 )
61
+ ->fetchArray();
62
+ $messages_new = Lib\Entities\Message::query( 'm' )->where( 'm.seen', '0' )->count();
63
+ $messages_link = Lib\Utils\Common::escAdminUrl( Modules\Messages\Ajax::pageSlug() );
64
+
65
+ static::renderTemplate( 'buttons', compact(
66
+ 'doc_link',
67
+ 'show_contact_us_notice',
68
+ 'show_feedback_notice',
69
+ 'current_user',
70
+ 'messages',
71
+ 'messages_new',
72
+ 'messages_link'
73
+ ) );
74
+ }
75
  }
backend/components/support/ButtonsAjax.php CHANGED
@@ -1,78 +1,88 @@
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
  }
1
+ <?php
2
+ namespace Bookly\Backend\Components\Support;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Backend\Modules;
6
+ use Bookly\Backend\Components\Support\Lib\Urls;
7
+
8
+ /**
9
+ * Class ButtonsAjax
10
+ * @package Bookly\Backend\Components\Support
11
+ */
12
+ class ButtonsAjax extends Lib\Base\Ajax
13
+ {
14
+ /**
15
+ * Send support request.
16
+ */
17
+ public static function sendSupportRequest()
18
+ {
19
+ $name = trim( self::parameter( 'name' ) );
20
+ $email = trim( self::parameter( 'email' ) );
21
+ $msg = trim( self::parameter( 'msg' ) );
22
+
23
+ // Validation.
24
+ if ( $email == '' || $msg == '' ) {
25
+ wp_send_json_error( array( 'message' => __( 'All fields marked with an asterisk (*) are required.', 'bookly' ) ) );
26
+ }
27
+ if ( ! is_email( $email ) ) {
28
+ wp_send_json_error( array(
29
+ 'invalid_email' => true,
30
+ 'message' => __( 'Invalid email.', 'bookly' ),
31
+ ) );
32
+ }
33
+
34
+ $plugins = apply_filters( 'bookly_plugins', array() );
35
+ $message = self::renderTemplate( '_email_to_support', compact( 'name', 'email', 'msg', 'plugins' ), false );
36
+ $headers = array(
37
+ 'Content-Type: text/html; charset=utf-8',
38
+ 'From: ' . get_option( 'bookly_email_sender_name' ) . ' <' . get_option( 'bookly_email_sender' ) . '>',
39
+ 'Reply-To: ' . $name . ' <' . $email . '>'
40
+ );
41
+
42
+ if ( wp_mail( 'support@bookly.info', 'Support Request ' . site_url(), $message, $headers ) ) {
43
+ wp_send_json_success( array( 'message' => __( 'Sent successfully.', 'bookly' ) ) );
44
+ } else {
45
+ wp_send_json_error( array( 'message' => __( 'Error sending support request.', 'bookly' ) ) );
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Dismiss notice for 'Contact Us' button.
51
+ */
52
+ public static function dismissContactUsNotice()
53
+ {
54
+ update_user_meta( get_current_user_id(), 'bookly_dismiss_contact_us_notice', 1 );
55
+
56
+ wp_send_json_success();
57
+ }
58
+
59
+ /**
60
+ * Record click on 'Contact Us' button.
61
+ */
62
+ public static function contactUsBtnClicked()
63
+ {
64
+ update_user_meta( get_current_user_id(), 'bookly_dismiss_contact_us_notice', 1 );
65
+ update_user_meta( get_current_user_id(), 'bookly_contact_us_btn_clicked', 1 );
66
+
67
+ wp_send_json_success();
68
+ }
69
+
70
+ /**
71
+ * Dismiss notice for 'Feedback' button.
72
+ */
73
+ public static function dismissFeedbackNotice()
74
+ {
75
+ update_user_meta( get_current_user_id(), 'bookly_dismiss_feedback_notice', 1 );
76
+
77
+ wp_send_json_success();
78
+ }
79
+
80
+ /**
81
+ * Proceed to feature requests.
82
+ */
83
+ public static function proceedToFeatureRequests()
84
+ {
85
+ update_user_meta( get_current_user_id(), 'bookly_feature_requests_rules_hide', self::parameter( 'hide', 0 ) );
86
+ wp_send_json_success( array( 'target' => Lib\Utils\Common::prepareUrlReferrers( Urls::FEATURES_REQUEST_PAGE, 'notification_bar' ) ) );
87
+ }
88
  }
backend/components/support/lib/Urls.php CHANGED
@@ -1,11 +1,12 @@
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
  }
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 REVIEWS_PAGE = 'https://wordpress.org/plugins/bookly-responsive-appointment-booking-tool/#reviews';
11
+ const FEATURES_REQUEST_PAGE = 'https://support.booking-wp-plugin.com/hc/en-us/community/topics/200189331-Feature-Requests';
12
  }
backend/components/support/resources/js/support.js CHANGED
@@ -1,136 +1,174 @@
1
- jQuery(function ($) {
2
- var $modal = $('#bookly-support-modal'),
3
- $btnContactUs = $('#bookly-contact-us-btn'),
4
- $btnFeedback = $('#bookly-feedback-btn')
5
- ;
6
-
7
- if ($btnContactUs.data('trigger')) {
8
- $btnContactUs
9
- .popover().popover('show')
10
- .next('.popover')
11
- .css({right:22+$btnFeedback.outerWidth()+'px',left:'auto'})
12
- .find('.arrow').removeClass().addClass('popover-arrow').css({right:($btnContactUs.outerWidth()/2)+'px',left:'auto'}).end()
13
- .find('.popover-content button').on('click', function () {
14
- $btnContactUs.popover('hide');
15
- $.ajax({
16
- url : ajaxurl,
17
- type : 'POST',
18
- data : { action : 'bookly_dismiss_contact_us_notice', csrf_token : SupportL10n.csrf_token },
19
- success : function(response) {
20
- $btnContactUs.attr("data-processed", true);
21
- }
22
- });
23
- }).end()
24
- .end()
25
- .on('click', function () {
26
- $btnContactUs.popover('hide');
27
- $.ajax({
28
- url : ajaxurl,
29
- type : 'POST',
30
- data : { action : 'bookly_contact_us_btn_clicked', csrf_token : SupportL10n.csrf_token }
31
- });
32
- });
33
- }
34
-
35
- if ($btnFeedback.data('trigger')) {
36
- $btnFeedback
37
- .popover().popover('show')
38
- .next('.popover')
39
- .css({right:'10px',left:'auto'})
40
- .find('.arrow').removeClass().addClass('popover-arrow').css({right:($btnFeedback.outerWidth()/2)+'px',left:'auto'}).end()
41
- .find('.popover-content').css({overflow:'hidden'})
42
- .find('button').on('click', function () {
43
- $btnFeedback.popover('hide');
44
- $.ajax({
45
- url : ajaxurl,
46
- type : 'POST',
47
- data : { action : 'bookly_dismiss_feedback_notice', csrf_token : SupportL10n.csrf_token }
48
- });
49
- }).end()
50
- .end()
51
- .end()
52
- .on('click', function () {
53
- $btnFeedback.popover('hide');
54
- $.ajax({
55
- url : ajaxurl,
56
- type : 'POST',
57
- data : { action : 'bookly_dismiss_feedback_notice', csrf_token : SupportL10n.csrf_token }
58
- });
59
- });
60
- }
61
-
62
- $('#bookly-support-send').on('click', function (e) {
63
- var $name = $('#bookly-support-name'),
64
- $email = $('#bookly-support-email'),
65
- $msg = $('#bookly-support-msg')
66
- ;
67
-
68
- // Validation.
69
- $email.closest('.form-group').toggleClass('has-error', $email.val() == '');
70
- $msg.closest('.form-group').toggleClass('has-error', $msg.val() == '');
71
-
72
- // Send request.
73
- if ($modal.find('.has-error').length == 0) {
74
- var ladda = Ladda.create(this);
75
- ladda.start();
76
- $.ajax({
77
- url : ajaxurl,
78
- type : 'POST',
79
- data : {
80
- action : 'bookly_send_support_request',
81
- csrf_token : SupportL10n.csrf_token,
82
- name : $name.val(),
83
- email : $email.val(),
84
- msg : $msg.val()
85
- },
86
- dataType : 'json',
87
- success : function (response) {
88
- if (response.success) {
89
- $msg.val('');
90
- $modal.modal('hide');
91
- booklyAlert({success : [response.data.message]});
92
- } else {
93
- booklyAlert({error : [response.data.message]});
94
- if (response.data.invalid_email) {
95
- $email.closest('.form-group').addClass('has-error');
96
- }
97
- }
98
- },
99
- complete : function () {
100
- ladda.stop();
101
- }
102
- });
103
- }
104
- });
105
-
106
- $('#bookly-js-mark-read-all-messages').on('click', function (e) {
107
- e.preventDefault();
108
- var $link = $(this),
109
- ladda = Ladda.create($('#bookly-bell').get(0)),
110
- $dropdown = $link.closest(".dropdown-menu");
111
-
112
- $dropdown.prev().dropdown("toggle");
113
- ladda.start();
114
- $.ajax({
115
- url : ajaxurl,
116
- type : 'POST',
117
- data : {
118
- action: 'bookly_mark_read_all_messages',
119
- csrf_token: SupportL10n.csrf_token
120
- },
121
- dataType : 'json',
122
- success : function (response) {
123
- if (response.success) {
124
- $('.bookly-js-new-messages-count').remove();
125
- $link.closest('li').remove();
126
- $('a', $dropdown).each(function () {
127
- $(this).html($(this).text());
128
- });
129
- }
130
- },
131
- complete : function () {
132
- ladda.stop();
133
- }
134
- });
135
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  });
1
+ jQuery(function ($) {
2
+ var $modal = $('#bookly-support-modal'),
3
+ $btnContactUs = $('#bookly-contact-us-btn'),
4
+ $btnFeedback = $('#bookly-feedback-btn')
5
+ ;
6
+
7
+ if ($btnContactUs.data('trigger')) {
8
+ $btnContactUs
9
+ .popover().popover('show')
10
+ .next('.popover')
11
+ .css({right:22+$btnFeedback.outerWidth()+'px',left:'auto'})
12
+ .find('.arrow').removeClass().addClass('popover-arrow').css({right:($btnContactUs.outerWidth()/2)+'px',left:'auto'}).end()
13
+ .find('.popover-content button').on('click', function () {
14
+ $btnContactUs.popover('hide');
15
+ $.ajax({
16
+ url : ajaxurl,
17
+ type : 'POST',
18
+ data : { action : 'bookly_dismiss_contact_us_notice', csrf_token : SupportL10n.csrf_token },
19
+ success : function(response) {
20
+ $btnContactUs.attr("data-processed", true);
21
+ }
22
+ });
23
+ }).end()
24
+ .end()
25
+ .on('click', function () {
26
+ $btnContactUs.popover('hide');
27
+ $.ajax({
28
+ url : ajaxurl,
29
+ type : 'POST',
30
+ data : { action : 'bookly_contact_us_btn_clicked', csrf_token : SupportL10n.csrf_token }
31
+ });
32
+ });
33
+ }
34
+
35
+ if ($btnFeedback.data('trigger')) {
36
+ $btnFeedback
37
+ .popover().popover('show')
38
+ .next('.popover')
39
+ .css({right:'10px',left:'auto'})
40
+ .find('.arrow').removeClass().addClass('popover-arrow').css({right:($btnFeedback.outerWidth()/2)+'px',left:'auto'}).end()
41
+ .find('.popover-content').css({overflow:'hidden'})
42
+ .find('button').on('click', function () {
43
+ $btnFeedback.popover('hide');
44
+ $.ajax({
45
+ url : ajaxurl,
46
+ type : 'POST',
47
+ data : { action : 'bookly_dismiss_feedback_notice', csrf_token : SupportL10n.csrf_token }
48
+ });
49
+ }).end()
50
+ .end()
51
+ .end()
52
+ .on('click', function () {
53
+ $btnFeedback.popover('hide');
54
+ $.ajax({
55
+ url : ajaxurl,
56
+ type : 'POST',
57
+ data : { action : 'bookly_dismiss_feedback_notice', csrf_token : SupportL10n.csrf_token }
58
+ });
59
+ });
60
+ }
61
+
62
+ $('#bookly-support-send').on('click', function (e) {
63
+ var $name = $('#bookly-support-name'),
64
+ $email = $('#bookly-support-email'),
65
+ $msg = $('#bookly-support-msg')
66
+ ;
67
+
68
+ // Validation.
69
+ $email.closest('.form-group').toggleClass('has-error', $email.val() == '');
70
+ $msg.closest('.form-group').toggleClass('has-error', $msg.val() == '');
71
+
72
+ // Send request.
73
+ if ($modal.find('.has-error').length == 0) {
74
+ var ladda = Ladda.create(this);
75
+ ladda.start();
76
+ $.ajax({
77
+ url : ajaxurl,
78
+ type : 'POST',
79
+ data : {
80
+ action : 'bookly_send_support_request',
81
+ csrf_token : SupportL10n.csrf_token,
82
+ name : $name.val(),
83
+ email : $email.val(),
84
+ msg : $msg.val()
85
+ },
86
+ dataType : 'json',
87
+ success : function (response) {
88
+ if (response.success) {
89
+ $msg.val('');
90
+ $modal.modal('hide');
91
+ booklyAlert({success : [response.data.message]});
92
+ } else {
93
+ booklyAlert({error : [response.data.message]});
94
+ if (response.data.invalid_email) {
95
+ $email.closest('.form-group').addClass('has-error');
96
+ }
97
+ }
98
+ },
99
+ complete : function () {
100
+ ladda.stop();
101
+ }
102
+ });
103
+ }
104
+ });
105
+
106
+ $('#bookly-js-mark-read-all-messages').on('click', function (e) {
107
+ e.preventDefault();
108
+ var $link = $(this),
109
+ ladda = Ladda.create($('#bookly-bell').get(0)),
110
+ $dropdown = $link.closest(".dropdown-menu");
111
+
112
+ $dropdown.prev().dropdown("toggle");
113
+ ladda.start();
114
+ $.ajax({
115
+ url : ajaxurl,
116
+ type : 'POST',
117
+ data : {
118
+ action: 'bookly_mark_read_all_messages',
119
+ csrf_token: SupportL10n.csrf_token
120
+ },
121
+ dataType : 'json',
122
+ success : function (response) {
123
+ if (response.success) {
124
+ $('.bookly-js-new-messages-count').remove();
125
+ $link.closest('li').remove();
126
+ $('a', $dropdown).each(function () {
127
+ $(this).html($(this).text());
128
+ });
129
+ }
130
+ },
131
+ complete : function () {
132
+ ladda.stop();
133
+ }
134
+ });
135
+ });
136
+
137
+ $('[data-action=feature-request]')
138
+ .on('click', function () {
139
+ if ($(this).data('target')) {
140
+ window.open($(this).data('target'), '_blank');
141
+ } else {
142
+ $('#bookly-feature-requests-modal').modal('show');
143
+ }
144
+ });
145
+
146
+ $('.bookly-js-proceed-requests').on('click', function () {
147
+ var $modal = $('#bookly-feature-requests-modal'),
148
+ hide = $('#bookly-js-dont-show-again', $modal).prop('checked')||0,
149
+ ladda = Ladda.create(this);
150
+ ladda.start();
151
+ $.ajax({
152
+ url : ajaxurl,
153
+ type: 'POST',
154
+ data: {
155
+ action : 'bookly_proceed_to_feature_requests',
156
+ csrf_token: SupportL10n.csrf_token,
157
+ hide : hide
158
+ },
159
+ dataType: 'json',
160
+ success : function (response) {
161
+ if (response.success) {
162
+ $modal.modal('hide');
163
+ if (hide) {
164
+ $('[data-action=feature-request]').data('target', response.data.target);
165
+ }
166
+ window.open(response.data.target, '_blank');
167
+ }
168
+ },
169
+ complete: function () {
170
+ ladda.stop();
171
+ }
172
+ });
173
+ });
174
  });
backend/components/support/templates/_email_to_support.php CHANGED
@@ -1,19 +1,19 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
- <!doctype html>
3
- <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>
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly ?>
2
+ <!doctype html>
3
+ <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/components/support/templates/buttons.php CHANGED
@@ -1,86 +1,127 @@
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">
9
- #bookly-tbs .page-header > .popover {
10
- z-index: 1039;
11
- }
12
- </style>
13
- <span class="dropdown">
14
- <button type="button" class="btn btn-default-outline dropdown-toggle ladda-button" data-toggle="dropdown" id="bookly-bell" aria-haspopup="true" aria-expanded="true" data-spinner-size="40" data-style="zoom-in" data-spinner-color="#DDDDDD"><span class="ladda-label"><i class="bookly-icon glyphicon glyphicon-bell"></i></span></button>
15
- <?php if ( $messages_new ) : ?>
16
- <span class="badge bookly-js-new-messages-count"><?php echo $messages_new ?></span>
17
- <?php endif ?>
18
- <ul class="dropdown-menu dropdown-menu-right" aria-labelledby="bookly-bell">
19
- <?php foreach ( $messages as $message ) : ?>
20
- <li><a href="<?php echo $messages_link ?>"><?php echo Utils\DateTime::formatDate( $message['created'] ) . ': ';
21
- if ( $message['seen'] ) :
22
- echo esc_html( $message['subject'] );
23
- else:
24
- echo '<b>' . esc_html( $message['subject'] ) . '</b>';
25
- endif ?>
26
- </a></li>
27
- <?php endforeach ?>
28
- <li role="separator" class="divider"></li>
29
- <li><a href="<?php echo $messages_link ?>"><?php _e( 'Show all notifications', 'bookly' ) ?></a></li>
30
- <?php if ( $messages_new ) : ?>
31
- <li><a href="#" id="bookly-js-mark-read-all-messages"><?php _e( 'Mark all notifications as read', 'bookly' ) ?></a></li>
32
- <?php endif ?>
33
- </ul>
34
- </span>
35
- <a href="<?php echo esc_url( $doc_link ) ?>" target="_blank" id="bookly-help-btn" class="btn btn-default-outline">
36
- <i class="bookly-icon bookly-icon-help"></i><?php _e( 'Documentation', 'bookly' ) ?>
37
- </a>
38
- <a href="#bookly-support-modal" id="bookly-contact-us-btn" class="btn btn-default-outline"
39
- data-processed="false"
40
- data-toggle="modal"
41
- <?php if ( $show_contact_us_notice ) : ?>
42
- data-trigger="manual" data-placement="bottom" data-html="1"
43
- data-content="<?php echo esc_attr( '<button type="button" class="close pull-right bookly-margin-left-sm"><span>&times;</span></button>' . __( 'Need help? Contact us here.', 'bookly' ) ) ?>"
44
- <?php endif ?>
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"
52
- data-content="<?php echo esc_attr( '<button type="button" class="close pull-right bookly-margin-left-sm"><span>&times;</span></button><div class="pull-left">' . __( 'We care about your experience using Bookly!<br/>Leave a review and tell others what you think.', 'bookly' ) . '</div>' ) ?>"
53
- <?php endif ?>
54
- >
55
- <i class="bookly-icon bookly-icon-feedback"></i><?php _e( 'Feedback', 'bookly' ) ?>
56
- </a>
57
-
58
- <div id="bookly-support-modal" class="modal fade text-left" tabindex=-1>
59
- <div class="modal-dialog">
60
- <div class="modal-content">
61
- <div class="modal-header">
62
- <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
63
- <h4 class="modal-title"><?php _e( 'Leave us a message', 'bookly' ) ?></h4>
64
- </div>
65
- <div class="modal-body">
66
- <div class="form-group">
67
- <label for="bookly-support-name" class="control-label"><?php _e( 'Your name', 'bookly' ) ?></label>
68
- <input type="text" id="bookly-support-name" class="form-control" value="<?php echo esc_attr( $current_user->user_firstname . ' ' . $current_user->user_lastname ) ?>" />
69
- </div>
70
- <div class="form-group">
71
- <label for="bookly-support-email" class="control-label"><?php _e( 'Email address', 'bookly' ) ?> <span class="bookly-color-brand-danger">*</span></label>
72
- <input type="text" id="bookly-support-email" class="form-control" value="<?php echo esc_attr( $current_user->user_email ) ?>" />
73
- </div>
74
- <div class="form-group">
75
- <label for="bookly-support-msg" class="control-label"><?php _e( 'How can we help you?', 'bookly' ) ?> <span class="bookly-color-brand-danger">*</span></label>
76
- <textarea id="bookly-support-msg" class="form-control" rows="10"></textarea>
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>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  </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
+ <style type="text/css">
8
+ #bookly-tbs .page-header > .popover {
9
+ z-index: 1039;
10
+ }
11
+ </style>
12
+ <span class="dropdown">
13
+ <button type="button" class="btn btn-default-outline dropdown-toggle ladda-button" data-toggle="dropdown" id="bookly-bell" aria-haspopup="true" aria-expanded="true" data-spinner-size="40" data-style="zoom-in" data-spinner-color="#DDDDDD"><span class="ladda-label"><i class="bookly-icon glyphicon glyphicon-bell"></i></span></button>
14
+ <?php if ( $messages_new ) : ?>
15
+ <span class="badge bookly-js-new-messages-count"><?php echo $messages_new ?></span>
16
+ <?php endif ?>
17
+ <ul class="dropdown-menu dropdown-menu-right" aria-labelledby="bookly-bell">
18
+ <?php foreach ( $messages as $message ) : ?>
19
+ <li><a href="<?php echo $messages_link ?>"><?php echo Utils\DateTime::formatDate( $message['created'] ) . ': ';
20
+ if ( $message['seen'] ) :
21
+ echo esc_html( $message['subject'] );
22
+ else:
23
+ echo '<b>' . esc_html( $message['subject'] ) . '</b>';
24
+ endif ?>
25
+ </a></li>
26
+ <?php endforeach ?>
27
+ <li role="separator" class="divider"></li>
28
+ <li><a href="<?php echo $messages_link ?>"><?php esc_html_e( 'Show all notifications', 'bookly' ) ?></a></li>
29
+ <?php if ( $messages_new ) : ?>
30
+ <li><a href="#" id="bookly-js-mark-read-all-messages"><?php esc_html_e( 'Mark all notifications as read', 'bookly' ) ?></a></li>
31
+ <?php endif ?>
32
+ </ul>
33
+ </span>
34
+ <a href="<?php echo esc_url( $doc_link ) ?>" target="_blank" id="bookly-help-btn" class="btn btn-default-outline">
35
+ <i class="bookly-icon bookly-icon-help"></i><?php esc_html_e( 'Documentation', 'bookly' ) ?>
36
+ </a>
37
+
38
+ <a href="#bookly-support-modal" id="bookly-contact-us-btn" class="btn btn-default-outline"
39
+ data-toggle="modal"
40
+ <?php if ( $show_contact_us_notice ) : ?>
41
+ data-processed="false"
42
+ data-trigger="manual" data-placement="bottom" data-html="1"
43
+ data-content="<?php echo esc_attr( '<button type="button" class="close pull-right bookly-margin-left-sm"><span>&times;</span></button>' . esc_html__( 'Need help? Contact us here.', 'bookly' ) ) ?>"
44
+ <?php endif ?>
45
+ >
46
+ <i class="bookly-icon bookly-icon-contact-us"></i><?php esc_html_e( 'Contact Us', 'bookly' ) ?>
47
+ </a>
48
+
49
+ <?php if ( get_user_meta( get_current_user_id(), 'bookly_feature_requests_rules_hide', true ) ) : ?>
50
+ <a href="<?php echo Utils\Common::prepareUrlReferrers( Urls::FEATURES_REQUEST_PAGE, 'notification_bar' ) ?>" target="_blank" class="btn btn-default-outline bookly-margin-left-sm">
51
+ <i class="bookly-icon"><i class="fas fa-lightbulb"></i></i><?php esc_html_e( 'Feature requests', 'bookly' ) ?>
52
+ </a>
53
+ <?php else : ?>
54
+ <div id="bookly-feature-requests-modal" class="modal fade text-left" tabindex=-1>
55
+ <div class="modal-dialog">
56
+ <div class="modal-content">
57
+ <div class="modal-header">
58
+ <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
59
+ <h4 class="modal-title"><?php esc_html_e( 'Feature requests', 'bookly' ) ?></h4>
60
+ </div>
61
+ <div class="modal-body">
62
+ <p>
63
+ <?php esc_html_e( 'In the Feature Requests section of our Community, you can make suggestions about what you\'d like to see in our future releases.', 'bookly' ) ?>
64
+ </p>
65
+ <p>
66
+ <?php esc_html_e( 'Before you post, please check if the same suggestion has already been made. If so, vote for ideas you like and add a comment with the details about your situation.', 'bookly' ) ?>
67
+ </p>
68
+ <p>
69
+ <?php esc_html_e( 'It\'s much easier for us to address a suggestion if we clearly understand the context of the issue, the problem, and why it matters to you. When commenting or posting, please consider these questions so we can get a better idea of the problem you\'re facing:', 'bookly' ) ?>
70
+ <div>&#9679; <?php esc_html_e( 'What is the issue you\'re struggling with?', 'bookly' ) ?></div>
71
+ <div>&#9679; <?php esc_html_e( 'Where in your workflow do you encounter this issue?', 'bookly' ) ?></div>
72
+ <div>&#9679; <?php esc_html_e( 'Is this something that impacts just you, your whole team, or your customers?', 'bookly' ) ?></div>
73
+ </p>
74
+ <div class="form-inline">
75
+ <input type="checkbox" id="bookly-js-dont-show-again" /> <label class="bookly-checkbox-text" for="bookly-js-dont-show-again"><?php esc_html_e( 'don\'t show this notification again', 'bookly' ) ?></label>
76
+ </div>
77
+ </div>
78
+ <div class="modal-footer">
79
+ <?php Buttons::renderCustom( null, 'bookly-js-proceed-requests btn-success btn-lg', esc_html__( 'Proceed to Feature requests', 'bookly' ) ) ?>
80
+ <?php Buttons::renderCustom( null, 'btn-default btn-lg', esc_html__( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ <button data-action="feature-request" class="btn btn-default-outline bookly-margin-left-sm">
86
+ <i class="bookly-icon"><i class="fas fa-lightbulb"></i></i><?php esc_html_e( 'Feature requests', 'bookly' ) ?>
87
+ </button>
88
+ <?php endif ?>
89
+
90
+ <a href="<?php echo Utils\Common::prepareUrlReferrers( Urls::REVIEWS_PAGE, 'feedback' ) ?>" id="bookly-feedback-btn" target="_blank" class="btn btn-default-outline"
91
+ <?php if ( $show_feedback_notice ) : ?>
92
+ data-trigger="manual" data-placement="bottom" data-html="1"
93
+ data-content="<?php echo esc_attr( '<button type="button" class="close pull-right bookly-margin-left-sm"><span>&times;</span></button><div class="pull-left">' . esc_html__( 'We care about your experience of using Bookly!<br/>Leave a review and tell others what you think.', 'bookly' ) . '</div>' ) ?>"
94
+ <?php endif ?>
95
+ >
96
+ <i class="bookly-icon bookly-icon-feedback"></i><?php esc_html_e( 'Feedback', 'bookly' ) ?>
97
+ </a>
98
+
99
+ <div id="bookly-support-modal" class="modal fade text-left" tabindex=-1>
100
+ <div class="modal-dialog">
101
+ <div class="modal-content">
102
+ <div class="modal-header">
103
+ <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
104
+ <h4 class="modal-title"><?php esc_html_e( 'Leave us a message', 'bookly' ) ?></h4>
105
+ </div>
106
+ <div class="modal-body">
107
+ <div class="form-group">
108
+ <label for="bookly-support-name" class="control-label"><?php esc_html_e( 'Your name', 'bookly' ) ?></label>
109
+ <input type="text" id="bookly-support-name" class="form-control" value="<?php echo esc_attr( $current_user->user_firstname . ' ' . $current_user->user_lastname ) ?>" />
110
+ </div>
111
+ <div class="form-group">
112
+ <label for="bookly-support-email" class="control-label"><?php esc_html_e( 'Email address', 'bookly' ) ?> <span class="bookly-color-brand-danger">*</span></label>
113
+ <input type="text" id="bookly-support-email" class="form-control" value="<?php echo esc_attr( $current_user->user_email ) ?>" />
114
+ </div>
115
+ <div class="form-group">
116
+ <label for="bookly-support-msg" class="control-label"><?php esc_html_e( 'How can we help you?', 'bookly' ) ?> <span class="bookly-color-brand-danger">*</span></label>
117
+ <textarea id="bookly-support-msg" class="form-control" rows="10"></textarea>
118
+ </div>
119
+ </div>
120
+ <div class="modal-footer">
121
+ <?php Inputs::renderCsrf() ?>
122
+ <?php Buttons::renderCustom( 'bookly-support-send', 'btn-success btn-lg', esc_html__( 'Send', 'bookly' ) ) ?>
123
+ <?php Buttons::renderCustom( null, 'btn-default btn-lg', esc_html__( 'Cancel', 'bookly' ), array( 'data-dismiss' => 'modal' ) ) ?>
124
+ </div>
125
+ </div>
126
+ </div>
127
  </div>
backend/components/tiny_mce/Tools.php CHANGED
@@ -1,58 +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
- }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,18 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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/components/tiny_mce/templates/bookly_form.php CHANGED
@@ -1,424 +1,425 @@
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>
12
- </td>
13
- <td>
14
- <select id="bookly-select-category">
15
- <option value=""><?php _e( 'Select category', 'bookly' ) ?></option>
16
- </select>
17
- <div><label><input type="checkbox" id="bookly-hide-categories" /><?php _e( 'Hide this field', 'bookly' ) ?></label></div>
18
- </td>
19
- </tr>
20
- <tr>
21
- <td>
22
- <label for="bookly-select-service"><?php _e( 'Default value for service select', 'bookly' ) ?></label>
23
- </td>
24
- <td>
25
- <select id="bookly-select-service">
26
- <option value=""><?php _e( 'Select service', 'bookly' ) ?></option>
27
- </select>
28
- <div><label><input type="checkbox" id="bookly-hide-services" /><?php _e( 'Hide this field', 'bookly' ) ?></label></div>
29
- <i><?php _e( 'Please be aware that a value in this field is required in the frontend. If you choose to hide this field, please be sure to select a default value for it', 'bookly' ) ?></i>
30
- </td>
31
- </tr>
32
- <tr>
33
- <td>
34
- <label for="bookly-select-employee"><?php _e( 'Default value for employee select', 'bookly' ) ?></label>
35
- </td>
36
- <td>
37
- <select class="bookly-select-mobile" id="bookly-select-employee">
38
- <option value=""><?php _e( 'Any', 'bookly' ) ?></option>
39
- </select>
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>
47
- </td>
48
- <td>
49
- <label><input type="checkbox" id="bookly-hide-date" /><?php _e( 'Hide this block', 'bookly' ) ?></label>
50
- </td>
51
- </tr>
52
- <tr>
53
- <td>
54
- <label for="bookly-hide-week-days"><?php _e( 'Week days', 'bookly' ) ?></label>
55
- </td>
56
- <td>
57
- <label><input type="checkbox" id="bookly-hide-week-days" /><?php _e( 'Hide this block', 'bookly' ) ?></label>
58
- </td>
59
- </tr>
60
- <tr>
61
- <td>
62
- <label for="bookly-hide-time-range"><?php _e( 'Time range', 'bookly' ) ?></label>
63
- </td>
64
- <td>
65
- <label><input type="checkbox" id="bookly-hide-time-range" /><?php _e( 'Hide this block', 'bookly' ) ?></label>
66
- </td>
67
- </tr>
68
- <tr>
69
- <td></td>
70
- <td>
71
- <input class="button button-primary" id="bookly-insert-shortcode" type="submit" value="<?php _e( 'Insert', 'bookly' ) ?>" />
72
- </td>
73
- </tr>
74
- </table>
75
- </form>
76
- </div>
77
- <style type="text/css">
78
- #bookly-shortcode-form { margin-top: 15px; }
79
- #bookly-shortcode-form table { width: 100%; }
80
- #bookly-shortcode-form table td select { width: 100%; margin-bottom: 5px; }
81
- .bookly-media-icon {
82
- display: inline-block;
83
- width: 16px;
84
- height: 16px;
85
- vertical-align: text-top;
86
- margin: 0 2px;
87
- background: url("<?php echo plugins_url( 'resources/images/calendar.png', __DIR__ ) ?>") 0 0 no-repeat;
88
- }
89
- #TB_overlay { z-index: 100001 !important; }
90
- #TB_window { z-index: 100002 !important; }
91
- </style>
92
- <script type="text/javascript">
93
- jQuery(function ($) {
94
- var $select_location = $('#bookly-select-location'),
95
- $select_category = $('#bookly-select-category'),
96
- $select_service = $('#bookly-select-service'),
97
- $select_employee = $('#bookly-select-employee'),
98
- $hide_locations = $('#bookly-hide-locations'),
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'),
106
- $hide_week_days = $('#bookly-hide-week-days'),
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'] ) ?>,
114
- staff = <?php echo json_encode( $casest['staff'] ) ?>
115
- ;
116
-
117
- $add_button.on('click', function () {
118
- window.parent.tb_show(<?php echo json_encode( __( 'Insert Appointment Booking Form', 'bookly' ) ) ?>, this.href);
119
- window.setTimeout(function(){
120
- $('#TB_window').css({
121
- 'overflow-x': 'auto',
122
- 'overflow-y': 'hidden'
123
- });
124
- },100);
125
- });
126
- // insert data into select
127
- function setSelect($select, data, value) {
128
- // reset select
129
- $('option:not([value=""])', $select).remove();
130
- // and fill the new data
131
- var docFragment = document.createDocumentFragment();
132
-
133
- function valuesToArray(obj) {
134
- return Object.keys(obj).map(function (key) { return obj[key]; });
135
- }
136
-
137
- function compare(a, b) {
138
- if (parseInt(a.pos) < parseInt(b.pos))
139
- return -1;
140
- if (parseInt(a.pos) > parseInt(b.pos))
141
- return 1;
142
- return 0;
143
- }
144
-
145
- // sort select by position
146
- data = valuesToArray(data).sort(compare);
147
-
148
- $.each(data, function(key, object) {
149
- var option = document.createElement('option');
150
- option.value = object.id;
151
- option.text = object.name;
152
- docFragment.appendChild(option);
153
- });
154
- $select.append(docFragment);
155
- // set default value of select
156
- $select.val(value);
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) {
169
- if (services[s_id].category_id == category_id) {
170
- _staff[id] = staff_member;
171
- return false;
172
- }
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) {
218
- if ($.inArray(parseInt(id), category_ids) > -1) {
219
- _categories[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);
236
- }
237
-
238
- // Location select change
239
- $select_location.on('change', function () {
240
- var location_id = this.value,
241
- category_id = $select_category.val(),
242
- service_id = $select_service.val(),
243
- staff_id = $select_employee.val()
244
- ;
245
-
246
- // Validate selected values.
247
- if (location_id != '') {
248
- if (staff_id != '' && !locations[location_id].staff.hasOwnProperty(staff_id)) {
249
- staff_id = '';
250
- }
251
- if (service_id != '') {
252
- var valid = false;
253
- $.each(locations[location_id].staff, function(id) {
254
- if (staff[id].services.hasOwnProperty(service_id)) {
255
- valid = true;
256
- return false;
257
- }
258
- });
259
- if (!valid) {
260
- service_id = '';
261
- }
262
- }
263
- if (category_id != '') {
264
- var valid = false;
265
- $.each(locations[location_id].staff, function(id) {
266
- $.each(staff[id].services, function(s_id) {
267
- if (services[s_id].category_id == category_id) {
268
- valid = true;
269
- return false;
270
- }
271
- });
272
- if (valid) {
273
- return false;
274
- }
275
- });
276
- if (!valid) {
277
- category_id = '';
278
- }
279
- }
280
- }
281
- setSelects(location_id, category_id, service_id, staff_id);
282
- });
283
-
284
- // Category select change
285
- $select_category.on('change', function () {
286
- var location_id = $select_location.val(),
287
- category_id = this.value,
288
- service_id = $select_service.val(),
289
- staff_id = $select_employee.val()
290
- ;
291
-
292
- // Validate selected values.
293
- if (category_id != '') {
294
- if (service_id != '') {
295
- if (services[service_id].category_id != category_id) {
296
- service_id = '';
297
- }
298
- }
299
- if (staff_id != '') {
300
- var valid = false;
301
- $.each(staff[staff_id].services, function(id) {
302
- if (services[id].category_id == category_id) {
303
- valid = true;
304
- return false;
305
- }
306
- });
307
- if (!valid) {
308
- staff_id = '';
309
- }
310
- }
311
- }
312
- setSelects(location_id, category_id, service_id, staff_id);
313
- });
314
-
315
- // Service select change
316
- $select_service.on('change', function () {
317
- var location_id = $select_location.val(),
318
- category_id = '',
319
- service_id = this.value,
320
- staff_id = $select_employee.val()
321
- ;
322
-
323
- // Validate selected values.
324
- if (service_id != '') {
325
- if (staff_id != '' && !staff[staff_id].services.hasOwnProperty(service_id)) {
326
- staff_id = '';
327
- }
328
- }
329
- setSelects(location_id, category_id, service_id, staff_id);
330
- if (service_id) {
331
- $select_category.val(services[service_id].category_id);
332
- }
333
- });
334
-
335
- // Staff select change
336
- $select_employee.on('change', function() {
337
- var location_id = $select_location.val(),
338
- category_id = $select_category.val(),
339
- service_id = $select_service.val(),
340
- staff_id = this.value
341
- ;
342
-
343
- setSelects(location_id, category_id, service_id, staff_id);
344
- });
345
-
346
- // Set up draft selects.
347
- setSelect($select_location, locations);
348
- setSelect($select_category, categories);
349
- setSelect($select_service, services);
350
- setSelect($select_employee, staff);
351
-
352
- $insert.on('click', function (e) {
353
- e.preventDefault();
354
-
355
- var insert = '[bookly-form';
356
- var hide = [];
357
- if ($select_location.val()) {
358
- insert += ' location_id="' + $select_location.val() + '"';
359
- }
360
- if ($select_category.val()) {
361
- insert += ' category_id="' + $select_category.val() + '"';
362
- }
363
- if ($hide_locations.is(':checked')) {
364
- hide.push('locations');
365
- }
366
- if ($hide_categories.is(':checked')) {
367
- hide.push('categories');
368
- }
369
- if ($select_service.val()) {
370
- insert += ' service_id="' + $select_service.val() + '"';
371
- }
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
- }
387
- if ($hide_staff.is(':checked')) {
388
- hide.push('staff_members');
389
- }
390
- if ($hide_date.is(':checked')) {
391
- hide.push('date')
392
- }
393
- if ($hide_week_days.is(':checked')) {
394
- hide.push('week_days')
395
- }
396
- if ($hide_time_range.is(':checked')) {
397
- hide.push('time_range');
398
- }
399
- if (hide.length > 0) {
400
- insert += ' hide="' + hide.join() + '"';
401
- }
402
- insert += ']';
403
-
404
- window.send_to_editor(insert);
405
-
406
- $select_location.val('');
407
- $select_category.val('');
408
- $select_service.val('');
409
- $select_employee.val('');
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);
417
- $hide_time_range.prop('checked', false);
418
- $hide_number_of_persons.prop('checked', true);
419
-
420
- window.parent.tb_remove();
421
- return false;
422
- });
423
- });
 
424
  </script>
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 esc_html_e( 'Default value for category select', 'bookly' ) ?></label>
12
+ </td>
13
+ <td>
14
+ <select id="bookly-select-category">
15
+ <option value=""><?php esc_html_e( 'Select category', 'bookly' ) ?></option>
16
+ </select>
17
+ <div><label><input type="checkbox" id="bookly-hide-categories" /><?php esc_html_e( 'Hide this field', 'bookly' ) ?></label></div>
18
+ </td>
19
+ </tr>
20
+ <tr>
21
+ <td>
22
+ <label for="bookly-select-service"><?php esc_html_e( 'Default value for service select', 'bookly' ) ?></label>
23
+ </td>
24
+ <td>
25
+ <select id="bookly-select-service">
26
+ <option value=""><?php esc_html_e( 'Select service', 'bookly' ) ?></option>
27
+ </select>
28
+ <div><label><input type="checkbox" id="bookly-hide-services" /><?php esc_html_e( 'Hide this field', 'bookly' ) ?></label></div>
29
+ <i><?php esc_html_e( 'Please be aware that a value in this field is required in the frontend. If you choose to hide this field, please be sure to select a default value for it', 'bookly' ) ?></i>
30
+ </td>
31
+ </tr>
32
+ <tr>
33
+ <td>
34
+ <label for="bookly-select-employee"><?php esc_html_e( 'Default value for employee select', 'bookly' ) ?></label>
35
+ </td>
36
+ <td>
37
+ <select class="bookly-select-mobile" id="bookly-select-employee">
38
+ <option value=""><?php esc_html_e( 'Any', 'bookly' ) ?></option>
39
+ </select>
40
+ <div><label><input type="checkbox" id="bookly-hide-employee" /><?php esc_html_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 esc_html_e( 'Date', 'bookly' ) ?></label>
47
+ </td>
48
+ <td>
49
+ <label><input type="checkbox" id="bookly-hide-date" /><?php esc_html_e( 'Hide this block', 'bookly' ) ?></label>
50
+ </td>
51
+ </tr>
52
+ <tr>
53
+ <td>
54
+ <label for="bookly-hide-week-days"><?php esc_html_e( 'Week days', 'bookly' ) ?></label>
55
+ </td>
56
+ <td>
57
+ <label><input type="checkbox" id="bookly-hide-week-days" /><?php esc_html_e( 'Hide this block', 'bookly' ) ?></label>
58
+ </td>
59
+ </tr>
60
+ <tr>
61
+ <td>
62
+ <label for="bookly-hide-time-range"><?php esc_html_e( 'Time range', 'bookly' ) ?></label>
63
+ </td>
64
+ <td>
65
+ <label><input type="checkbox" id="bookly-hide-time-range" /><?php esc_html_e( 'Hide this block', 'bookly' ) ?></label>
66
+ </td>
67
+ </tr>
68
+ <tr>
69
+ <td></td>
70
+ <td>
71
+ <button class="button button-primary bookly-js-insert-shortcode" type="button"><?php esc_html_e( 'Insert', 'bookly' ) ?></button>
72
+ </td>
73
+ </tr>
74
+ </table>
75
+ </form>
76
+ </div>
77
+ <style type="text/css">
78
+ #bookly-shortcode-form { margin-top: 15px; }
79
+ #bookly-shortcode-form table { width: 100%; }
80
+ #bookly-shortcode-form table td select { width: 100%; margin-bottom: 5px; }
81
+ .bookly-media-icon {
82
+ display: inline-block;
83
+ width: 16px;
84
+ height: 16px;
85
+ vertical-align: text-top;
86
+ margin: 0 2px;
87
+ background: url("<?php echo plugins_url( 'resources/images/calendar.png', __DIR__ ) ?>") 0 0 no-repeat;
88
+ }
89
+ #TB_overlay { z-index: 100001 !important; }
90
+ #TB_window { z-index: 100002 !important; }
91
+ </style>
92
+ <script type="text/javascript">
93
+ jQuery(function ($) {
94
+ var $form = $('#bookly-shortcode-form'),
95
+ $select_location = $('#bookly-select-location',$form),
96
+ $select_category = $('#bookly-select-category',$form),
97
+ $select_service = $('#bookly-select-service',$form),
98
+ $select_employee = $('#bookly-select-employee',$form),
99
+ $hide_locations = $('#bookly-hide-locations',$form),
100
+ $hide_categories = $('#bookly-hide-categories',$form),
101
+ $hide_services = $('#bookly-hide-services',$form),
102
+ $hide_staff = $('#bookly-hide-employee',$form),
103
+ $hide_service_duration = $('#bookly-hide-service-duration',$form),
104
+ $hide_number_of_persons = $('#bookly-hide-number-of-persons',$form),
105
+ $hide_quantity = $('#bookly-hide-quantity',$form),
106
+ $hide_date = $('#bookly-hide-date',$form),
107
+ $hide_week_days = $('#bookly-hide-week-days',$form),
108
+ $hide_time_range = $('#bookly-hide-time-range',$form),
109
+ $add_button = $('#add-bookly-form'),
110
+ $insert = $('button.bookly-js-insert-shortcode', $form),
111
+ location_custom = <?php echo (int) Bookly\Lib\Proxy\Locations::servicesPerLocationAllowed() ?>,
112
+ locations = <?php echo json_encode( $casest['locations'] ) ?>,
113
+ categories = <?php echo json_encode( $casest['categories'] ) ?>,
114
+ services = <?php echo json_encode( $casest['services'] ) ?>,
115
+ staff = <?php echo json_encode( $casest['staff'] ) ?>
116
+ ;
117
+
118
+ $add_button.on('click', function () {
119
+ window.parent.tb_show(<?php echo json_encode( __( 'Insert Appointment Booking Form', 'bookly' ) ) ?>, this.href);
120
+ window.setTimeout(function(){
121
+ $('#TB_window').css({
122
+ 'overflow-x': 'auto',
123
+ 'overflow-y': 'hidden'
124
+ });
125
+ },100);
126
+ });
127
+ // insert data into select
128
+ function setSelect($select, data, value) {
129
+ // reset select
130
+ $('option:not([value=""])', $select).remove();
131
+ // and fill the new data
132
+ var docFragment = document.createDocumentFragment();
133
+
134
+ function valuesToArray(obj) {
135
+ return Object.keys(obj).map(function (key) { return obj[key]; });
136
+ }
137
+
138
+ function compare(a, b) {
139
+ if (parseInt(a.pos) < parseInt(b.pos))
140
+ return -1;
141
+ if (parseInt(a.pos) > parseInt(b.pos))
142
+ return 1;
143
+ return 0;
144
+ }
145
+
146
+ // sort select by position
147
+ data = valuesToArray(data).sort(compare);
148
+
149
+ $.each(data, function(key, object) {
150
+ var option = document.createElement('option');
151
+ option.value = object.id;
152
+ option.text = object.name;
153
+ docFragment.appendChild(option);
154
+ });
155
+ $select.append(docFragment);
156
+ // set default value of select
157
+ $select.val(value);
158
+ }
159
+
160
+ function setSelects(location_id, category_id, service_id, staff_id) {
161
+ var _location_id = (location_custom && location_id) ? location_id : 0
162
+ var _staff = {}, _services = {}, _categories = {}, _nop = {}, _max_capacity = null, _min_capacity = null;
163
+ $.each(staff, function(id, staff_member) {
164
+ if (!location_id || locations[location_id].staff.hasOwnProperty(id)) {
165
+ if (!service_id) {
166
+ if (!category_id) {
167
+ _staff[id] = staff_member;
168
+ } else {
169
+ $.each(staff_member.services, function(s_id) {
170
+ if (services[s_id].category_id == category_id) {
171
+ _staff[id] = staff_member;
172
+ return false;
173
+ }
174
+ });
175
+ }
176
+ } else if (staff_member.services.hasOwnProperty(service_id)) {
177
+ // var _location_id = staff_member.services[service_id].locations.hasOwnProperty(location_id) ? location_id : 0;
178
+ if (staff_member.services[service_id].locations.hasOwnProperty(_location_id)) {
179
+ if ( staff_member.services[service_id].locations[_location_id].price != null) {
180
+ _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;
181
+ _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;
182
+ _staff[id] = {
183
+ id : id,
184
+ name : staff_member.name + ' (' + staff_member.services[service_id].locations[_location_id].price + ')',
185
+ pos : staff_member.pos
186
+ };
187
+ } else {
188
+ _staff[id] = {
189
+ id : id,
190
+ name : staff_member.name,
191
+ pos : staff_member.pos
192
+ };
193
+ }
194
+ }
195
+ }
196
+ }
197
+ });
198
+ if (!location_id) {
199
+ _categories = categories;
200
+ $.each(services, function(id, service) {
201
+ if (!category_id || service.category_id == category_id) {
202
+ if (!staff_id || staff[staff_id].services.hasOwnProperty(id)) {
203
+ _services[id] = service;
204
+ }
205
+ }
206
+ });
207
+ } else {
208
+ var category_ids = [],
209
+ service_ids = [];
210
+ $.each(staff, function (st_id) {
211
+ $.each(staff[st_id].services, function (s_id) {
212
+ if (staff[st_id].services[s_id].locations.hasOwnProperty(_location_id)) {
213
+ category_ids.push(services[s_id].category_id);
214
+ service_ids.push(s_id);
215
+ }
216
+ });
217
+ });
218
+ $.each(categories, function(id, category) {
219
+ if ($.inArray(parseInt(id), category_ids) > -1) {
220
+ _categories[id] = category;
221
+ }
222
+ });
223
+ $.each(services, function(id, service) {
224
+ if ($.inArray(id, service_ids) > -1) {
225
+ if (!category_id || service.category_id == category_id) {
226
+ if (!staff_id || staff[staff_id].services.hasOwnProperty(id)) {
227
+ _services[id] = service;
228
+ }
229
+ }
230
+ }
231
+ });
232
+ }
233
+
234
+ setSelect($select_category, _categories, category_id);
235
+ setSelect($select_service, _services, service_id);
236
+ setSelect($select_employee, _staff, staff_id);
237
+ }
238
+
239
+ // Location select change
240
+ $select_location.on('change', function () {
241
+ var location_id = this.value,
242
+ category_id = $select_category.val(),
243
+ service_id = $select_service.val(),
244
+ staff_id = $select_employee.val()
245
+ ;
246
+
247
+ // Validate selected values.
248
+ if (location_id != '') {
249
+ if (staff_id != '' && !locations[location_id].staff.hasOwnProperty(staff_id)) {
250
+ staff_id = '';
251
+ }
252
+ if (service_id != '') {
253
+ var valid = false;
254
+ $.each(locations[location_id].staff, function(id) {
255
+ if (staff[id].services.hasOwnProperty(service_id)) {
256
+ valid = true;
257
+ return false;
258
+ }
259
+ });
260
+ if (!valid) {
261
+ service_id = '';
262
+ }
263
+ }
264
+ if (category_id != '') {
265
+ var valid = false;
266
+ $.each(locations[location_id].staff, function(id) {
267
+ $.each(staff[id].services, function(s_id) {
268
+ if (services[s_id].category_id == category_id) {
269
+ valid = true;
270
+ return false;
271
+ }
272
+ });
273
+ if (valid) {
274
+ return false;
275
+ }
276
+ });
277
+ if (!valid) {
278
+ category_id = '';
279
+ }
280
+ }
281
+ }
282
+ setSelects(location_id, category_id, service_id, staff_id);
283
+ });
284
+
285
+ // Category select change
286
+ $select_category.on('change', function () {
287
+ var location_id = $select_location.val(),
288
+ category_id = this.value,
289
+ service_id = $select_service.val(),
290
+ staff_id = $select_employee.val()
291
+ ;
292
+
293
+ // Validate selected values.
294
+ if (category_id != '') {
295
+ if (service_id != '') {
296
+ if (services[service_id].category_id != category_id) {
297
+ service_id = '';
298
+ }
299
+ }
300
+ if (staff_id != '') {
301
+ var valid = false;
302
+ $.each(staff[staff_id].services, function(id) {
303
+ if (services[id].category_id == category_id) {
304
+ valid = true;
305
+ return false;
306
+ }
307
+ });
308
+ if (!valid) {
309
+ staff_id = '';
310
+ }
311
+ }
312
+ }
313
+ setSelects(location_id, category_id, service_id, staff_id);
314
+ });
315
+
316
+ // Service select change
317
+ $select_service.on('change', function () {
318
+ var location_id = $select_location.val(),
319
+ category_id = '',
320
+ service_id = this.value,
321
+ staff_id = $select_employee.val()
322
+ ;
323
+
324
+ // Validate selected values.
325
+ if (service_id != '') {
326
+ if (staff_id != '' && !staff[staff_id].services.hasOwnProperty(service_id)) {
327
+ staff_id = '';
328
+ }
329
+ }
330
+ setSelects(location_id, category_id, service_id, staff_id);
331
+ if (service_id) {
332
+ $select_category.val(services[service_id].category_id);
333
+ }
334
+ });
335
+
336
+ // Staff select change
337
+ $select_employee.on('change', function() {
338
+ var location_id = $select_location.val(),
339
+ category_id = $select_category.val(),
340
+ service_id = $select_service.val(),
341
+ staff_id = this.value
342
+ ;
343
+
344
+ setSelects(location_id, category_id, service_id, staff_id);
345
+ });
346
+
347
+ // Set up draft selects.
348
+ setSelect($select_location, locations);
349
+ setSelect($select_category, categories);
350
+ setSelect($select_service, services);
351
+ setSelect($select_employee, staff);
352
+
353
+ $insert.on('click', function (e) {
354
+ e.preventDefault();
355
+
356
+ var insert = '[bookly-form';
357
+ var hide = [];
358
+ if ($select_location.val()) {
359
+ insert += ' location_id="' + $select_location.val() + '"';
360
+ }
361
+ if ($select_category.val()) {
362
+ insert += ' category_id="' + $select_category.val() + '"';
363
+ }
364
+ if ($hide_locations.is(':checked')) {
365
+ hide.push('locations');
366
+ }
367
+ if ($hide_categories.is(':checked')) {
368
+ hide.push('categories');
369
+ }
370
+ if ($select_service.val()) {
371
+ insert += ' service_id="' + $select_service.val() + '"';
372
+ }
373
+ if ($hide_services.is(':checked')) {
374
+ hide.push('services');
375
+ }
376
+ if ($hide_service_duration.is(':checked')) {
377
+ hide.push('service_duration');
378
+ }
379
+ if ($select_employee.val()) {
380
+ insert += ' staff_member_id="' + $select_employee.val() + '"';
381
+ }
382
+ if ($hide_number_of_persons.is(':not(:checked)')) {
383
+ insert += ' show_number_of_persons="1"';
384
+ }
385
+ if ($hide_quantity.is(':checked')) {
386
+ hide.push('quantity');
387
+ }
388
+ if ($hide_staff.is(':checked')) {
389
+ hide.push('staff_members');
390
+ }
391
+ if ($hide_date.is(':checked')) {
392
+ hide.push('date')
393
+ }
394
+ if ($hide_week_days.is(':checked')) {
395
+ hide.push('week_days')
396
+ }
397
+ if ($hide_time_range.is(':checked')) {
398
+ hide.push('time_range');
399
+ }
400
+ if (hide.length > 0) {
401
+ insert += ' hide="' + hide.join() + '"';
402
+ }
403
+ insert += ']';
404
+
405
+ window.send_to_editor(insert);
406
+
407
+ $select_location.val('');
408
+ $select_category.val('');
409
+ $select_service.val('');
410
+ $select_employee.val('');
411
+ $hide_locations.prop('checked', false);
412
+ $hide_categories.prop('checked', false);
413
+ $hide_services.prop('checked', false);
414
+ $hide_service_duration.prop('checked', false);
415
+ $hide_staff.prop('checked', false);
416
+ $hide_date.prop('checked', false);
417
+ $hide_week_days.prop('checked', false);
418
+ $hide_time_range.prop('checked', false);
419
+ $hide_number_of_persons.prop('checked', true);
420
+
421
+ window.parent.tb_remove();
422
+ return false;
423
+ });
424
+ });
425
  </script>
backend/modules/appearance/Ajax.php CHANGED
@@ -1,132 +1,136 @@
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
  }
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_email_confirm',
41
+ 'bookly_l10n_label_employee',
42
+ 'bookly_l10n_label_service_duration',
43
+ 'bookly_l10n_label_finish_by',
44
+ 'bookly_l10n_label_name',
45
+ 'bookly_l10n_label_first_name',
46
+ 'bookly_l10n_label_last_name',
47
+ 'bookly_l10n_label_notes',
48
+ 'bookly_l10n_label_number_of_persons',
49
+ 'bookly_l10n_label_pay_ccard',
50
+ 'bookly_l10n_label_pay_locally',
51
+ 'bookly_l10n_label_phone',
52
+ 'bookly_l10n_label_select_date',
53
+ 'bookly_l10n_label_service',
54
+ 'bookly_l10n_label_start_from',
55
+ 'bookly_l10n_option_category',
56
+ 'bookly_l10n_option_employee',
57
+ 'bookly_l10n_option_service',
58
+ 'bookly_l10n_option_day',
59
+ 'bookly_l10n_option_month',
60
+ 'bookly_l10n_option_year',
61
+ 'bookly_l10n_step_service',
62
+ 'bookly_l10n_step_service_mobile_button_next',
63
+ 'bookly_l10n_step_service_button_next',
64
+ 'bookly_l10n_step_time',
65
+ 'bookly_l10n_step_time_slot_not_available',
66
+ 'bookly_l10n_step_details',
67
+ 'bookly_l10n_step_details_button_next',
68
+ 'bookly_l10n_step_details_button_login',
69
+ 'bookly_l10n_step_payment',
70
+ 'bookly_l10n_step_payment_button_next',
71
+ 'bookly_l10n_step_done',
72
+ // Validator errors.
73
+ 'bookly_l10n_required_email',
74
+ 'bookly_l10n_email_confirm_not_match',
75
+ 'bookly_l10n_required_employee',
76
+ 'bookly_l10n_required_name',
77
+ 'bookly_l10n_required_first_name',
78
+ 'bookly_l10n_required_last_name',
79
+ 'bookly_l10n_required_phone',
80
+ 'bookly_l10n_required_service',
81
+ // Color.
82
+ 'bookly_app_color',
83
+ // Checkboxes.
84
+ 'bookly_app_required_employee',
85
+ 'bookly_app_service_name_with_duration',
86
+ 'bookly_app_show_blocked_timeslots',
87
+ 'bookly_app_show_calendar',
88
+ 'bookly_app_show_day_one_column',
89
+ 'bookly_app_show_time_zone_switcher',
90
+ 'bookly_app_show_login_button',
91
+ 'bookly_app_show_facebook_login_button',
92
+ 'bookly_app_show_notes',
93
+ 'bookly_app_show_progress_tracker',
94
+ 'bookly_app_align_buttons_left',
95
+ 'bookly_app_staff_name_with_price',
96
+ 'bookly_cst_required_details',
97
+ 'bookly_app_service_duration_with_price',
98
+ 'bookly_cst_first_last_name',
99
+ 'bookly_app_show_email_confirm',
100
+ // Options.
101
+ 'bookly_multiply_appointments_quantity_max',
102
+ ) ) );
103
+
104
+ // Allow add-ons to add their options.
105
+ $options_to_save = Proxy\Shared::prepareOptions( $options_to_save, $options );
106
+
107
+ // Save options.
108
+ foreach ( $options_to_save as $option_name => $option_value ) {
109
+ update_option( $option_name, $option_value );
110
+ // Register string for translate in WPML.
111
+ if ( strpos( $option_name, 'bookly_l10n_' ) === 0 ) {
112
+ do_action( 'wpml_register_single_string', 'bookly', $option_name, $option_value );
113
+ }
114
+ }
115
+
116
+ wp_send_json_success();
117
+ }
118
+
119
+ /**
120
+ * Ajax request to dismiss appearance notice for current user.
121
+ */
122
+ public static function dismissAppearanceNotice()
123
+ {
124
+ update_user_meta( get_current_user_id(), Lib\Plugin::getPrefix() . 'dismiss_appearance_notice', 1 );
125
+ }
126
+
127
+ /**
128
+ * Process ajax request to save custom css
129
+ */
130
+ public static function saveCustomCss()
131
+ {
132
+ update_option( 'bookly_app_custom_styles', self::parameter( 'custom_css' ) );
133
+
134
+ wp_send_json_success( array( 'message' => __( 'Your custom CSS was saved. Please refresh the page to see your changes.', 'bookly') ) );
135
+ }
136
  }
backend/modules/appearance/Page.php CHANGED
@@ -1,103 +1,108 @@
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
  }
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( 'step' => 1, 'show' => true, 'title' => get_option( 'bookly_l10n_step_service' ) ),
82
+ 3 => array( 'step' => 3, 'show' => true, 'title' => get_option( 'bookly_l10n_step_time' ) ),
83
+ 6 => array( 'step' => 6, 'show' => true, 'title' => get_option( 'bookly_l10n_step_details' ) ),
84
+ 7 => array( 'step' => 7, 'show' => true, 'title' => get_option( 'bookly_l10n_step_payment' ) ),
85
+ 8 => array( 'step' => 8, 'show' => true, 'title' => get_option( 'bookly_l10n_step_done' ) ),
86
+ );
87
+ if ( Lib\Config::serviceExtrasActive() ) {
88
+ if ( get_option( 'bookly_service_extras_after_step_time' ) ) {
89
+ $steps[2] = $steps[3];
90
+ $steps[3] = array( 'step' => 2, 'show' => get_option( 'bookly_service_extras_enabled' ), 'title' => get_option( 'bookly_l10n_step_extras' ) );
91
+ } else {
92
+ $steps[2] = array( 'step' => 2, 'show' => get_option( 'bookly_service_extras_enabled' ), 'title' => get_option( 'bookly_l10n_step_extras' ) );
93
+ }
94
+ }
95
+ if ( Lib\Config::recurringAppointmentsActive() ) {
96
+ $steps[4] = array( 'step' => 4, 'show' => get_option( 'bookly_recurring_appointments_enabled' ), 'title' => get_option( 'bookly_l10n_step_repeat' ) );
97
+ }
98
+ if ( Lib\Config::cartActive() ) {
99
+ $steps[5] = array( 'step' => 5, 'show' => get_option( 'bookly_cart_enabled' ), 'title' => get_option( 'bookly_l10n_step_cart' ) );
100
+ }
101
+ ksort( $steps );
102
+
103
+ $custom_css = get_option( 'bookly_app_custom_styles' );
104
+
105
+ // Render general layout.
106
+ self::renderTemplate( 'index', compact( 'steps', 'custom_css' ) );
107
+ }
108
  }
backend/modules/appearance/proxy/Cart.php CHANGED
@@ -1,17 +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
  }
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 CHANGED
@@ -1,14 +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
  }
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 CHANGED
@@ -1,16 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,16 +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
  }
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 CHANGED
@@ -1,15 +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
  }
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 CHANGED
@@ -1,16 +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
  }
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 CHANGED
@@ -1,16 +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
  }
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 CHANGED
@@ -1,16 +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
  }
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 CHANGED
@@ -1,23 +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
  }
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 CHANGED
@@ -1,18 +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
  }
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 CHANGED
@@ -1,18 +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
  }
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 CHANGED
@@ -1,19 +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
  }
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/Tasks.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Backend\Modules\Appearance\Proxy;
3
+
4
+ use Bookly\Lib;
5
+
6
+ /**
7
+ * Class Tasks
8
+ * @package Bookly\Backend\Modules\Appearance\Proxy
9
+ *
10
+ * @method static void renderSkipButton() Render 'Skip' button on time step.
11
+ */
12
+ abstract class Tasks extends Lib\Base\Proxy
13
+ {
14
+
15
+ }
backend/modules/appearance/proxy/WaitingList.php CHANGED
@@ -1,15 +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
  }
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/css/bootstrap-editable.css CHANGED
@@ -1,663 +1,663 @@
1
- /*! X-editable - v1.5.1
2
- * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
3
- * http://github.com/vitalets/x-editable
4
- * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
5
- .editableform {
6
- margin-bottom: 0; /* overwrites bootstrap margin */
7
- }
8
-
9
- .editableform .control-group {
10
- margin-bottom: 0; /* overwrites bootstrap margin */
11
- white-space: nowrap; /* prevent wrapping buttons on new line */
12
- line-height: 20px; /* overwriting bootstrap line-height. See #133 */
13
- }
14
-
15
- /*
16
- BS3 width:1005 for inputs breaks editable form in popup
17
- See: https://github.com/vitalets/x-editable/issues/393
18
- */
19
- .editableform .form-control {
20
- width: auto;
21
- }
22
-
23
- .editable-buttons {
24
- display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */
25
- vertical-align: top;
26
- margin-left: 7px;
27
- /* inline-block emulation for IE7*/
28
- zoom: 1;
29
- *display: inline;
30
- }
31
-
32
- .editable-buttons.editable-buttons-bottom {
33
- display: block;
34
- margin-top: 7px;
35
- margin-left: 0;
36
- }
37
-
38
- .editable-input {
39
- vertical-align: top;
40
- display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */
41
- width: auto; /* bootstrap-responsive has width: 100% that breakes layout */
42
- white-space: normal; /* reset white-space decalred in parent*/
43
- /* display-inline emulation for IE7*/
44
- zoom: 1;
45
- *display: inline;
46
- }
47
-
48
- .editable-buttons .editable-cancel {
49
- margin-left: 7px;
50
- }
51
-
52
- /*for jquery-ui buttons need set height to look more pretty*/
53
- .editable-buttons button.ui-button-icon-only {
54
- height: 24px;
55
- width: 30px;
56
- }
57
-
58
- .editableform-loading {
59
- background: url('../../../../resources/images/loading.gif') center center no-repeat;
60
- height: 25px;
61
- width: auto;
62
- min-width: 25px;
63
- }
64
-
65
- .editable-inline .editableform-loading {
66
- background-position: left 5px;
67
- }
68
-
69
- .editable-error-block {
70
- max-width: 300px;
71
- margin: 5px 0 0 0;
72
- width: auto;
73
- white-space: normal;
74
- }
75
-
76
- /*add padding for jquery ui*/
77
- .editable-error-block.ui-state-error {
78
- padding: 3px;
79
- }
80
-
81
- .editable-error {
82
- color: red;
83
- }
84
-
85
- /* ---- For specific types ---- */
86
-
87
- .editableform .editable-date {
88
- padding: 0;
89
- margin: 0;
90
- float: left;
91
- }
92
-
93
- /* move datepicker icon to center of add-on button. See https://github.com/vitalets/x-editable/issues/183 */
94
- .editable-inline .add-on .icon-th {
95
- margin-top: 3px;
96
- margin-left: 1px;
97
- }
98
-
99
-
100
- /* checklist vertical alignment */
101
- .editable-checklist label input[type="checkbox"],
102
- .editable-checklist label span {
103
- vertical-align: middle;
104
- margin: 0;
105
- }
106
-
107
- .editable-checklist label {
108
- white-space: nowrap;
109
- }
110
-
111
- /* set exact width of textarea to fit buttons toolbar */
112
- .editable-wysihtml5 {
113
- width: 566px;
114
- height: 250px;
115
- }
116
-
117
- /* clear button shown as link in date inputs */
118
- .editable-clear {
119
- clear: both;
120
- font-size: 0.9em;
121
- text-decoration: none;
122
- text-align: right;
123
- }
124
-
125
- /* IOS-style clear button for text inputs */
126
- .editable-clear-x {
127
- background: url('../../../../resources/images/clear.png') center center no-repeat;
128
- display: block;
129
- width: 13px;
130
- height: 13px;
131
- position: absolute;
132
- opacity: 0.6;
133
- z-index: 100;
134
-
135
- top: 50%;
136
- right: 6px;
137
- margin-top: -6px;
138
-
139
- }
140
-
141
- .editable-clear-x:hover {
142
- opacity: 1;
143
- }
144
-
145
- .editable-pre-wrapped {
146
- white-space: pre-wrap;
147
- }
148
- .editable-container.editable-popup {
149
- max-width: none !important; /* without this rule poshytip/tooltip does not stretch */
150
- }
151
-
152
- .editable-container.popover {
153
- width: auto; /* without this rule popover does not stretch */
154
- }
155
-
156
- .editable-container.editable-inline {
157
- display: inline-block;
158
- vertical-align: middle;
159
- width: auto;
160
- /* inline-block emulation for IE7*/
161
- zoom: 1;
162
- *display: inline;
163
- }
164
-
165
- .editable-container.ui-widget {
166
- font-size: inherit; /* jqueryui widget font 1.1em too big, overwrite it */
167
- z-index: 9990; /* should be less than select2 dropdown z-index to close dropdown first when click */
168
- }
169
- .editable-click,
170
- a.editable-click,
171
- a.editable-click:hover {
172
- text-decoration: none;
173
- border-bottom: dashed 1px #0088cc;
174
- }
175
-
176
- .editable-click.editable-disabled,
177
- a.editable-click.editable-disabled,
178
- a.editable-click.editable-disabled:hover {
179
- color: #585858;
180
- cursor: default;
181
- border-bottom: none;
182
- }
183
-
184
- .editable-empty, .editable-empty:hover, .editable-empty:focus{
185
- font-style: italic;
186
- color: #DD1144;
187
- /* border-bottom: none; */
188
- text-decoration: none;
189
- }
190
-
191
- .editable-unsaved {
192
- font-weight: bold;
193
- }
194
-
195
- .editable-unsaved:after {
196
- /* content: '*'*/
197
- }
198
-
199
- .editable-bg-transition {
200
- -webkit-transition: background-color 1400ms ease-out;
201
- -moz-transition: background-color 1400ms ease-out;
202
- -o-transition: background-color 1400ms ease-out;
203
- -ms-transition: background-color 1400ms ease-out;
204
- transition: background-color 1400ms ease-out;
205
- }
206
-
207
- /*see https://github.com/vitalets/x-editable/issues/139 */
208
- .form-horizontal .editable
209
- {
210
- padding-top: 5px;
211
- display:inline-block;
212
- }
213
-
214
-
215
- /*!
216
- * Datepicker for Bootstrap
217
- *
218
- * Copyright 2012 Stefan Petre
219
- * Improvements by Andrew Rowls
220
- * Licensed under the Apache License v2.0
221
- * http://www.apache.org/licenses/LICENSE-2.0
222
- *
223
- */
224
- .datepicker {
225
- padding: 4px;
226
- -webkit-border-radius: 4px;
227
- -moz-border-radius: 4px;
228
- border-radius: 4px;
229
- direction: ltr;
230
- /*.dow {
231
- border-top: 1px solid #ddd !important;
232
- }*/
233
-
234
- }
235
- .datepicker-inline {
236
- width: 220px;
237
- }
238
- .datepicker.datepicker-rtl {
239
- direction: rtl;
240
- }
241
- .datepicker.datepicker-rtl table tr td span {
242
- float: right;
243
- }
244
- .datepicker-dropdown {
245
- top: 0;
246
- left: 0;
247
- }
248
- .datepicker-dropdown:before {
249
- content: '';
250
- display: inline-block;
251
- border-left: 7px solid transparent;
252
- border-right: 7px solid transparent;
253
- border-bottom: 7px solid #ccc;
254
- border-bottom-color: rgba(0, 0, 0, 0.2);
255
- position: absolute;
256
- top: -7px;
257
- left: 6px;
258
- }
259
- .datepicker-dropdown:after {
260
- content: '';
261
- display: inline-block;
262
- border-left: 6px solid transparent;
263
- border-right: 6px solid transparent;
264
- border-bottom: 6px solid #ffffff;
265
- position: absolute;
266
- top: -6px;
267
- left: 7px;
268
- }
269
- .datepicker > div {
270
- display: none;
271
- }
272
- .datepicker.days div.datepicker-days {
273
- display: block;
274
- }
275
- .datepicker.months div.datepicker-months {
276
- display: block;
277
- }
278
- .datepicker.years div.datepicker-years {
279
- display: block;
280
- }
281
- .datepicker table {
282
- margin: 0;
283
- }
284
- .datepicker td,
285
- .datepicker th {
286
- text-align: center;
287
- width: 20px;
288
- height: 20px;
289
- -webkit-border-radius: 4px;
290
- -moz-border-radius: 4px;
291
- border-radius: 4px;
292
- border: none;
293
- }
294
- .table-striped .datepicker table tr td,
295
- .table-striped .datepicker table tr th {
296
- background-color: transparent;
297
- }
298
- .datepicker table tr td.day:hover {
299
- background: #eeeeee;
300
- cursor: pointer;
301
- }
302
- .datepicker table tr td.old,
303
- .datepicker table tr td.new {
304
- color: #999999;
305
- }
306
- .datepicker table tr td.disabled,
307
- .datepicker table tr td.disabled:hover {
308
- background: none;
309
- color: #999999;
310
- cursor: default;
311
- }
312
- .datepicker table tr td.today,
313
- .datepicker table tr td.today:hover,
314
- .datepicker table tr td.today.disabled,
315
- .datepicker table tr td.today.disabled:hover {
316
- background-color: #fde19a;
317
- background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a);
318
- background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a);
319
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
320
- background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a);
321
- background-image: -o-linear-gradient(top, #fdd49a, #fdf59a);
322
- background-image: linear-gradient(top, #fdd49a, #fdf59a);
323
- background-repeat: repeat-x;
324
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
325
- border-color: #fdf59a #fdf59a #fbed50;
326
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
327
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
328
- color: #000;
329
- }
330
- .datepicker table tr td.today:hover,
331
- .datepicker table tr td.today:hover:hover,
332
- .datepicker table tr td.today.disabled:hover,
333
- .datepicker table tr td.today.disabled:hover:hover,
334
- .datepicker table tr td.today:active,
335
- .datepicker table tr td.today:hover:active,
336
- .datepicker table tr td.today.disabled:active,
337
- .datepicker table tr td.today.disabled:hover:active,
338
- .datepicker table tr td.today.active,
339
- .datepicker table tr td.today:hover.active,
340
- .datepicker table tr td.today.disabled.active,
341
- .datepicker table tr td.today.disabled:hover.active,
342
- .datepicker table tr td.today.disabled,
343
- .datepicker table tr td.today:hover.disabled,
344
- .datepicker table tr td.today.disabled.disabled,
345
- .datepicker table tr td.today.disabled:hover.disabled,
346
- .datepicker table tr td.today[disabled],
347
- .datepicker table tr td.today:hover[disabled],
348
- .datepicker table tr td.today.disabled[disabled],
349
- .datepicker table tr td.today.disabled:hover[disabled] {
350
- background-color: #fdf59a;
351
- }
352
- .datepicker table tr td.today:active,
353
- .datepicker table tr td.today:hover:active,
354
- .datepicker table tr td.today.disabled:active,
355
- .datepicker table tr td.today.disabled:hover:active,
356
- .datepicker table tr td.today.active,
357
- .datepicker table tr td.today:hover.active,
358
- .datepicker table tr td.today.disabled.active,
359
- .datepicker table tr td.today.disabled:hover.active {
360
- background-color: #fbf069 \9;
361
- }
362
- .datepicker table tr td.today:hover:hover {
363
- color: #000;
364
- }
365
- .datepicker table tr td.today.active:hover {
366
- color: #fff;
367
- }
368
- .datepicker table tr td.range,
369
- .datepicker table tr td.range:hover,
370
- .datepicker table tr td.range.disabled,
371
- .datepicker table tr td.range.disabled:hover {
372
- background: #eeeeee;
373
- -webkit-border-radius: 0;
374
- -moz-border-radius: 0;
375
- border-radius: 0;
376
- }
377
- .datepicker table tr td.range.today,
378
- .datepicker table tr td.range.today:hover,
379
- .datepicker table tr td.range.today.disabled,
380
- .datepicker table tr td.range.today.disabled:hover {
381
- background-color: #f3d17a;
382
- background-image: -moz-linear-gradient(top, #f3c17a, #f3e97a);
383
- background-image: -ms-linear-gradient(top, #f3c17a, #f3e97a);
384
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a));
385
- background-image: -webkit-linear-gradient(top, #f3c17a, #f3e97a);
386
- background-image: -o-linear-gradient(top, #f3c17a, #f3e97a);
387
- background-image: linear-gradient(top, #f3c17a, #f3e97a);
388
- background-repeat: repeat-x;
389
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);
390
- border-color: #f3e97a #f3e97a #edde34;
391
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
392
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
393
- -webkit-border-radius: 0;
394
- -moz-border-radius: 0;
395
- border-radius: 0;
396
- }
397
- .datepicker table tr td.range.today:hover,
398
- .datepicker table tr td.range.today:hover:hover,
399
- .datepicker table tr td.range.today.disabled:hover,
400
- .datepicker table tr td.range.today.disabled:hover:hover,
401
- .datepicker table tr td.range.today:active,
402
- .datepicker table tr td.range.today:hover:active,
403
- .datepicker table tr td.range.today.disabled:active,
404
- .datepicker table tr td.range.today.disabled:hover:active,
405
- .datepicker table tr td.range.today.active,
406
- .datepicker table tr td.range.today:hover.active,
407
- .datepicker table tr td.range.today.disabled.active,
408
- .datepicker table tr td.range.today.disabled:hover.active,
409
- .datepicker table tr td.range.today.disabled,
410
- .datepicker table tr td.range.today:hover.disabled,
411
- .datepicker table tr td.range.today.disabled.disabled,
412
- .datepicker table tr td.range.today.disabled:hover.disabled,
413
- .datepicker table tr td.range.today[disabled],
414
- .datepicker table tr td.range.today:hover[disabled],
415
- .datepicker table tr td.range.today.disabled[disabled],
416
- .datepicker table tr td.range.today.disabled:hover[disabled] {
417
- background-color: #f3e97a;
418
- }
419
- .datepicker table tr td.range.today:active,
420
- .datepicker table tr td.range.today:hover:active,
421
- .datepicker table tr td.range.today.disabled:active,
422
- .datepicker table tr td.range.today.disabled:hover:active,
423
- .datepicker table tr td.range.today.active,
424
- .datepicker table tr td.range.today:hover.active,
425
- .datepicker table tr td.range.today.disabled.active,
426
- .datepicker table tr td.range.today.disabled:hover.active {
427
- background-color: #efe24b \9;
428
- }
429
- .datepicker table tr td.selected,
430
- .datepicker table tr td.selected:hover,
431
- .datepicker table tr td.selected.disabled,
432
- .datepicker table tr td.selected.disabled:hover {
433
- background-color: #9e9e9e;
434
- background-image: -moz-linear-gradient(top, #b3b3b3, #808080);
435
- background-image: -ms-linear-gradient(top, #b3b3b3, #808080);
436
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080));
437
- background-image: -webkit-linear-gradient(top, #b3b3b3, #808080);
438
- background-image: -o-linear-gradient(top, #b3b3b3, #808080);
439
- background-image: linear-gradient(top, #b3b3b3, #808080);
440
- background-repeat: repeat-x;
441
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);
442
- border-color: #808080 #808080 #595959;
443
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
444
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
445
- color: #fff;
446
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
447
- }
448
- .datepicker table tr td.selected:hover,
449
- .datepicker table tr td.selected:hover:hover,
450
- .datepicker table tr td.selected.disabled:hover,
451
- .datepicker table tr td.selected.disabled:hover:hover,
452
- .datepicker table tr td.selected:active,
453
- .datepicker table tr td.selected:hover:active,
454
- .datepicker table tr td.selected.disabled:active,
455
- .datepicker table tr td.selected.disabled:hover:active,
456
- .datepicker table tr td.selected.active,
457
- .datepicker table tr td.selected:hover.active,
458
- .datepicker table tr td.selected.disabled.active,
459
- .datepicker table tr td.selected.disabled:hover.active,
460
- .datepicker table tr td.selected.disabled,
461
- .datepicker table tr td.selected:hover.disabled,
462
- .datepicker table tr td.selected.disabled.disabled,
463
- .datepicker table tr td.selected.disabled:hover.disabled,
464
- .datepicker table tr td.selected[disabled],
465
- .datepicker table tr td.selected:hover[disabled],
466
- .datepicker table tr td.selected.disabled[disabled],
467
- .datepicker table tr td.selected.disabled:hover[disabled] {
468
- background-color: #808080;
469
- }
470
- .datepicker table tr td.selected:active,
471
- .datepicker table tr td.selected:hover:active,
472
- .datepicker table tr td.selected.disabled:active,
473
- .datepicker table tr td.selected.disabled:hover:active,
474
- .datepicker table tr td.selected.active,
475
- .datepicker table tr td.selected:hover.active,
476
- .datepicker table tr td.selected.disabled.active,
477
- .datepicker table tr td.selected.disabled:hover.active {
478
- background-color: #666666 \9;
479
- }
480
- .datepicker table tr td.active,
481
- .datepicker table tr td.active:hover,
482
- .datepicker table tr td.active.disabled,
483
- .datepicker table tr td.active.disabled:hover {
484
- background-color: #006dcc;
485
- background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
486
- background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
487
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
488
- background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
489
- background-image: -o-linear-gradient(top, #0088cc, #0044cc);
490
- background-image: linear-gradient(top, #0088cc, #0044cc);
491
- background-repeat: repeat-x;
492
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
493
- border-color: #0044cc #0044cc #002a80;
494
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
495
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
496
- color: #fff;
497
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
498
- }
499
- .datepicker table tr td.active:hover,
500
- .datepicker table tr td.active:hover:hover,
501
- .datepicker table tr td.active.disabled:hover,
502
- .datepicker table tr td.active.disabled:hover:hover,
503
- .datepicker table tr td.active:active,
504
- .datepicker table tr td.active:hover:active,
505
- .datepicker table tr td.active.disabled:active,
506
- .datepicker table tr td.active.disabled:hover:active,
507
- .datepicker table tr td.active.active,
508
- .datepicker table tr td.active:hover.active,
509
- .datepicker table tr td.active.disabled.active,
510
- .datepicker table tr td.active.disabled:hover.active,
511
- .datepicker table tr td.active.disabled,
512
- .datepicker table tr td.active:hover.disabled,
513
- .datepicker table tr td.active.disabled.disabled,
514
- .datepicker table tr td.active.disabled:hover.disabled,
515
- .datepicker table tr td.active[disabled],
516
- .datepicker table tr td.active:hover[disabled],
517
- .datepicker table tr td.active.disabled[disabled],
518
- .datepicker table tr td.active.disabled:hover[disabled] {
519
- background-color: #0044cc;
520
- }
521
- .datepicker table tr td.active:active,
522
- .datepicker table tr td.active:hover:active,
523
- .datepicker table tr td.active.disabled:active,
524
- .datepicker table tr td.active.disabled:hover:active,
525
- .datepicker table tr td.active.active,
526
- .datepicker table tr td.active:hover.active,
527
- .datepicker table tr td.active.disabled.active,
528
- .datepicker table tr td.active.disabled:hover.active {
529
- background-color: #003399 \9;
530
- }
531
- .datepicker table tr td span {
532
- display: block;
533
- width: 23%;
534
- height: 54px;
535
- line-height: 54px;
536
- float: left;
537
- margin: 1%;
538
- cursor: pointer;
539
- -webkit-border-radius: 4px;
540
- -moz-border-radius: 4px;
541
- border-radius: 4px;
542
- }
543
- .datepicker table tr td span:hover {
544
- background: #eeeeee;
545
- }
546
- .datepicker table tr td span.disabled,
547
- .datepicker table tr td span.disabled:hover {
548
- background: none;
549
- color: #999999;
550
- cursor: default;
551
- }
552
- .datepicker table tr td span.active,
553
- .datepicker table tr td span.active:hover,
554
- .datepicker table tr td span.active.disabled,
555
- .datepicker table tr td span.active.disabled:hover {
556
- background-color: #006dcc;
557
- background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
558
- background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
559
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
560
- background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
561
- background-image: -o-linear-gradient(top, #0088cc, #0044cc);
562
- background-image: linear-gradient(top, #0088cc, #0044cc);
563
- background-repeat: repeat-x;
564
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
565
- border-color: #0044cc #0044cc #002a80;
566
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
567
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
568
- color: #fff;
569
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
570
- }
571
- .datepicker table tr td span.active:hover,
572
- .datepicker table tr td span.active:hover:hover,
573
- .datepicker table tr td span.active.disabled:hover,
574
- .datepicker table tr td span.active.disabled:hover:hover,
575
- .datepicker table tr td span.active:active,
576
- .datepicker table tr td span.active:hover:active,
577
- .datepicker table tr td span.active.disabled:active,
578
- .datepicker table tr td span.active.disabled:hover:active,
579
- .datepicker table tr td span.active.active,
580
- .datepicker table tr td span.active:hover.active,
581
- .datepicker table tr td span.active.disabled.active,
582
- .datepicker table tr td span.active.disabled:hover.active,
583
- .datepicker table tr td span.active.disabled,
584
- .datepicker table tr td span.active:hover.disabled,
585
- .datepicker table tr td span.active.disabled.disabled,
586
- .datepicker table tr td span.active.disabled:hover.disabled,
587
- .datepicker table tr td span.active[disabled],
588
- .datepicker table tr td span.active:hover[disabled],
589
- .datepicker table tr td span.active.disabled[disabled],
590
- .datepicker table tr td span.active.disabled:hover[disabled] {
591
- background-color: #0044cc;
592
- }
593
- .datepicker table tr td span.active:active,
594
- .datepicker table tr td span.active:hover:active,
595
- .datepicker table tr td span.active.disabled:active,
596
- .datepicker table tr td span.active.disabled:hover:active,
597
- .datepicker table tr td span.active.active,
598
- .datepicker table tr td span.active:hover.active,
599
- .datepicker table tr td span.active.disabled.active,
600
- .datepicker table tr td span.active.disabled:hover.active {
601
- background-color: #003399 \9;
602
- }
603
- .datepicker table tr td span.old,
604
- .datepicker table tr td span.new {
605
- color: #999999;
606
- }
607
- .datepicker th.datepicker-switch {
608
- width: 145px;
609
- }
610
- .datepicker thead tr:first-child th,
611
- .datepicker tfoot tr th {
612
- cursor: pointer;
613
- }
614
- .datepicker thead tr:first-child th:hover,
615
- .datepicker tfoot tr th:hover {
616
- background: #eeeeee;
617
- }
618
- .datepicker .cw {
619
- font-size: 10px;
620
- width: 12px;
621
- padding: 0 2px 0 5px;
622
- vertical-align: middle;
623
- }
624
- .datepicker thead tr:first-child th.cw {
625
- cursor: default;
626
- background-color: transparent;
627
- }
628
- .input-append.date .add-on i,
629
- .input-prepend.date .add-on i {
630
- display: block;
631
- cursor: pointer;
632
- width: 16px;
633
- height: 16px;
634
- }
635
- .input-daterange input {
636
- text-align: center;
637
- }
638
- .input-daterange input:first-child {
639
- -webkit-border-radius: 3px 0 0 3px;
640
- -moz-border-radius: 3px 0 0 3px;
641
- border-radius: 3px 0 0 3px;
642
- }
643
- .input-daterange input:last-child {
644
- -webkit-border-radius: 0 3px 3px 0;
645
- -moz-border-radius: 0 3px 3px 0;
646
- border-radius: 0 3px 3px 0;
647
- }
648
- .input-daterange .add-on {
649
- display: inline-block;
650
- width: auto;
651
- min-width: 16px;
652
- height: 18px;
653
- padding: 4px 5px;
654
- font-weight: normal;
655
- line-height: 18px;
656
- text-align: center;
657
- text-shadow: 0 1px 0 #ffffff;
658
- vertical-align: middle;
659
- background-color: #eeeeee;
660
- border: 1px solid #ccc;
661
- margin-left: -5px;
662
- margin-right: -5px;
663
- }
1
+ /*! X-editable - v1.5.1
2
+ * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
3
+ * http://github.com/vitalets/x-editable
4
+ * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
5
+ .editableform {
6
+ margin-bottom: 0; /* overwrites bootstrap margin */
7
+ }
8
+
9
+ .editableform .control-group {
10
+ margin-bottom: 0; /* overwrites bootstrap margin */
11
+ white-space: nowrap; /* prevent wrapping buttons on new line */
12
+ line-height: 20px; /* overwriting bootstrap line-height. See #133 */
13
+ }
14
+
15
+ /*
16
+ BS3 width:1005 for inputs breaks editable form in popup
17
+ See: https://github.com/vitalets/x-editable/issues/393
18
+ */
19
+ .editableform .form-control {
20
+ width: auto;
21
+ }
22
+
23
+ .editable-buttons {
24
+ display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */
25
+ vertical-align: top;
26
+ margin-left: 7px;
27
+ /* inline-block emulation for IE7*/
28
+ zoom: 1;
29
+ *display: inline;
30
+ }
31
+
32
+ .editable-buttons.editable-buttons-bottom {
33
+ display: block;
34
+ margin-top: 7px;
35
+ margin-left: 0;
36
+ }
37
+
38
+ .editable-input {
39
+ vertical-align: top;
40
+ display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */
41
+ width: auto; /* bootstrap-responsive has width: 100% that breakes layout */
42
+ white-space: normal; /* reset white-space decalred in parent*/
43
+ /* display-inline emulation for IE7*/
44
+ zoom: 1;
45
+ *display: inline;
46
+ }
47
+
48
+ .editable-buttons .editable-cancel {
49
+ margin-left: 7px;
50
+ }
51
+
52
+ /*for jquery-ui buttons need set height to look more pretty*/
53
+ .editable-buttons button.ui-button-icon-only {
54
+ height: 24px;
55
+ width: 30px;
56
+ }
57
+
58
+ .editableform-loading {
59
+ background: url('../../../../resources/images/loading.gif') center center no-repeat;
60
+ height: 25px;
61
+ width: auto;
62
+ min-width: 25px;
63
+ }
64
+
65
+ .editable-inline .editableform-loading {
66
+ background-position: left 5px;
67
+ }
68
+
69
+ .editable-error-block {
70
+ max-width: 300px;
71
+ margin: 5px 0 0 0;
72
+ width: auto;
73
+ white-space: normal;
74
+ }
75
+
76
+ /*add padding for jquery ui*/
77
+ .editable-error-block.ui-state-error {
78
+ padding: 3px;
79
+ }
80
+
81
+ .editable-error {
82
+ color: red;
83
+ }
84
+
85
+ /* ---- For specific types ---- */
86
+
87
+ .editableform .editable-date {
88
+ padding: 0;
89
+ margin: 0;
90
+ float: left;
91
+ }
92
+
93
+ /* move datepicker icon to center of add-on button. See https://github.com/vitalets/x-editable/issues/183 */
94
+ .editable-inline .add-on .icon-th {
95
+ margin-top: 3px;
96
+ margin-left: 1px;
97
+ }
98
+
99
+
100
+ /* checklist vertical alignment */
101
+ .editable-checklist label input[type="checkbox"],
102
+ .editable-checklist label span {
103
+ vertical-align: middle;
104
+ margin: 0;
105
+ }
106
+
107
+ .editable-checklist label {
108
+ white-space: nowrap;
109
+ }
110
+
111
+ /* set exact width of textarea to fit buttons toolbar */
112
+ .editable-wysihtml5 {
113
+ width: 566px;
114
+ height: 250px;
115
+ }
116
+
117
+ /* clear button shown as link in date inputs */
118
+ .editable-clear {
119
+ clear: both;
120
+ font-size: 0.9em;
121
+ text-decoration: none;
122
+ text-align: right;
123
+ }
124
+
125
+ /* IOS-style clear button for text inputs */
126
+ .editable-clear-x {
127
+ background: url('../../../../resources/images/clear.png') center center no-repeat;
128
+ display: block;
129
+ width: 13px;
130
+ height: 13px;
131
+ position: absolute;
132
+ opacity: 0.6;
133
+ z-index: 100;
134
+
135
+ top: 50%;
136
+ right: 6px;
137
+ margin-top: -6px;
138
+
139
+ }
140
+
141
+ .editable-clear-x:hover {
142
+ opacity: 1;
143
+ }
144
+
145
+ .editable-pre-wrapped {
146
+ white-space: pre-wrap;
147
+ }
148
+ .editable-container.editable-popup {
149
+ max-width: none !important; /* without this rule poshytip/tooltip does not stretch */
150
+ }
151
+
152
+ .editable-container.popover {
153
+ width: auto; /* without this rule popover does not stretch */
154
+ }
155
+
156
+ .editable-container.editable-inline {
157
+ display: inline-block;
158
+ vertical-align: middle;
159
+ width: auto;
160
+ /* inline-block emulation for IE7*/
161
+ zoom: 1;
162
+ *display: inline;
163
+ }
164
+
165
+ .editable-container.ui-widget {
166
+ font-size: inherit; /* jqueryui widget font 1.1em too big, overwrite it */
167
+ z-index: 9990; /* should be less than select2 dropdown z-index to close dropdown first when click */
168
+ }
169
+ .editable-click,
170
+ a.editable-click,
171
+ a.editable-click:hover {
172
+ text-decoration: none;
173
+ border-bottom: dashed 1px #0088cc;
174
+ }
175
+
176
+ .editable-click.editable-disabled,
177
+ a.editable-click.editable-disabled,
178
+ a.editable-click.editable-disabled:hover {
179
+ color: #585858;
180
+ cursor: default;
181
+ border-bottom: none;
182
+ }
183
+
184
+ .editable-empty, .editable-empty:hover, .editable-empty:focus{
185
+ font-style: italic;
186
+ color: #DD1144;
187
+ /* border-bottom: none; */
188
+ text-decoration: none;
189
+ }
190
+
191
+ .editable-unsaved {
192
+ font-weight: bold;
193
+ }
194
+
195
+ .editable-unsaved:after {
196
+ /* content: '*'*/
197
+ }
198
+
199
+ .editable-bg-transition {
200
+ -webkit-transition: background-color 1400ms ease-out;
201
+ -moz-transition: background-color 1400ms ease-out;
202
+ -o-transition: background-color 1400ms ease-out;
203
+ -ms-transition: background-color 1400ms ease-out;
204
+ transition: background-color 1400ms ease-out;
205
+ }
206
+
207
+ /*see https://github.com/vitalets/x-editable/issues/139 */
208
+ .form-horizontal .editable
209
+ {
210
+ padding-top: 5px;
211
+ display:inline-block;
212
+ }
213
+
214
+
215
+ /*!
216
+ * Datepicker for Bootstrap
217
+ *
218
+ * Copyright 2012 Stefan Petre
219
+ * Improvements by Andrew Rowls
220
+ * Licensed under the Apache License v2.0
221
+ * http://www.apache.org/licenses/LICENSE-2.0
222
+ *
223
+ */
224
+ .datepicker {
225
+ padding: 4px;
226
+ -webkit-border-radius: 4px;
227
+ -moz-border-radius: 4px;
228
+ border-radius: 4px;
229
+ direction: ltr;
230
+ /*.dow {
231
+ border-top: 1px solid #ddd !important;
232
+ }*/
233
+
234
+ }
235
+ .datepicker-inline {
236
+ width: 220px;
237
+ }
238
+ .datepicker.datepicker-rtl {
239
+ direction: rtl;
240
+ }
241
+ .datepicker.datepicker-rtl table tr td span {
242
+ float: right;
243
+ }
244
+ .datepicker-dropdown {
245
+ top: 0;
246
+ left: 0;
247
+ }
248
+ .datepicker-dropdown:before {
249
+ content: '';
250
+ display: inline-block;
251
+ border-left: 7px solid transparent;
252
+ border-right: 7px solid transparent;
253
+ border-bottom: 7px solid #ccc;
254
+ border-bottom-color: rgba(0, 0, 0, 0.2);
255
+ position: absolute;
256
+ top: -7px;
257
+ left: 6px;
258
+ }
259
+ .datepicker-dropdown:after {
260
+ content: '';
261
+ display: inline-block;
262
+ border-left: 6px solid transparent;
263
+ border-right: 6px solid transparent;
264
+ border-bottom: 6px solid #ffffff;
265
+ position: absolute;
266
+ top: -6px;
267
+ left: 7px;
268
+ }
269
+ .datepicker > div {
270
+ display: none;
271
+ }
272
+ .datepicker.days div.datepicker-days {
273
+ display: block;
274
+ }
275
+ .datepicker.months div.datepicker-months {
276
+ display: block;
277
+ }
278
+ .datepicker.years div.datepicker-years {
279
+ display: block;
280
+ }
281
+ .datepicker table {
282
+ margin: 0;
283
+ }
284
+ .datepicker td,
285
+ .datepicker th {
286
+ text-align: center;
287
+ width: 20px;
288
+ height: 20px;
289
+ -webkit-border-radius: 4px;
290
+ -moz-border-radius: 4px;
291
+ border-radius: 4px;
292
+ border: none;
293
+ }
294
+ .table-striped .datepicker table tr td,
295
+ .table-striped .datepicker table tr th {
296
+ background-color: transparent;
297
+ }
298
+ .datepicker table tr td.day:hover {
299
+ background: #eeeeee;
300
+ cursor: pointer;
301
+ }
302
+ .datepicker table tr td.old,
303
+ .datepicker table tr td.new {
304
+ color: #999999;
305
+ }
306
+ .datepicker table tr td.disabled,
307
+ .datepicker table tr td.disabled:hover {
308
+ background: none;
309
+ color: #999999;
310
+ cursor: default;
311
+ }
312
+ .datepicker table tr td.today,
313
+ .datepicker table tr td.today:hover,
314
+ .datepicker table tr td.today.disabled,
315
+ .datepicker table tr td.today.disabled:hover {
316
+ background-color: #fde19a;
317
+ background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a);
318
+ background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a);
319
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
320
+ background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a);
321
+ background-image: -o-linear-gradient(top, #fdd49a, #fdf59a);
322
+ background-image: linear-gradient(top, #fdd49a, #fdf59a);
323
+ background-repeat: repeat-x;
324
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
325
+ border-color: #fdf59a #fdf59a #fbed50;
326
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
327
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
328
+ color: #000;
329
+ }
330
+ .datepicker table tr td.today:hover,
331
+ .datepicker table tr td.today:hover:hover,
332
+ .datepicker table tr td.today.disabled:hover,
333
+ .datepicker table tr td.today.disabled:hover:hover,
334
+ .datepicker table tr td.today:active,
335
+ .datepicker table tr td.today:hover:active,
336
+ .datepicker table tr td.today.disabled:active,
337
+ .datepicker table tr td.today.disabled:hover:active,
338
+ .datepicker table tr td.today.active,
339
+ .datepicker table tr td.today:hover.active,
340
+ .datepicker table tr td.today.disabled.active,
341
+ .datepicker table tr td.today.disabled:hover.active,
342
+ .datepicker table tr td.today.disabled,
343
+ .datepicker table tr td.today:hover.disabled,
344
+ .datepicker table tr td.today.disabled.disabled,
345
+ .datepicker table tr td.today.disabled:hover.disabled,
346
+ .datepicker table tr td.today[disabled],
347
+ .datepicker table tr td.today:hover[disabled],
348
+ .datepicker table tr td.today.disabled[disabled],
349
+ .datepicker table tr td.today.disabled:hover[disabled] {
350
+ background-color: #fdf59a;
351
+ }
352
+ .datepicker table tr td.today:active,
353
+ .datepicker table tr td.today:hover:active,
354
+ .datepicker table tr td.today.disabled:active,
355
+ .datepicker table tr td.today.disabled:hover:active,
356
+ .datepicker table tr td.today.active,
357
+ .datepicker table tr td.today:hover.active,
358
+ .datepicker table tr td.today.disabled.active,
359
+ .datepicker table tr td.today.disabled:hover.active {
360
+ background-color: #fbf069 \9;
361
+ }
362
+ .datepicker table tr td.today:hover:hover {
363
+ color: #000;
364
+ }
365
+ .datepicker table tr td.today.active:hover {
366
+ color: #fff;
367
+ }
368
+ .datepicker table tr td.range,
369
+ .datepicker table tr td.range:hover,
370
+ .datepicker table tr td.range.disabled,
371
+ .datepicker table tr td.range.disabled:hover {
372
+ background: #eeeeee;
373
+ -webkit-border-radius: 0;
374
+ -moz-border-radius: 0;
375
+ border-radius: 0;
376
+ }
377
+ .datepicker table tr td.range.today,
378
+ .datepicker table tr td.range.today:hover,
379
+ .datepicker table tr td.range.today.disabled,
380
+ .datepicker table tr td.range.today.disabled:hover {
381
+ background-color: #f3d17a;
382
+ background-image: -moz-linear-gradient(top, #f3c17a, #f3e97a);
383
+ background-image: -ms-linear-gradient(top, #f3c17a, #f3e97a);
384
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a));
385
+ background-image: -webkit-linear-gradient(top, #f3c17a, #f3e97a);
386
+ background-image: -o-linear-gradient(top, #f3c17a, #f3e97a);
387
+ background-image: linear-gradient(top, #f3c17a, #f3e97a);
388
+ background-repeat: repeat-x;
389
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);
390
+ border-color: #f3e97a #f3e97a #edde34;
391
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
392
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
393
+ -webkit-border-radius: 0;
394
+ -moz-border-radius: 0;
395
+ border-radius: 0;
396
+ }
397
+ .datepicker table tr td.range.today:hover,
398
+ .datepicker table tr td.range.today:hover:hover,
399
+ .datepicker table tr td.range.today.disabled:hover,
400
+ .datepicker table tr td.range.today.disabled:hover:hover,
401
+ .datepicker table tr td.range.today:active,
402
+ .datepicker table tr td.range.today:hover:active,
403
+ .datepicker table tr td.range.today.disabled:active,
404
+ .datepicker table tr td.range.today.disabled:hover:active,
405
+ .datepicker table tr td.range.today.active,
406
+ .datepicker table tr td.range.today:hover.active,
407
+ .datepicker table tr td.range.today.disabled.active,
408
+ .datepicker table tr td.range.today.disabled:hover.active,
409
+ .datepicker table tr td.range.today.disabled,
410
+ .datepicker table tr td.range.today:hover.disabled,
411
+ .datepicker table tr td.range.today.disabled.disabled,
412
+ .datepicker table tr td.range.today.disabled:hover.disabled,
413
+ .datepicker table tr td.range.today[disabled],
414
+ .datepicker table tr td.range.today:hover[disabled],
415
+ .datepicker table tr td.range.today.disabled[disabled],
416
+ .datepicker table tr td.range.today.disabled:hover[disabled] {
417
+ background-color: #f3e97a;
418
+ }
419
+ .datepicker table tr td.range.today:active,
420
+ .datepicker table tr td.range.today:hover:active,
421
+ .datepicker table tr td.range.today.disabled:active,
422
+ .datepicker table tr td.range.today.disabled:hover:active,
423
+ .datepicker table tr td.range.today.active,
424
+ .datepicker table tr td.range.today:hover.active,
425
+ .datepicker table tr td.range.today.disabled.active,
426
+ .datepicker table tr td.range.today.disabled:hover.active {
427
+ background-color: #efe24b \9;
428
+ }
429
+ .datepicker table tr td.selected,
430
+ .datepicker table tr td.selected:hover,
431
+ .datepicker table tr td.selected.disabled,
432
+ .datepicker table tr td.selected.disabled:hover {
433
+ background-color: #9e9e9e;
434
+ background-image: -moz-linear-gradient(top, #b3b3b3, #808080);
435
+ background-image: -ms-linear-gradient(top, #b3b3b3, #808080);
436
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080));
437
+ background-image: -webkit-linear-gradient(top, #b3b3b3, #808080);
438
+ background-image: -o-linear-gradient(top, #b3b3b3, #808080);
439
+ background-image: linear-gradient(top, #b3b3b3, #808080);
440
+ background-repeat: repeat-x;
441
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);
442
+ border-color: #808080 #808080 #595959;
443
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
444
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
445
+ color: #fff;
446
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
447
+ }
448
+ .datepicker table tr td.selected:hover,
449
+ .datepicker table tr td.selected:hover:hover,
450
+ .datepicker table tr td.selected.disabled:hover,
451
+ .datepicker table tr td.selected.disabled:hover:hover,
452
+ .datepicker table tr td.selected:active,
453
+ .datepicker table tr td.selected:hover:active,
454
+ .datepicker table tr td.selected.disabled:active,
455
+ .datepicker table tr td.selected.disabled:hover:active,
456
+ .datepicker table tr td.selected.active,
457
+ .datepicker table tr td.selected:hover.active,
458
+ .datepicker table tr td.selected.disabled.active,
459
+ .datepicker table tr td.selected.disabled:hover.active,
460
+ .datepicker table tr td.selected.disabled,
461
+ .datepicker table tr td.selected:hover.disabled,
462
+ .datepicker table tr td.selected.disabled.disabled,
463
+ .datepicker table tr td.selected.disabled:hover.disabled,
464
+ .datepicker table tr td.selected[disabled],
465
+ .datepicker table tr td.selected:hover[disabled],
466
+ .datepicker table tr td.selected.disabled[disabled],
467
+ .datepicker table tr td.selected.disabled:hover[disabled] {
468
+ background-color: #808080;
469
+ }
470
+ .datepicker table tr td.selected:active,
471
+ .datepicker table tr td.selected:hover:active,
472
+ .datepicker table tr td.selected.disabled:active,
473
+ .datepicker table tr td.selected.disabled:hover:active,
474
+ .datepicker table tr td.selected.active,
475
+ .datepicker table tr td.selected:hover.active,
476
+ .datepicker table tr td.selected.disabled.active,
477
+ .datepicker table tr td.selected.disabled:hover.active {
478
+ background-color: #666666 \9;
479
+ }
480
+ .datepicker table tr td.active,
481
+ .datepicker table tr td.active:hover,
482
+ .datepicker table tr td.active.disabled,
483
+ .datepicker table tr td.active.disabled:hover {
484
+ background-color: #006dcc;
485
+ background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
486
+ background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
487
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
488
+ background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
489
+ background-image: -o-linear-gradient(top, #0088cc, #0044cc);
490
+ background-image: linear-gradient(top, #0088cc, #0044cc);
491
+ background-repeat: repeat-x;
492
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
493
+ border-color: #0044cc #0044cc #002a80;
494
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
495
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
496
+ color: #fff;
497
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
498
+ }
499
+ .datepicker table tr td.active:hover,
500
+ .datepicker table tr td.active:hover:hover,
501
+ .datepicker table tr td.active.disabled:hover,
502
+ .datepicker table tr td.active.disabled:hover:hover,
503
+ .datepicker table tr td.active:active,
504
+ .datepicker table tr td.active:hover:active,
505
+ .datepicker table tr td.active.disabled:active,
506
+ .datepicker table tr td.active.disabled:hover:active,
507
+ .datepicker table tr td.active.active,
508
+ .datepicker table tr td.active:hover.active,
509
+ .datepicker table tr td.active.disabled.active,
510
+ .datepicker table tr td.active.disabled:hover.active,
511
+ .datepicker table tr td.active.disabled,
512
+ .datepicker table tr td.active:hover.disabled,
513
+ .datepicker table tr td.active.disabled.disabled,
514
+ .datepicker table tr td.active.disabled:hover.disabled,
515
+ .datepicker table tr td.active[disabled],
516
+ .datepicker table tr td.active:hover[disabled],
517
+ .datepicker table tr td.active.disabled[disabled],
518
+ .datepicker table tr td.active.disabled:hover[disabled] {
519
+ background-color: #0044cc;
520
+ }
521
+ .datepicker table tr td.active:active,
522
+ .datepicker table tr td.active:hover:active,
523
+ .datepicker table tr td.active.disabled:active,
524
+ .datepicker table tr td.active.disabled:hover:active,
525
+ .datepicker table tr td.active.active,
526
+ .datepicker table tr td.active:hover.active,
527
+ .datepicker table tr td.active.disabled.active,
528
+ .datepicker table tr td.active.disabled:hover.active {
529
+ background-color: #003399 \9;
530
+ }
531
+ .datepicker table tr td span {
532
+ display: block;
533
+ width: 23%;
534
+ height: 54px;
535
+ line-height: 54px;
536
+ float: left;
537
+ margin: 1%;
538
+ cursor: pointer;
539
+ -webkit-border-radius: 4px;
540
+ -moz-border-radius: 4px;
541
+ border-radius: 4px;
542
+ }
543
+ .datepicker table tr td span:hover {
544
+ background: #eeeeee;
545
+ }
546
+ .datepicker table tr td span.disabled,
547
+ .datepicker table tr td span.disabled:hover {
548
+ background: none;
549
+ color: #999999;
550
+ cursor: default;
551
+ }
552
+ .datepicker table tr td span.active,
553
+ .datepicker table tr td span.active:hover,
554
+ .datepicker table tr td span.active.disabled,
555
+ .datepicker table tr td span.active.disabled:hover {
556
+ background-color: #006dcc;
557
+ background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
558
+ background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
559
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
560
+ background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
561
+ background-image: -o-linear-gradient(top, #0088cc, #0044cc);
562
+ background-image: linear-gradient(top, #0088cc, #0044cc);
563
+ background-repeat: repeat-x;
564
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
565
+ border-color: #0044cc #0044cc #002a80;
566
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
567
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
568
+ color: #fff;
569
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
570
+ }
571
+ .datepicker table tr td span.active:hover,
572
+ .datepicker table tr td span.active:hover:hover,
573
+ .datepicker table tr td span.active.disabled:hover,
574
+ .datepicker table tr td span.active.disabled:hover:hover,
575
+ .datepicker table tr td span.active:active,
576
+ .datepicker table tr td span.active:hover:active,
577
+ .datepicker table tr td span.active.disabled:active,
578
+ .datepicker table tr td span.active.disabled:hover:active,
579
+ .datepicker table tr td span.active.active,
580
+ .datepicker table tr td span.active:hover.active,
581
+ .datepicker table tr td span.active.disabled.active,
582
+ .datepicker table tr td span.active.disabled:hover.active,
583
+ .datepicker table tr td span.active.disabled,
584
+ .datepicker table tr td span.active:hover.disabled,
585
+ .datepicker table tr td span.active.disabled.disabled,
586
+ .datepicker table tr td span.active.disabled:hover.disabled,
587
+ .datepicker table tr td span.active[disabled],
588
+ .datepicker table tr td span.active:hover[disabled],
589
+ .datepicker table tr td span.active.disabled[disabled],
590
+ .datepicker table tr td span.active.disabled:hover[disabled] {
591
+ background-color: #0044cc;
592
+ }
593
+ .datepicker table tr td span.active:active,
594
+ .datepicker table tr td span.active:hover:active,
595
+ .datepicker table tr td span.active.disabled:active,
596
+ .datepicker table tr td span.active.disabled:hover:active,
597
+ .datepicker table tr td span.active.active,
598
+ .datepicker table tr td span.active:hover.active,
599
+ .datepicker table tr td span.active.disabled.active,
600
+ .datepicker table tr td span.active.disabled:hover.active {
601
+ background-color: #003399 \9;
602
+ }
603
+ .datepicker table tr td span.old,
604
+ .datepicker table tr td span.new {
605
+ color: #999999;
606
+ }
607
+ .datepicker th.datepicker-switch {
608
+ width: 145px;
609
+ }
610
+ .datepicker thead tr:first-child th,
611
+ .datepicker tfoot tr th {
612
+ cursor: pointer;
613
+ }
614
+ .datepicker thead tr:first-child th:hover,
615
+ .datepicker tfoot tr th:hover {
616
+ background: #eeeeee;
617
+ }
618
+ .datepicker .cw {
619
+ font-size: 10px;
620
+ width: 12px;
621
+ padding: 0 2px 0 5px;
622
+ vertical-align: middle;
623
+ }
624
+ .datepicker thead tr:first-child th.cw {
625
+ cursor: default;
626
+ background-color: transparent;
627
+ }
628
+ .input-append.date .add-on i,
629
+ .input-prepend.date .add-on i {
630
+ display: block;
631
+ cursor: pointer;
632
+ width: 16px;
633
+ height: 16px;
634
+ }
635
+ .input-daterange input {
636
+ text-align: center;
637
+ }
638
+ .input-daterange input:first-child {
639
+ -webkit-border-radius: 3px 0 0 3px;
640
+ -moz-border-radius: 3px 0 0 3px;
641
+ border-radius: 3px 0 0 3px;
642
+ }
643
+ .input-daterange input:last-child {
644
+ -webkit-border-radius: 0 3px 3px 0;
645
+ -moz-border-radius: 0 3px 3px 0;
646
+ border-radius: 0 3px 3px 0;
647
+ }
648
+ .input-daterange .add-on {
649
+ display: inline-block;
650
+ width: auto;
651
+ min-width: 16px;
652
+ height: 18px;
653
+ padding: 4px 5px;
654
+ font-weight: normal;
655
+ line-height: 18px;
656
+ text-align: center;
657
+ text-shadow: 0 1px 0 #ffffff;
658
+ vertical-align: middle;
659
+ background-color: #eeeeee;
660
+ border: 1px solid #ccc;
661
+ margin-left: -5px;
662
+ margin-right: -5px;
663
+ }
backend/modules/appearance/resources/js/appearance.js CHANGED
@@ -1,782 +1,838 @@
1
- jQuery(function($) {
2
- var
3
- $color_picker = $('.bookly-js-color-picker'),
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"]'),
37
- $repeat_variant = $('.bookly-js-repeat-variant'),
38
- $repeat_variant_monthly = $('.bookly-js-repeat-variant-monthly'),
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 () {
66
- $(this).data('default', $(this).prop('checked'));
67
- });
68
- // Menu fix for WP 3.8.1
69
- $('#toplevel_page_ab-system > ul').css('margin-left', '0px');
70
-
71
- // Apply color from color picker.
72
- var applyColor = function() {
73
- var color = $color_picker.wpColorPicker('color'),
74
- color_important = color + '!important;';
75
- $('.bookly-progress-tracker').find('.active').css('color', color).find('.step').css('background', color);
76
- $('.bookly-js-mobile-step-1 label').css('color', color);
77
- $('.bookly-label-error').css('color', color);
78
- $('.bookly-js-actions > a').css('background-color', color);
79
- $('.bookly-js-mobile-next-step').css('background', color);
80
- $('.bookly-js-week-days label').css('background-color', color);
81
- $('.picker__frame').attr('style', 'background: ' + color_important);
82
- $('.picker__header').attr('style', 'border-bottom: ' + '1px solid ' + color_important);
83
- $('.picker__day').off().mouseenter(function() {
84
- $(this).attr('style', 'color: ' + color_important);
85
- }).mouseleave(function(){
86
- $(this).attr('style', $(this).hasClass('picker__day--selected') ? 'color: ' + color_important : '')
87
- });
88
- $('.picker__day--selected').attr('style', 'color: ' + color_important);
89
- $('.picker__button--clear').attr('style', 'color: ' + color_important);
90
- $('.picker__button--today').attr('style', 'color: ' + color_important);
91
- $('.bookly-extra-step .bookly-extras-thumb.bookly-extras-selected').css('border-color', color);
92
- $('.bookly-columnizer .bookly-day, .bookly-schedule-date,.bookly-pagination li.active').css({
93
- 'background': color,
94
- 'border-color': color
95
- });
96
- $('.bookly-columnizer .bookly-hour').off().hover(
97
- function() { // mouse-on
98
- $(this).css({
99
- 'color': color,
100
- 'border': '2px solid ' + color
101
- });
102
- $(this).find('.bookly-hour-icon').css({
103
- 'border-color': color,
104
- 'color': color
105
- });
106
- $(this).find('.bookly-hour-icon > span').css({
107
- 'background': color
108
- });
109
- },
110
- function() { // mouse-out
111
- $(this).css({
112
- 'color': '#333333',
113
- 'border': '1px solid #cccccc'
114
- });
115
- $(this).find('.bookly-hour-icon').css({
116
- 'border-color': '#333333',
117
- 'color': '#cccccc'
118
- });
119
- $(this).find('.bookly-hour-icon > span').css({
120
- 'background': '#cccccc'
121
- });
122
- }
123
- );
124
- $('.bookly-details-step label').css('color', color);
125
- $('.bookly-card-form label').css('color', color);
126
- $('.bookly-nav-tabs .ladda-button, .bookly-nav-steps .ladda-button, .bookly-btn, .bookly-round, .bookly-square').css('background-color', color);
127
- $('.bookly-triangle').css('border-bottom-color', color);
128
- $('#bookly-pickadate-style').html('.picker__nav--next:before { border-left: 6px solid ' + color_important + ' } .picker__nav--prev:before { border-right: 6px solid ' + color_important + ' }');
129
- };
130
-
131
- // Init color picker.
132
- $color_picker.wpColorPicker({
133
- change : applyColor
134
- });
135
-
136
- // Init editable elements.
137
- $editableElements.editable();
138
-
139
- // Show progress tracker.
140
- $show_progress_tracker.on('change', 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;
182
- }
183
- });
184
-
185
- // Dismiss help notice.
186
- $('#bookly-js-hint-alert').on('closed.bs.alert', function () {
187
- $.ajax({
188
- url: ajaxurl,
189
- data: { action: 'bookly_dismiss_appearance_notice', csrf_token : BooklyL10n.csrf_token }
190
- });
191
- });
192
-
193
- /**
194
- * Step Service
195
- */
196
-
197
- // Init calendar.
198
- $('.bookly-js-date-from').pickadate({
199
- formatSubmit : 'yyyy-mm-dd',
200
- format : BooklyL10n.date_format,
201
- min : true,
202
- clear : false,
203
- close : false,
204
- today : BooklyL10n.today,
205
- weekdaysShort : BooklyL10n.days,
206
- monthsFull : BooklyL10n.months,
207
- labelMonthNext : BooklyL10n.nextMonth,
208
- labelMonthPrev : BooklyL10n.prevMonth,
209
- onRender : applyColor,
210
- firstDay : BooklyL10n.start_of_week == 1
211
- });
212
-
213
- // Show price next to staff member name.
214
- $staff_name_with_price.on('change', function () {
215
- var staff = $('.bookly-js-select-employee').val();
216
- if (staff) {
217
- $('.bookly-js-select-employee').val(staff * -1);
218
- }
219
- $('.employee-name-price').toggle($staff_name_with_price.prop("checked"));
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();
315
- if (service) {
316
- $('.bookly-js-select-service').val(service * -1);
317
- }
318
- $('.service-name-duration').toggle($service_name_with_duration.prop("checked"));
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);
338
- });
339
-
340
-
341
- /**
342
- * Step Time
343
- */
344
-
345
- // Init calendar.
346
- $time_step_calendar.pickadate({
347
- formatSubmit : 'yyyy-mm-dd',
348
- format : BooklyL10n.date_format,
349
- min : true,
350
- weekdaysShort : BooklyL10n.days,
351
- monthsFull : BooklyL10n.months,
352
- labelMonthNext : BooklyL10n.nextMonth,
353
- labelMonthPrev : BooklyL10n.prevMonth,
354
- close : false,
355
- clear : false,
356
- today : false,
357
- closeOnSelect : false,
358
- onRender : applyColor,
359
- firstDay : BooklyL10n.start_of_week == 1,
360
- klass : {
361
- picker: 'picker picker--opened picker--focused'
362
- },
363
- onClose : function() {
364
- this.open(false);
365
- }
366
- });
367
- $time_step_calendar_wrap.find('.picker__holder').css({ top : '0px', left : '0px' });
368
-
369
- // Show calendar.
370
- $show_calendar.on('change', function() {
371
- if (this.checked) {
372
- $time_step_calendar_wrap.show();
373
- $day_multi_columns.find('.col3,.col4,.col5,.col6,.col7').hide();
374
- $day_multi_columns.find('.col2 button:gt(0)').attr('style', 'display: none !important');
375
- $day_one_column.find('.col2,.col3,.col4,.col5,.col6,.col7').hide();
376
- } else {
377
- $time_step_calendar_wrap.hide();
378
- $day_multi_columns.find('.col2 button:gt(0)').attr('style', 'display: block !important');
379
- $day_multi_columns.find('.col2 button.bookly-js-first-child').attr('style', 'background: ' + $color_picker.wpColorPicker('color') + '!important;display: block !important');
380
- $day_multi_columns.find('.col3,.col4,.col5,.col6,.col7').css('display','inline-block');
381
- $day_one_column.find('.col2,.col3,.col4,.col5,.col6,.col7').css('display','inline-block');
382
- }
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
-
405
- // Show day as one column.
406
- $show_day_one_column.change(function() {
407
- if (this.checked) {
408
- $day_one_column.show();
409
- $day_multi_columns.hide();
410
- } else {
411
- $day_one_column.hide();
412
- $day_multi_columns.show();
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
- */
459
-
460
- // Init calendar.
461
- $repeat_step_calendar.pickadate({
462
- formatSubmit : 'yyyy-mm-dd',
463
- format : BooklyL10n.date_format,
464
- min : true,
465
- clear : false,
466
- close : false,
467
- today : BooklyL10n.today,
468
- weekdaysShort : BooklyL10n.days,
469
- monthsFull : BooklyL10n.months,
470
- labelMonthNext : BooklyL10n.nextMonth,
471
- labelMonthPrev : BooklyL10n.prevMonth,
472
- onRender : applyColor,
473
- firstDay : BooklyL10n.start_of_week == 1
474
- });
475
- $repeat_variant.on('change', function () {
476
- $repeat_variants.hide();
477
- $('.bookly-js-variant-' + this.value).show()
478
- }).trigger('change');
479
-
480
- $repeat_variant_monthly.on('change', function () {
481
- $repeat_monthly_week_day.toggle(this.value != 'specific');
482
- $repeat_monthly_specific_day.toggle(this.value == 'specific');
483
- }).trigger('change');
484
-
485
- $repeat_weekly_week_day.on('change', function () {
486
- var $this = $(this);
487
- if ($this.is(':checked')) {
488
- $this.parent().not("[class*='active']").addClass('active');
489
- } else {
490
- $this.parent().removeClass('active');
491
- }
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
- */
512
-
513
- // Init phone field.
514
- if (BooklyL10n.intlTelInput.enabled) {
515
- $('.bookly-user-phone').intlTelInput({
516
- preferredCountries: [BooklyL10n.intlTelInput.country],
517
- initialCountry: BooklyL10n.intlTelInput.country,
518
- geoIpLookup: function (callback) {
519
- $.get('https://ipinfo.io', function() {}, 'jsonp').always(function(resp) {
520
- var countryCode = (resp && resp.country) ? resp.country : '';
521
- callback(countryCode);
522
- });
523
- },
524
- utilsScript: BooklyL10n.intlTelInput.utils
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');
550
- if (this.checked) {
551
- $('.bookly-details-full-name').css('display', 'none');
552
- $('.bookly-details-first-last-name').css('display', 'table');
553
- } else {
554
- $('.bookly-details-full-name').css('display', 'block');
555
- $('.bookly-details-first-last-name').css('display', 'none');
556
- }
557
- });
558
-
559
- // Show notes field.
560
- $show_notes_field.change(function () {
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
- */
608
-
609
- // Switch payment step view (single/several services).
610
- $('#bookly-payment-step-view').on('change', function () {
611
- $('.bookly-js-payment-single-app').toggle(this.value == 'single-app');
612
- $('.bookly-js-payment-several-apps').toggle(this.value == 'several-apps');
613
- });
614
-
615
- // Show credit card form.
616
- $('.bookly-payment-nav :radio').on('change', function () {
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
- */
627
-
628
- // Switch done step view (success/error).
629
- $('#bookly-done-step-view').on('change', function () {
630
- $('.bookly-js-done-success').toggle(this.value == 'booking-success');
631
- $('.bookly-js-done-limit-error').toggle(this.value == 'booking-limit-error');
632
- $('.bookly-js-done-processing').toggle(this.value == 'booking-processing');
633
- });
634
-
635
-
636
- /**
637
- * Misc.
638
- */
639
- $('.bookly-js-simple-popover').popover();
640
-
641
- // Custom CSS.
642
- $('#bookly-custom-css-save').on('click', function (e) {
643
- var $custom_css = $('#bookly-custom-css'),
644
- $modal = $('#bookly-custom-css-dialog');
645
-
646
- saved_css = $custom_css.val();
647
-
648
- var ladda = Ladda.create(this);
649
- ladda.start();
650
-
651
- $.ajax({
652
- url : ajaxurl,
653
- type : 'POST',
654
- data : {
655
- action : 'bookly_save_custom_css',
656
- csrf_token : BooklyL10n.csrf_token,
657
- custom_css : $custom_css.val()
658
- },
659
- dataType : 'json',
660
- success : function (response) {
661
- if (response.success) {
662
- $modal.modal('hide');
663
- booklyAlert({success : [response.data.message]});
664
- }
665
- },
666
- complete : function () {
667
- ladda.stop();
668
- }
669
- });
670
- });
671
-
672
- $('#bookly-custom-css-cancel').on('click', function (e) {
673
- var $custom_css = $('#bookly-custom-css'),
674
- $modal = $('#bookly-custom-css-dialog');
675
-
676
- $modal.modal('hide');
677
-
678
- $custom_css.val(saved_css);
679
- });
680
-
681
- $('#bookly-custom-css').keydown(function(e) {
682
- if(e.keyCode === 9) { //tab button
683
- var start = this.selectionStart;
684
- var end = this.selectionEnd;
685
-
686
- var $this = $(this);
687
- var value = $this.val();
688
-
689
- $this.val(value.substring(0, start)
690
- + "\t"
691
- + value.substring(end));
692
-
693
- this.selectionStart = this.selectionEnd = start + 1;
694
-
695
- e.preventDefault();
696
- }
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.
710
- 'bookly_app_service_name_with_duration' : Number($service_name_with_duration.prop('checked')),
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.
747
- $editableElements.each(function () {
748
- $.extend(data.options, $(this).editable('getValue', true));
749
- });
750
-
751
- // Update data and show spinner while updating.
752
- var ladda = Ladda.create(this);
753
- ladda.start();
754
- $.post(ajaxurl, data, function (response) {
755
- ladda.stop();
756
- booklyAlert({success : [BooklyL10n.saved]});
757
- });
758
- });
759
-
760
- // Reset options to defaults.
761
- $reset_button.on('click', function() {
762
- // Reset color.
763
- $color_picker.wpColorPicker('color', $color_picker.data('selected'));
764
-
765
- // Reset editable texts.
766
- $editableElements.each(function () {
767
- $(this).editable('setValue', $.extend({}, $(this).data('values')));
768
- });
769
-
770
- $checkboxes.each(function () {
771
- if ($(this).prop('checked') != $(this).data('default')) {
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
  });
1
+ jQuery(function($) {
2
+ var
3
+ $color_picker = $('.bookly-js-color-picker'),
4
+ $editableElements = $('.bookly-js-editable'),
5
+ $show_progress_tracker = $('#bookly-show-progress-tracker'),
6
+ $align_buttons_left = $('#bookly-align-buttons-left'),
7
+ $step_settings = $('#bookly-step-settings'),
8
+ $bookly_show_step_extras = $('#bookly-show-step-extras'),
9
+ $bookly_show_step_repeat = $('#bookly-show-step-repeat'),
10
+ $bookly_show_step_cart = $('#bookly-show-step-cart'),
11
+ // Service step.
12
+ $staff_name_with_price = $('#bookly-staff-name-with-price'),
13
+ $service_duration_with_price = $('#bookly-service-duration-with-price'),
14
+ $service_name_with_duration = $('#bookly-service-name-with-duration'),
15
+ $required_employee = $('#bookly-required-employee'),
16
+ $required_location = $('#bookly-required-location'),
17
+ $show_ratings = $('#bookly-show-ratings'),
18
+ $show_chain_appointments = $('#bookly-show-chain-appointments'),
19
+ $show_location = $('#bookly-show-location'),
20
+ $show_custom_duration = $('#bookly-show-custom-duration'),
21
+ $show_nop = $('#bookly-show-nop'),
22
+ $show_quantity = $('#bookly-show-quantity'),
23
+ // Time step.
24
+ $time_step_nop = $('#bookly-show-nop-on-time-step'),
25
+ $time_step_calendar = $('.bookly-js-selected-date'),
26
+ $time_step_calendar_wrap = $('.bookly-js-slot-calendar'),
27
+ $show_blocked_timeslots = $('#bookly-show-blocked-timeslots'),
28
+ $show_waiting_list = $('#bookly-show-waiting-list'),
29
+ $show_day_one_column = $('#bookly-show-day-one-column'),
30
+ $show_time_zone_switcher = $('#bookly-show-time-zone-switcher'),
31
+ $show_calendar = $('#bookly-show-calendar'),
32
+ $day_one_column = $('#bookly-day-one-column'),
33
+ $day_multi_columns = $('#bookly-day-multi-columns'),
34
+ $columnizer = $('.bookly-time-step .bookly-columnizer-wrap'),
35
+ // Step repeat.
36
+ $repeat_step_calendar = $('.bookly-js-repeat-until'),
37
+ $repeat_variants = $('[class^="bookly-js-variant"]'),
38
+ $repeat_variant = $('.bookly-js-repeat-variant'),
39
+ $repeat_variant_monthly = $('.bookly-js-repeat-variant-monthly'),
40
+ $repeat_weekly_week_day = $('.bookly-js-week-day'),
41
+ $repeat_monthly_specific_day = $('.bookly-js-monthly-specific-day'),
42
+ $repeat_monthly_week_day = $('.bookly-js-monthly-week-day'),
43
+ // Step Cart.
44
+ $show_cart_extras = $('#bookly-show-cart-extras'),
45
+ // Step details.
46
+ $required_details = $('#bookly-cst-required-details'),
47
+ $show_login_button = $('#bookly-show-login-button'),
48
+ $show_facebook_login_button = $('#bookly-show-facebook-login-button'),
49
+ $first_last_name = $('#bookly-cst-first-last-name'),
50
+ $confirm_email = $('#bookly-cst-show-email-confirm'),
51
+ $show_notes_field = $('#bookly-show-notes'),
52
+ $show_birthday_fields = $('#bookly-show-birthday'),
53
+ $show_address_fields = $('#bookly-show-address'),
54
+ $show_google_maps = $('#bookly-show-google-maps'),
55
+ $show_custom_fields = $('#bookly-show-custom-fields'),
56
+ $show_customer_information = $('#bookly-show-customer-information'),
57
+ $show_files = $('#bookly-show-files'),
58
+ // Step payment.
59
+ $show_coupons = $('#bookly-show-coupons'),
60
+ // Buttons.
61
+ $save_button = $('#ajax-send-appearance'),
62
+ $reset_button = $('button[type=reset]'),
63
+ $checkboxes = $('#bookly-appearance').find('input[type="checkbox"]'),
64
+ $selects = $('#bookly-appearance').find('select[data-default]')
65
+ ;
66
+
67
+ $checkboxes.each(function () {
68
+ $(this).data('default', $(this).prop('checked'));
69
+ });
70
+ // Menu fix for WP 3.8.1
71
+ $('#toplevel_page_ab-system > ul').css('margin-left', '0px');
72
+
73
+ // Apply color from color picker.
74
+ var applyColor = function() {
75
+ var color = $color_picker.wpColorPicker('color'),
76
+ color_important = color + '!important;';
77
+ $('.bookly-progress-tracker').find('.active').css('color', color).find('.step').css('background', color);
78
+ $('.bookly-js-mobile-step-1 label').css('color', color);
79
+ $('.bookly-label-error').css('color', color);
80
+ $('.bookly-js-actions > a').css('background-color', color);
81
+ $('.bookly-js-mobile-next-step').css('background', color);
82
+ $('.bookly-js-week-days label').css('background-color', color);
83
+ $('.picker__frame').attr('style', 'background: ' + color_important);
84
+ $('.picker__header').attr('style', 'border-bottom: ' + '1px solid ' + color_important);
85
+ $('.picker__day').off().mouseenter(function() {
86
+ $(this).attr('style', 'color: ' + color_important);
87
+ }).mouseleave(function(){
88
+ $(this).attr('style', $(this).hasClass('picker__day--selected') ? 'color: ' + color_important : '')
89
+ });
90
+ $('.picker__day--selected').attr('style', 'color: ' + color_important);
91
+ $('.picker__button--clear').attr('style', 'color: ' + color_important);
92
+ $('.picker__button--today').attr('style', 'color: ' + color_important);
93
+ $('.bookly-extra-step .bookly-extras-thumb.bookly-extras-selected').css('border-color', color);
94
+ $('.bookly-columnizer .bookly-day, .bookly-schedule-date,.bookly-pagination li.active').css({
95
+ 'background': color,
96
+ 'border-color': color
97
+ });
98
+ $('.bookly-columnizer .bookly-hour').off().hover(
99
+ function() { // mouse-on
100
+ $(this).css({
101
+ 'color': color,
102
+ 'border': '2px solid ' + color
103
+ });
104
+ $(this).find('.bookly-hour-icon').css({
105
+ 'border-color': color,
106
+ 'color': color
107
+ });
108
+ $(this).find('.bookly-hour-icon > span').css({
109
+ 'background': color
110
+ });
111
+ },
112
+ function() { // mouse-out
113
+ $(this).css({
114
+ 'color': '#333333',
115
+ 'border': '1px solid #cccccc'
116
+ });
117
+ $(this).find('.bookly-hour-icon').css({
118
+ 'border-color': '#333333',
119
+ 'color': '#cccccc'
120
+ });
121
+ $(this).find('.bookly-hour-icon > span').css({
122
+ 'background': '#cccccc'
123
+ });
124
+ }
125
+ );
126
+ $('.bookly-details-step label').css('color', color);
127
+ $('.bookly-card-form label').css('color', color);
128
+ $('.bookly-nav-tabs .ladda-button, .bookly-nav-steps .ladda-button, .bookly-btn, .bookly-round, .bookly-square').css('background-color', color);
129
+ $('.bookly-triangle').css('border-bottom-color', color);
130
+ $('#bookly-pickadate-style').html('.picker__nav--next:before { border-left: 6px solid ' + color_important + ' } .picker__nav--prev:before { border-right: 6px solid ' + color_important + ' }');
131
+ };
132
+
133
+ // Init color picker.
134
+ $color_picker.wpColorPicker({
135
+ change : applyColor
136
+ });
137
+
138
+ // Init editable elements.
139
+ $editableElements.editable();
140
+
141
+ // Show progress tracker.
142
+ $show_progress_tracker.on('change', function() {
143
+ $('.bookly-progress-tracker').toggle(this.checked);
144
+ }).trigger('change');
145
+
146
+ // Align buttons to the left
147
+ $align_buttons_left.on('change', function () {
148
+ if (this.checked) {
149
+ $('.bookly-nav-steps > div.bookly-right').removeClass('bookly-right').addClass('bookly-left');
150
+ } else {
151
+ $('.bookly-nav-steps > div.bookly-left').removeClass('bookly-left').addClass('bookly-right');
152
+ }
153
+ });
154
+
155
+ // Show steps.
156
+ $('.bookly-js-show-step').on('change', function () {
157
+ var target = $(this).data('target'),
158
+ $button = $('li.bookly-nav-item[data-target="#' + target + '"]'),
159
+ $step = $('div[data-step="' + target + '"]');
160
+ if ($(this).prop('checked')) {
161
+ $button.show();
162
+ $step.show();
163
+ } else {
164
+ if ($button.hasClass('active')) {
165
+ $('li.bookly-nav-item[data-target="#bookly-step-1"]').trigger('click');
166
+ }
167
+ $button.hide();
168
+ $step.hide();
169
+ }
170
+ // Hide/show cart buttons
171
+ if ( target == 'bookly-step-5') {
172
+ $('.bookly-js-go-to-cart').toggle( $(this).prop('checked') );
173
+ }
174
+
175
+ $('.bookly-progress-tracker > div:visible').each(function (num) {
176
+ $(this).find('.bookly-js-step-number').html(num + 1);
177
+ });
178
+ $('.bookly-js-appearance-steps > li:visible').each(function (num) {
179
+ $(this).find('.bookly-js-step-number').html(num + 1);
180
+ });
181
+ }).trigger('change');
182
+
183
+ // Show step specific settings.
184
+ $('li.bookly-nav-item').on('shown.bs.tab', function (e) {
185
+ $step_settings.children().hide();
186
+ switch (e.target.getAttribute('data-target')) {
187
+ case '#bookly-step-1': $step_settings.find('.bookly-js-service-settings').show(); break;
188
+ case '#bookly-step-3': $step_settings.find('.bookly-js-time-settings').show(); break;
189
+ case '#bookly-step-5': $step_settings.find('.bookly-js-cart-settings').show(); break;
190
+ case '#bookly-step-6': $step_settings.find('.bookly-js-details-settings').show(); break;
191
+ case '#bookly-step-7': $step_settings.find('.bookly-js-payment-settings').show(); break;
192
+ case '#bookly-step-8': $step_settings.find('.bookly-js-done-settings').show(); break;
193
+ }
194
+ });
195
+
196
+ // Dismiss help notice.
197
+ $('#bookly-js-hint-alert').on('closed.bs.alert', function () {
198
+ $.ajax({
199
+ url: ajaxurl,
200
+ data: { action: 'bookly_dismiss_appearance_notice', csrf_token : BooklyL10n.csrf_token }
201
+ });
202
+ });
203
+
204
+ /**
205
+ * Step Service
206
+ */
207
+
208
+ // Init calendar.
209
+ $('.bookly-js-date-from').pickadate({
210
+ formatSubmit : 'yyyy-mm-dd',
211
+ format : BooklyL10n.date_format,
212
+ min : true,
213
+ clear : false,
214
+ close : false,
215
+ today : BooklyL10n.today,
216
+ weekdaysShort : BooklyL10n.days,
217
+ monthsFull : BooklyL10n.months,
218
+ labelMonthNext : BooklyL10n.nextMonth,
219
+ labelMonthPrev : BooklyL10n.prevMonth,
220
+ onRender : applyColor,
221
+ firstDay : BooklyL10n.start_of_week == 1
222
+ });
223
+
224
+ // Show price next to staff member name.
225
+ $staff_name_with_price.on('change', function () {
226
+ var staff = $('.bookly-js-select-employee').val();
227
+ if (staff) {
228
+ $('.bookly-js-select-employee').val(staff * -1);
229
+ }
230
+ $('.employee-name-price').toggle($staff_name_with_price.prop("checked"));
231
+ $('.employee-name').toggle(!$staff_name_with_price.prop("checked"));
232
+ }).trigger('change');
233
+
234
+ if ($service_duration_with_price.prop("checked")) {
235
+ $('.bookly-js-select-duration').val(-1);
236
+ }
237
+
238
+ // Show price next to service duration.
239
+ $service_duration_with_price.on('change', function () {
240
+ var duration = $('.bookly-js-select-duration').val();
241
+ if (duration) {
242
+ $('.bookly-js-select-duration').val(duration * -1);
243
+ }
244
+ $('.bookly-js-duration-price').toggle($service_duration_with_price.prop("checked"));
245
+ $('.bookly-js-duration').toggle(!$service_duration_with_price.prop("checked"));
246
+ }).trigger('change');
247
+
248
+ $show_ratings.on('change', function () {
249
+ var state = $(this).prop('checked');
250
+ $('.bookly-js-select-employee option').each(function () {
251
+ if ($(this).val() != '0') {
252
+ if (!state) {
253
+ if ($(this).text().charAt(0) == '') {
254
+ $(this).text($(this).text().substring(5));
255
+ }
256
+ } else {
257
+ var rating = Math.round(10 * (Math.random() * 6 + 1)) / 10;
258
+ if (rating <= 5) {
259
+ $(this).text('★' + rating.toFixed(1) + ' ' + $(this).text());
260
+ }
261
+ }
262
+ }
263
+ });
264
+ }).trigger('change');
265
+
266
+ // Show chain appointments
267
+ $show_chain_appointments.on('change', function () {
268
+ $('.bookly-js-chain-appointments').toggle( this.checked );
269
+ });
270
+
271
+ // Show location
272
+ $show_location.on('change', function () {
273
+ $('.bookly-js-location').toggle( this.checked );
274
+ if (!this.disabled) {
275
+ if (this.checked) {
276
+ $required_location.closest('[data-toggle="popover"]').popover('destroy');
277
+ $required_location.prop('disabled', false);
278
+ } else {
279
+ $required_location.closest('[data-toggle="popover"]').popover();
280
+ $required_location.prop('checked', false).prop('disabled', true).trigger('change');
281
+ }
282
+ }
283
+ }).trigger('change');
284
+
285
+ // Show custom duration
286
+ $show_custom_duration.on('change', function () {
287
+ $('.bookly-js-custom-duration').toggle( this.checked );
288
+ if (this.checked) {
289
+ $service_duration_with_price.closest('[data-toggle="popover"]').popover('destroy');
290
+ $service_duration_with_price.prop('disabled', false);
291
+ } else {
292
+ $service_duration_with_price.closest('[data-toggle="popover"]').popover();
293
+ $service_duration_with_price.prop('checked', false).prop('disabled', true).trigger('change');
294
+ }
295
+ }).trigger('change');
296
+
297
+ // Show number of persons
298
+ $show_nop.on('change', function () {
299
+ $('.bookly-js-nop').toggle( this.checked );
300
+ if (this.checked) {
301
+ $time_step_nop.closest('[data-toggle="popover"]').popover('destroy');
302
+ $time_step_nop.prop('disabled', false);
303
+ } else {
304
+ $time_step_nop.closest('[data-toggle="popover"]').popover();
305
+ $time_step_nop.prop('checked', false).prop('disabled', true).trigger('change');
306
+ }
307
+ }).trigger('change');
308
+
309
+ // Show quantity
310
+ $show_quantity.on('change', function () {
311
+ $('.bookly-js-quantity').toggle( this.checked );
312
+ });
313
+
314
+ // Set max quantity
315
+ $('.bookly_multiply_appointments_quantity_max').on('save', function (e, params) {
316
+ var $options = '';
317
+ for (var x = 1; x <= params.newValue['bookly_multiply_appointments_quantity_max']; x++) {
318
+ $options += "<option>" + x + "</option>";
319
+ }
320
+ $('.bookly-js-select-quantity').html($options);
321
+ });
322
+
323
+ // Show duration next to service name.
324
+ $service_name_with_duration.on('change', function () {
325
+ var service = $('.bookly-js-select-service').val();
326
+ if (service) {
327
+ $('.bookly-js-select-service').val(service * -1);
328
+ }
329
+ $('.service-name-duration').toggle($service_name_with_duration.prop("checked"));
330
+ $('.service-name').toggle(!$service_name_with_duration.prop("checked"));
331
+ }).trigger('change');
332
+
333
+ // Show price next to service duration.
334
+ $service_duration_with_price.on('change', function () {
335
+ if ($(this).prop('checked')) {
336
+ $('.bookly-js-select-duration option[value="1"]').each(function () {
337
+ $(this).text($(this).attr('data-text-1'));
338
+ });
339
+ } else {
340
+ $('.bookly-js-select-duration option[value="1"]').each(function () {
341
+ $(this).text($(this).attr('data-text-0'));
342
+ });
343
+ }
344
+ }).trigger('change');
345
+
346
+ // Clickable week-days.
347
+ $repeat_weekly_week_day.on('change', function () {
348
+ $(this).parent().toggleClass('active', this.checked);
349
+ });
350
+
351
+
352
+ /**
353
+ * Step Time
354
+ */
355
+
356
+ // Init calendar.
357
+ $time_step_calendar.pickadate({
358
+ formatSubmit : 'yyyy-mm-dd',
359
+ format : BooklyL10n.date_format,
360
+ min : true,
361
+ weekdaysShort : BooklyL10n.days,
362
+ monthsFull : BooklyL10n.months,
363
+ labelMonthNext : BooklyL10n.nextMonth,
364
+ labelMonthPrev : BooklyL10n.prevMonth,
365
+ close : false,
366
+ clear : false,
367
+ today : false,
368
+ closeOnSelect : false,
369
+ onRender : applyColor,
370
+ firstDay : BooklyL10n.start_of_week == 1,
371
+ klass : {
372
+ picker: 'picker picker--opened picker--focused'
373
+ },
374
+ onClose : function() {
375
+ this.open(false);
376
+ }
377
+ });
378
+ $time_step_calendar_wrap.find('.picker__holder').css({ top : '0px', left : '0px' });
379
+
380
+ // Show calendar.
381
+ $show_calendar.on('change', function() {
382
+ if (this.checked) {
383
+ $time_step_calendar_wrap.show();
384
+ $day_multi_columns.find('.col3,.col4,.col5,.col6,.col7').hide();
385
+ $day_multi_columns.find('.col2 button:gt(0)').attr('style', 'display: none !important');
386
+ $day_one_column.find('.col2,.col3,.col4,.col5,.col6,.col7').hide();
387
+ } else {
388
+ $time_step_calendar_wrap.hide();
389
+ $day_multi_columns.find('.col2 button:gt(0)').attr('style', 'display: block !important');
390
+ $day_multi_columns.find('.col2 button.bookly-js-first-child').attr('style', 'background: ' + $color_picker.wpColorPicker('color') + '!important;display: block !important');
391
+ $day_multi_columns.find('.col3,.col4,.col5,.col6,.col7').css('display','inline-block');
392
+ $day_one_column.find('.col2,.col3,.col4,.col5,.col6,.col7').css('display','inline-block');
393
+ }
394
+ }).trigger('change');
395
+
396
+ // Show blocked time slots.
397
+ $show_blocked_timeslots.on('change', function () {
398
+ if (this.checked) {
399
+ $('.bookly-hour.no-booked').removeClass('no-booked').addClass('booked');
400
+ $('.bookly-column .bookly-hour.booked .bookly-time-additional', $columnizer).text('');
401
+ } else {
402
+ $('.bookly-hour.booked').removeClass('booked').addClass('no-booked');
403
+ if ($time_step_nop.prop('checked')) {
404
+ $('.bookly-column .bookly-hour:not(.booked):not(.bookly-slot-in-waiting-list) .bookly-time-additional', $columnizer).each(function () {
405
+ var nop = Math.ceil(Math.random() * 9);
406
+ if (BooklyL10n.nop_format == 'busy') {
407
+ $(this).text('[' + nop + '/10]');
408
+ } else {
409
+ $(this).text('[' + nop + ']');
410
+ }
411
+ });
412
+ }
413
+ }
414
+ });
415
+
416
+ // Show day as one column.
417
+ $show_day_one_column.change(function() {
418
+ if (this.checked) {
419
+ $day_one_column.show();
420
+ $day_multi_columns.hide();
421
+ } else {
422
+ $day_one_column.hide();
423
+ $day_multi_columns.show();
424
+ }
425
+ });
426
+
427
+ // Show time zone switcher
428
+ $show_time_zone_switcher.on('change', function() {
429
+ $('.bookly-js-time-zone-switcher').toggle(this.checked);
430
+ }).trigger('change');
431
+
432
+ // Show nop/capacity
433
+ $time_step_nop.on('change', function () {
434
+ if (this.checked) {
435
+ $('.bookly-column', $columnizer).addClass('bookly-column-wide');
436
+ $('.bookly-column .bookly-hour:not(.booked):not(.bookly-slot-in-waiting-list) .bookly-time-additional', $columnizer).each(function () {
437
+ var nop = Math.ceil(Math.random() * 9);
438
+ if (BooklyL10n.nop_format == 'busy') {
439
+ $(this).text('[' + nop + '/10]');
440
+ } else {
441
+ $(this).text('[' + nop + ']');
442
+ }
443
+ });
444
+ $('.bookly-column.col5', $columnizer).hide();
445
+ $('.bookly-column.col6', $columnizer).hide();
446
+ $('.bookly-column.col7', $columnizer).hide();
447
+ } else {
448
+ $('.bookly-column', $columnizer).removeClass('bookly-column-wide');
449
+ $('.bookly-column .bookly-hour:not(.bookly-slot-in-waiting-list) .bookly-time-additional', $columnizer).text('');
450
+ if (!$show_calendar.prop('checked')) {
451
+ $('.bookly-column', $columnizer).removeClass('bookly-column-wide').show();
452
+ }
453
+ }
454
+ }).trigger('change');
455
+
456
+ $show_waiting_list.on('change', function () {
457
+ if (this.checked) {
458
+ $('.bookly-column .bookly-hour.no-waiting-list, .bookly-column .bookly-hour.bookly-slot-in-waiting-list').each(function () {
459
+ $(this).removeClass('no-waiting-list').addClass('bookly-slot-in-waiting-list').find('.bookly-time-additional').text('(' + Math.floor(Math.random() * 10) + ')');
460
+ })
461
+ } else {
462
+ $('.bookly-column .bookly-hour.bookly-slot-in-waiting-list').removeClass('bookly-slot-in-waiting-list').addClass('no-waiting-list').find('.bookly-time-additional').text('');
463
+ $time_step_nop.trigger('change');
464
+ }
465
+ }).trigger('change');
466
+
467
+ /**
468
+ * Step repeat.
469
+ */
470
+
471
+ // Init calendar.
472
+ $repeat_step_calendar.pickadate({
473
+ formatSubmit : 'yyyy-mm-dd',
474
+ format : BooklyL10n.date_format,
475
+ min : true,
476
+ clear : false,
477
+ close : false,
478
+ today : BooklyL10n.today,
479
+ weekdaysShort : BooklyL10n.days,
480
+ monthsFull : BooklyL10n.months,
481
+ labelMonthNext : BooklyL10n.nextMonth,
482
+ labelMonthPrev : BooklyL10n.prevMonth,
483
+ onRender : applyColor,
484
+ firstDay : BooklyL10n.start_of_week == 1
485
+ });
486
+ $repeat_variant.on('change', function () {
487
+ $repeat_variants.hide();
488
+ $('.bookly-js-variant-' + this.value).show()
489
+ }).trigger('change');
490
+
491
+ $repeat_variant_monthly.on('change', function () {
492
+ $repeat_monthly_week_day.toggle(this.value != 'specific');
493
+ $repeat_monthly_specific_day.toggle(this.value == 'specific');
494
+ }).trigger('change');
495
+
496
+ $repeat_weekly_week_day.on('change', function () {
497
+ var $this = $(this);
498
+ if ($this.is(':checked')) {
499
+ $this.parent().not("[class*='active']").addClass('active');
500
+ } else {
501
+ $this.parent().removeClass('active');
502
+ }
503
+ });
504
+
505
+
506
+ /**
507
+ * Step Repeat.
508
+ */
509
+ $bookly_show_step_repeat.change(function () {
510
+ $('.bookly-js-repeat-enabled').toggle(this.checked);
511
+ }).trigger('change');
512
+
513
+ /**
514
+ * Step Cart
515
+ */
516
+ $show_cart_extras.change(function () {
517
+ $('.bookly-js-extras-cart').toggle(this.checked);
518
+ }).trigger('change');
519
+
520
+ /**
521
+ * Step Details
522
+ */
523
+
524
+ // Init phone field.
525
+ if (BooklyL10n.intlTelInput.enabled) {
526
+ $('.bookly-user-phone').intlTelInput({
527
+ preferredCountries: [BooklyL10n.intlTelInput.country],
528
+ initialCountry: BooklyL10n.intlTelInput.country,
529
+ geoIpLookup: function (callback) {
530
+ $.get('https://ipinfo.io', function() {}, 'jsonp').always(function(resp) {
531
+ var countryCode = (resp && resp.country) ? resp.country : '';
532
+ callback(countryCode);
533
+ });
534
+ },
535
+ utilsScript: BooklyL10n.intlTelInput.utils
536
+ });
537
+ }
538
+
539
+ // Show login button.
540
+ $show_login_button.change(function () {
541
+ $('#bookly-login-button').toggle(this.checked);
542
+ }).trigger('change');
543
+
544
+ // Show Facebook login button.
545
+ $show_facebook_login_button.change(function () {
546
+ if ($(this).data('appid') == '') {
547
+ $('#bookly-facebook-warning').modal('show');
548
+ this.checked = false;
549
+ } else {
550
+ $('#bookly-facebook-login-button').toggle(this.checked);
551
+ }
552
+ });
553
+
554
+ if ($show_facebook_login_button.prop('checked')) {
555
+ $show_facebook_login_button.trigger('change');
556
+ }
557
+
558
+ // Show first and last name.
559
+ $first_last_name.on('change', function () {
560
+ $first_last_name.popover('toggle');
561
+ if (this.checked) {
562
+ $('.bookly-js-details-full-name').addClass('collapse');
563
+ $('.bookly-js-details-first-last-name').removeClass('collapse');
564
+ if ($confirm_email.is(':checked')) {
565
+ $('.bookly-js-details-email').removeClass('collapse');
566
+ $('.bookly-js-details-confirm').removeClass('collapse');
567
+ $('.bookly-js-details-email-confirm').addClass('collapse');
568
+ } else {
569
+ $('.bookly-js-details-email').removeClass('collapse');
570
+ $('.bookly-js-details-confirm').addClass('collapse');
571
+ $('.bookly-js-details-email-confirm').addClass('collapse');
572
+ }
573
+ } else {
574
+ $('.bookly-js-details-full-name').removeClass('collapse');
575
+ $('.bookly-js-details-first-last-name').addClass('collapse');
576
+ if ($confirm_email.is(':checked')) {
577
+ $('.bookly-js-details-email').addClass('collapse');
578
+ $('.bookly-js-details-confirm').addClass('collapse');
579
+ $('.bookly-js-details-email-confirm').removeClass('collapse');
580
+ } else {
581
+ $('.bookly-js-details-email').removeClass('collapse');
582
+ $('.bookly-js-details-confirm').addClass('collapse');
583
+ $('.bookly-js-details-email-confirm').addClass('collapse');
584
+ }
585
+ }
586
+ });
587
+
588
+ // Show first and last name.
589
+ $confirm_email.on('change', function () {
590
+ if (this.checked) {
591
+ if ($first_last_name.is(':checked')) {
592
+ $('.bookly-js-details-email').removeClass('collapse');
593
+ $('.bookly-js-details-confirm').removeClass('collapse');
594
+ $('.bookly-js-details-email-confirm').addClass('collapse');
595
+ } else {
596
+ $('.bookly-js-details-email').addClass('collapse');
597
+ $('.bookly-js-details-confirm').addClass('collapse');
598
+ $('.bookly-js-details-email-confirm').removeClass('collapse');
599
+ }
600
+ } else {
601
+ if ($first_last_name.is(':checked')) {
602
+ $('.bookly-js-details-email').removeClass('collapse');
603
+ $('.bookly-js-details-confirm').addClass('collapse');
604
+ $('.bookly-js-details-email-confirm').addClass('collapse');
605
+ } else {
606
+ $('.bookly-js-details-email').removeClass('collapse');
607
+ $('.bookly-js-details-confirm').addClass('collapse');
608
+ $('.bookly-js-details-email-confirm').addClass('collapse');
609
+ }
610
+ }
611
+ });
612
+
613
+ // Show notes field.
614
+ $show_notes_field.change(function () {
615
+ $('#bookly-js-notes').toggle(this.checked);
616
+ }).trigger('change');
617
+
618
+ // Show birthday fields
619
+ $show_birthday_fields.change(function () {
620
+ $('#bookly-js-birthday').toggle(this.checked);
621
+ }).trigger('change');
622
+
623
+ // Show address fields
624
+ $show_address_fields.change(function () {
625
+ $('#bookly-js-address').toggle(this.checked);
626
+ if (this.checked) {
627
+ $show_google_maps.closest('[data-toggle="popover"]').popover('destroy');
628
+ $show_google_maps.prop('disabled', false);
629
+ } else {
630
+ $show_google_maps.closest('[data-toggle="popover"]').popover();
631
+ $show_google_maps.prop('checked', false).prop('disabled', true).trigger('change');
632
+ }
633
+ }).trigger('change');
634
+
635
+ // Show address fields
636
+ $show_google_maps.change(function () {
637
+ $('.bookly-js-google-maps').toggle(this.checked);
638
+ }).trigger('change');
639
+
640
+ $show_custom_fields.change(function () {
641
+ $('.bookly-js-custom-fields').toggle(this.checked);
642
+ if (this.checked) {
643
+ $show_files.closest('[data-toggle="popover"]').popover('destroy');
644
+ $show_files.prop('disabled', false);
645
+ } else {
646
+ $show_files.closest('[data-toggle="popover"]').popover();
647
+ $show_files.prop('checked', false).prop('disabled', true).trigger('change');
648
+ }
649
+ }).trigger('change');
650
+
651
+ $show_files.change(function () {
652
+ $('.bookly-js-files').toggle(this.checked);
653
+ }).trigger('change');
654
+
655
+ $show_customer_information.change(function () {
656
+ $('.bookly-js-customer-information').toggle(this.checked);
657
+ }).trigger('change');
658
+
659
+ /**
660
+ * Step Payment.
661
+ */
662
+
663
+ // Switch payment step view (single/several services).
664
+ $('#bookly-payment-step-view').on('change', function () {
665
+ $('.bookly-js-payment-single-app').toggle(this.value == 'single-app');
666
+ $('.bookly-js-payment-several-apps').toggle(this.value == 'several-apps');
667
+ });
668
+
669
+ // Show credit card form.
670
+ $('.bookly-payment-nav :radio').on('change', function () {
671
+ $('form.bookly-card-form').toggle(this.id == 'bookly-card-payment');
672
+ });
673
+
674
+ $show_coupons.on('change', function () {
675
+ $('.bookly-js-payment-coupons').toggle( this.checked );
676
+ });
677
+
678
+ /**
679
+ * Step Done.
680
+ */
681
+
682
+ // Switch done step view (success/error).
683
+ $('#bookly-done-step-view').on('change', function () {
684
+ $('.bookly-js-done-success').toggle(this.value == 'booking-success');
685
+ $('.bookly-js-done-limit-error').toggle(this.value == 'booking-limit-error');
686
+ $('.bookly-js-done-processing').toggle(this.value == 'booking-processing');
687
+ });
688
+
689
+
690
+ /**
691
+ * Misc.
692
+ */
693
+ $('.bookly-js-simple-popover').popover();
694
+
695
+ // Custom CSS.
696
+ $('#bookly-custom-css-save').on('click', function (e) {
697
+ var $custom_css = $('#bookly-custom-css'),
698
+ $modal = $('#bookly-custom-css-dialog');
699
+
700
+ saved_css = $custom_css.val();
701
+
702
+ var ladda = Ladda.create(this);
703
+ ladda.start();
704
+
705
+ $.ajax({
706
+ url : ajaxurl,
707
+ type : 'POST',
708
+ data : {
709
+ action : 'bookly_save_custom_css',
710
+ csrf_token : BooklyL10n.csrf_token,
711
+ custom_css : $custom_css.val()
712
+ },
713
+ dataType : 'json',
714
+ success : function (response) {
715
+ if (response.success) {
716
+ $modal.modal('hide');
717
+ booklyAlert({success : [response.data.message]});
718
+ }
719
+ },
720
+ complete : function () {
721
+ ladda.stop();
722
+ }
723
+ });
724
+ });
725
+
726
+ $('#bookly-custom-css-cancel').on('click', function (e) {
727
+ var $custom_css = $('#bookly-custom-css'),
728
+ $modal = $('#bookly-custom-css-dialog');
729
+
730
+ $modal.modal('hide');
731
+
732
+ $custom_css.val(saved_css);
733
+ });
734
+
735
+ $('#bookly-custom-css').keydown(function(e) {
736
+ if(e.keyCode === 9) { //tab button
737
+ var start = this.selectionStart;
738
+ var end = this.selectionEnd;
739
+
740
+ var $this = $(this);
741
+ var value = $this.val();
742
+
743
+ $this.val(value.substring(0, start)
744
+ + "\t"
745
+ + value.substring(end));
746
+
747
+ this.selectionStart = this.selectionEnd = start + 1;
748
+
749
+ e.preventDefault();
750
+ }
751
+ });
752
+
753
+ // Save options.
754
+ $save_button.on('click', function (e) {
755
+ e.preventDefault();
756
+ // Prepare data.
757
+ var data = {
758
+ action : 'bookly_update_appearance_options',
759
+ csrf_token: BooklyL10n.csrf_token,
760
+ options : {
761
+ // Color.
762
+ 'bookly_app_color' : $color_picker.wpColorPicker('color'),
763
+ // Checkboxes.
764
+ 'bookly_app_service_name_with_duration' : Number($service_name_with_duration.prop('checked')),
765
+ 'bookly_app_show_blocked_timeslots' : Number($show_blocked_timeslots.prop('checked')),
766
+ 'bookly_app_show_calendar' : Number($show_calendar.prop('checked')),
767
+ 'bookly_app_show_day_one_column' : Number($show_day_one_column.prop('checked')),
768
+ 'bookly_app_show_time_zone_switcher' : Number($show_time_zone_switcher.prop('checked')),
769
+ 'bookly_app_show_login_button' : Number($show_login_button.prop('checked')),
770
+ 'bookly_app_show_facebook_login_button' : Number($show_facebook_login_button.prop('checked')),
771
+ 'bookly_app_show_notes' : Number($show_notes_field.prop('checked')),
772
+ 'bookly_app_show_birthday' : Number($show_birthday_fields.prop('checked')),
773
+ 'bookly_app_show_address' : Number($show_address_fields.prop('checked')),
774
+ 'bookly_app_show_progress_tracker' : Number($show_progress_tracker.prop('checked')),
775
+ 'bookly_app_align_buttons_left' : Number($align_buttons_left.prop('checked')),
776
+ 'bookly_app_staff_name_with_price' : Number($staff_name_with_price.prop('checked')),
777
+ 'bookly_app_service_duration_with_price': Number($service_duration_with_price.prop('checked')),
778
+ 'bookly_app_required_employee' : Number($required_employee.prop('checked')),
779
+ 'bookly_app_required_location' : Number($required_location.prop('checked')),
780
+ 'bookly_group_booking_app_show_nop' : Number($time_step_nop.prop('checked')),
781
+ 'bookly_ratings_app_show_on_frontend' : Number($show_ratings.prop('checked')),
782
+ 'bookly_cst_required_details' : $required_details.val() == 'both' ? ['phone', 'email'] : [$required_details.val()],
783
+ 'bookly_cst_first_last_name' : Number($first_last_name.prop('checked')),
784
+ 'bookly_app_show_email_confirm' : Number($confirm_email.prop('checked')),
785
+ 'bookly_service_extras_enabled' : Number($bookly_show_step_extras.prop('checked')),
786
+ 'bookly_recurring_appointments_enabled' : Number($bookly_show_step_repeat.prop('checked')),
787
+ 'bookly_cart_enabled' : Number($bookly_show_step_cart.prop('checked')),
788
+ 'bookly_chain_appointments_enabled' : Number($show_chain_appointments.prop('checked')),
789
+ 'bookly_coupons_enabled' : Number($show_coupons.prop('checked')),
790
+ 'bookly_custom_fields_enabled' : Number($show_custom_fields.prop('checked')),
791
+ 'bookly_customer_information_enabled' : Number($show_customer_information.prop('checked')),
792
+ 'bookly_files_enabled' : Number($show_files.prop('checked')),
793
+ 'bookly_waiting_list_enabled' : Number($show_waiting_list.prop('checked')),
794
+ 'bookly_google_maps_address_enabled' : Number($show_google_maps.prop('checked')),
795
+ 'bookly_service_extras_show_in_cart' : Number($show_cart_extras.prop('checked')),
796
+ 'bookly_locations_enabled' : Number($show_location.prop('checked')),
797
+ 'bookly_custom_duration_enabled' : Number($show_custom_duration.prop('checked')),
798
+ 'bookly_group_booking_enabled' : Number($show_nop.prop('checked')),
799
+ 'bookly_multiply_appointments_enabled' : Number($show_quantity.prop('checked'))
800
+ }
801
+ };
802
+ // Add data from editable elements.
803
+ $editableElements.each(function () {
804
+ $.extend(data.options, $(this).editable('getValue', true));
805
+ });
806
+
807
+ // Update data and show spinner while updating.
808
+ var ladda = Ladda.create(this);
809
+ ladda.start();
810
+ $.post(ajaxurl, data, function (response) {
811
+ ladda.stop();
812
+ booklyAlert({success : [BooklyL10n.saved]});
813
+ });
814
+ });
815
+
816
+ // Reset options to defaults.
817
+ $reset_button.on('click', function() {
818
+ // Reset color.
819
+ $color_picker.wpColorPicker('color', $color_picker.data('selected'));
820
+
821
+ // Reset editable texts.
822
+ $editableElements.each(function () {
823
+ $(this).editable('setValue', $.extend({}, $(this).data('values')));
824
+ });
825
+
826
+ $checkboxes.each(function () {
827
+ if ($(this).prop('checked') != $(this).data('default')) {
828
+ $(this).prop('checked', $(this).data('default')).trigger('change');
829
+ }
830
+ });
831
+ $selects.each(function () {
832
+ if ($(this).val() != $(this).data('default')) {
833
+ $(this).val($(this).data('default')).trigger('change');
834
+ }
835
+ });
836
+ $first_last_name.popover('hide');
837
+ });
838
  });
backend/modules/appearance/resources/js/bootstrap-editable.bookly.js CHANGED
@@ -1,125 +1,125 @@
1
- jQuery(function($) {
2
- var Bookly = function (options) {
3
- this.init('bookly', options, Bookly.defaults);
4
- };
5
-
6
- // Inherit from Abstract input.
7
- $.fn.editableutils.inherit(Bookly, $.fn.editabletypes.abstractinput);
8
-
9
- $.extend(Bookly.prototype, {
10
- html2value: function(html) {
11
- return $.extend({}, $(this.options.scope).data('values'));
12
- },
13
- value2html: function (values, element) {
14
- $.each(values, function (option_name, option_value) {
15
- // Find all elements which display option value.
16
- $('.bookly-js-option.' + option_name).each(function () {
17
- var $this = $(this);
18
- if (!$this.hasClass('editable') || $this.is(element)) {
19
- // Update text.
20
- $this.text(option_value);
21
- }
22
- });
23
- });
24
- },
25
- activate: function () {
26
- this.$tpl.find(':input:first').focus();
27
- },
28
- value2input: function (values) {
29
- var _this = this;
30
- this.$tpl.empty();
31
- $.each(values, function (option_name, option_value) {
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.
89
- this.$tpl.closest('form').find('.bookly-js-codes').html($(this.options.scope).data('codes'));
90
- },
91
- input2value: function () {
92
- var _this = this;
93
- var values = {};
94
- this.$tpl.find(':input').each(function () {
95
- values[this.name] = this.value;
96
- // Find all elements which display option value.
97
- var option_name = this.name;
98
- var option_value = this.value;
99
- $('.bookly-js-option.' + option_name).each(function () {
100
- var $this = $(this);
101
- if ($this.hasClass('editable') && !$this.is(_this.$tpl)) {
102
- // Update editable value.
103
- var val = $this.editable('getValue', true);
104
- val[option_name] = option_value;
105
- $this.editable('setValue', val);
106
- }
107
- });
108
- });
109
-
110
- return values;
111
- }
112
- });
113
-
114
- Bookly.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
115
- tpl: '<div/>',
116
- fieldType: 'input'
117
- });
118
-
119
- $.fn.editabletypes.bookly = Bookly;
120
-
121
- // Set template for popovers and editable form.
122
- $.fn.popover.Constructor.DEFAULTS.template = '<div class="popover"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>';
123
- $.fn.editableform.template = '<form class="form-inline editableform"><div class="control-group"><div><div class="editable-input"></div><div class="editable-buttons"></div></div><div class="bookly-js-codes"></div><div class="editable-error-block"></div></div></form>';
124
- $.fn.editableform.buttons = '<div class="btn-group btn-group-sm"><button type="submit" class="btn btn-success editable-submit"><span class="glyphicon glyphicon-ok"></span></button><button type="button" class="btn btn-default editable-cancel"><span class="glyphicon glyphicon-remove"></span></button></div>';
125
  });
1
+ jQuery(function($) {
2
+ var Bookly = function (options) {
3
+ this.init('bookly', options, Bookly.defaults);
4
+ };
5
+
6
+ // Inherit from Abstract input.
7
+ $.fn.editableutils.inherit(Bookly, $.fn.editabletypes.abstractinput);
8
+
9
+ $.extend(Bookly.prototype, {
10
+ html2value: function(html) {
11
+ return $.extend({}, $(this.options.scope).data('values'));
12
+ },
13
+ value2html: function (values, element) {
14
+ $.each(values, function (option_name, option_value) {
15
+ // Find all elements which display option value.
16
+ $('.bookly-js-option[data-option="' + option_name + '"]').each(function () {
17
+ var $this = $(this);
18
+ if (!$this.hasClass('editable') || $this.is(element)) {
19
+ // Update text.
20
+ $this.text(option_value);
21
+ }
22
+ });
23
+ });
24
+ },
25
+ activate: function () {
26
+ this.$tpl.find(':input:first').focus();
27
+ },
28
+ value2input: function (values) {
29
+ var _this = this;
30
+ this.$tpl.empty();
31
+ $.each(values, function (option_name, option_value) {
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.
89
+ this.$tpl.closest('form').find('.bookly-js-codes').html($(this.options.scope).data('codes'));
90
+ },
91
+ input2value: function () {
92
+ var _this = this;
93
+ var values = {};
94
+ this.$tpl.find(':input').each(function () {
95
+ values[this.name] = this.value;
96
+ // Find all elements which display option value.
97
+ var option_name = this.name;
98
+ var option_value = this.value;
99
+ $('.bookly-js-option.' + option_name).each(function () {
100
+ var $this = $(this);
101
+ if ($this.hasClass('editable') && !$this.is(_this.$tpl)) {
102
+ // Update editable value.
103
+ var val = $this.editable('getValue', true);
104
+ val[option_name] = option_value;
105
+ $this.editable('setValue', val);
106
+ }
107
+ });
108
+ });
109
+
110
+ return values;
111
+ }
112
+ });
113
+
114
+ Bookly.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
115
+ tpl: '<div/>',
116
+ fieldType: 'input'
117
+ });
118
+
119
+ $.fn.editabletypes.bookly = Bookly;
120
+
121
+ // Set template for popovers and editable form.
122
+ $.fn.popover.Constructor.DEFAULTS.template = '<div class="popover"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>';
123
+ $.fn.editableform.template = '<form class="form-inline editableform"><div class="control-group"><div><div class="editable-input"></div><div class="editable-buttons"></div></div><div class="bookly-js-codes"></div><div class="editable-error-block"></div></div></form>';
124
+ $.fn.editableform.buttons = '<div class="btn-group btn-group-sm"><button type="submit" class="btn btn-success editable-submit"><span class="glyphicon glyphicon-ok"></span></button><button type="button" class="btn btn-default editable-cancel"><span class="glyphicon glyphicon-remove"></span></button></div>';
125
  });
backend/modules/appearance/resources/js/bootstrap-editable.min.js CHANGED
@@ -1,7 +1,7 @@
1
- /*! X-editable - v1.5.1
2
- * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
3
- * http://github.com/vitalets/x-editable
4
- * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
5
- !function(a){"use strict";var b=function(b,c){this.options=a.extend({},a.fn.editableform.defaults,c),this.$div=a(b),this.options.scope||(this.options.scope=this)};b.prototype={constructor:b,initInput:function(){this.input=this.options.input,this.value=this.input.str2value(this.options.value),this.input.prerender()},initTemplate:function(){this.$form=a(a.fn.editableform.template)},initButtons:function(){var b=this.$form.find(".editable-buttons");b.append(a.fn.editableform.buttons),"bottom"===this.options.showbuttons&&b.addClass("editable-buttons-bottom")},render:function(){this.$loading=a(a.fn.editableform.loading),this.$div.empty().append(this.$loading),this.initTemplate(),this.options.showbuttons?this.initButtons():this.$form.find(".editable-buttons").remove(),this.showLoading(),this.isSaving=!1,this.$div.triggerHandler("rendering"),this.initInput(),this.$form.find("div.editable-input").append(this.input.$tpl),this.$div.append(this.$form),a.when(this.input.render()).then(a.proxy(function(){if(this.options.showbuttons||this.input.autosubmit(),this.$form.find(".editable-cancel").click(a.proxy(this.cancel,this)),this.input.error)this.error(this.input.error),this.$form.find(".editable-submit").attr("disabled",!0),this.input.$input.attr("disabled",!0),this.$form.submit(function(a){a.preventDefault()});else{this.error(!1),this.input.$input.removeAttr("disabled"),this.$form.find(".editable-submit").removeAttr("disabled");var b=null===this.value||void 0===this.value||""===this.value?this.options.defaultValue:this.value;this.input.value2input(b),this.$form.submit(a.proxy(this.submit,this))}this.$div.triggerHandler("rendered"),this.showForm(),this.input.postrender&&this.input.postrender()},this))},cancel:function(){this.$div.triggerHandler("cancel")},showLoading:function(){var a,b;this.$form?(a=this.$form.outerWidth(),b=this.$form.outerHeight(),a&&this.$loading.width(a),b&&this.$loading.height(b),this.$form.hide()):(a=this.$loading.parent().width(),a&&this.$loading.width(a)),this.$loading.show()},showForm:function(a){this.$loading.hide(),this.$form.show(),a!==!1&&this.input.activate(),this.$div.triggerHandler("show")},error:function(b){var c,d=this.$form.find(".control-group"),e=this.$form.find(".editable-error-block");if(b===!1)d.removeClass(a.fn.editableform.errorGroupClass),e.removeClass(a.fn.editableform.errorBlockClass).empty().hide();else{if(b){c=(""+b).split("\n");for(var f=0;f<c.length;f++)c[f]=a("<div>").text(c[f]).html();b=c.join("<br>")}d.addClass(a.fn.editableform.errorGroupClass),e.addClass(a.fn.editableform.errorBlockClass).html(b).show()}},submit:function(b){b.stopPropagation(),b.preventDefault();var c=this.input.input2value(),d=this.validate(c);if("object"===a.type(d)&&void 0!==d.newValue){if(c=d.newValue,this.input.value2input(c),"string"==typeof d.msg)return this.error(d.msg),this.showForm(),void 0}else if(d)return this.error(d),this.showForm(),void 0;if(!this.options.savenochange&&this.input.value2str(c)==this.input.value2str(this.value))return this.$div.triggerHandler("nochange"),void 0;var e=this.input.value2submit(c);this.isSaving=!0,a.when(this.save(e)).done(a.proxy(function(a){this.isSaving=!1;var b="function"==typeof this.options.success?this.options.success.call(this.options.scope,a,c):null;return b===!1?(this.error(!1),this.showForm(!1),void 0):"string"==typeof b?(this.error(b),this.showForm(),void 0):(b&&"object"==typeof b&&b.hasOwnProperty("newValue")&&(c=b.newValue),this.error(!1),this.value=c,this.$div.triggerHandler("save",{newValue:c,submitValue:e,response:a}),void 0)},this)).fail(a.proxy(function(a){this.isSaving=!1;var b;b="function"==typeof this.options.error?this.options.error.call(this.options.scope,a,c):"string"==typeof a?a:a.responseText||a.statusText||"Unknown error!",this.error(b),this.showForm()},this))},save:function(b){this.options.pk=a.fn.editableutils.tryParseJson(this.options.pk,!0);var c,d="function"==typeof this.options.pk?this.options.pk.call(this.options.scope):this.options.pk,e=!!("function"==typeof this.options.url||this.options.url&&("always"===this.options.send||"auto"===this.options.send&&null!==d&&void 0!==d));return e?(this.showLoading(),c={name:this.options.name||"",value:b,pk:d},"function"==typeof this.options.params?c=this.options.params.call(this.options.scope,c):(this.options.params=a.fn.editableutils.tryParseJson(this.options.params,!0),a.extend(c,this.options.params)),"function"==typeof this.options.url?this.options.url.call(this.options.scope,c):a.ajax(a.extend({url:this.options.url,data:c,type:"POST"},this.options.ajaxOptions))):void 0},validate:function(a){return void 0===a&&(a=this.value),"function"==typeof this.options.validate?this.options.validate.call(this.options.scope,a):void 0},option:function(a,b){a in this.options&&(this.options[a]=b),"value"===a&&this.setValue(b)},setValue:function(a,b){this.value=b?this.input.str2value(a):a,this.$form&&this.$form.is(":visible")&&this.input.value2input(this.value)}},a.fn.editableform=function(c){var d=arguments;return this.each(function(){var e=a(this),f=e.data("editableform"),g="object"==typeof c&&c;f||e.data("editableform",f=new b(this,g)),"string"==typeof c&&f[c].apply(f,Array.prototype.slice.call(d,1))})},a.fn.editableform.Constructor=b,a.fn.editableform.defaults={type:"text",url:null,params:null,name:null,pk:null,value:null,defaultValue:null,send:"auto",validate:null,success:null,error:null,ajaxOptions:null,showbuttons:!0,scope:null,savenochange:!1},a.fn.editableform.template='<form class="form-inline editableform"><div class="control-group"><div><div class="editable-input"></div><div class="editable-buttons"></div></div><div class="editable-error-block"></div></div></form>',a.fn.editableform.loading='<div class="editableform-loading"></div>',a.fn.editableform.buttons='<button type="submit" class="editable-submit">ok</button><button type="button" class="editable-cancel">cancel</button>',a.fn.editableform.errorGroupClass=null,a.fn.editableform.errorBlockClass="editable-error",a.fn.editableform.engine="jquery"}(window.jQuery),function(a){"use strict";a.fn.editableutils={inherit:function(a,b){var c=function(){};c.prototype=b.prototype,a.prototype=new c,a.prototype.constructor=a,a.superclass=b.prototype},setCursorPosition:function(a,b){if(a.setSelectionRange)a.setSelectionRange(b,b);else if(a.createTextRange){var c=a.createTextRange();c.collapse(!0),c.moveEnd("character",b),c.moveStart("character",b),c.select()}},tryParseJson:function(a,b){if("string"==typeof a&&a.length&&a.match(/^[\{\[].*[\}\]]$/))if(b)try{a=new Function("return "+a)()}catch(c){}finally{return a}else a=new Function("return "+a)();return a},sliceObj:function(b,c,d){var e,f,g={};if(!a.isArray(c)||!c.length)return g;for(var h=0;h<c.length;h++)e=c[h],b.hasOwnProperty(e)&&(g[e]=b[e]),d!==!0&&(f=e.toLowerCase(),b.hasOwnProperty(f)&&(g[e]=b[f]));return g},getConfigData:function(b){var c={};return a.each(b.data(),function(a,b){("object"!=typeof b||b&&"object"==typeof b&&(b.constructor===Object||b.constructor===Array))&&(c[a]=b)}),c},objectKeys:function(a){if(Object.keys)return Object.keys(a);if(a!==Object(a))throw new TypeError("Object.keys called on a non-object");var b,c=[];for(b in a)Object.prototype.hasOwnProperty.call(a,b)&&c.push(b);return c},escape:function(b){return a("<div>").text(b).html()},itemsByValue:function(b,c,d){if(!c||null===b)return[];if("function"!=typeof d){var e=d||"value";d=function(a){return a[e]}}var f=a.isArray(b),g=[],h=this;return a.each(c,function(c,e){if(e.children)g=g.concat(h.itemsByValue(b,e.children,d));else if(f)a.grep(b,function(a){return a==(e&&"object"==typeof e?d(e):e)}).length&&g.push(e);else{var i=e&&"object"==typeof e?d(e):e;b==i&&g.push(e)}}),g},createInput:function(b){var c,d,e,f=b.type;return"date"===f&&("inline"===b.mode?a.fn.editabletypes.datefield?f="datefield":a.fn.editabletypes.dateuifield&&(f="dateuifield"):a.fn.editabletypes.date?f="date":a.fn.editabletypes.dateui&&(f="dateui"),"date"!==f||a.fn.editabletypes.date||(f="combodate")),"datetime"===f&&"inline"===b.mode&&(f="datetimefield"),"wysihtml5"!==f||a.fn.editabletypes[f]||(f="textarea"),"function"==typeof a.fn.editabletypes[f]?(c=a.fn.editabletypes[f],d=this.sliceObj(b,this.objectKeys(c.defaults)),e=new c(d)):(a.error("Unknown type: "+f),!1)},supportsTransitions:function(){var a=document.body||document.documentElement,b=a.style,c="transition",d=["Moz","Webkit","Khtml","O","ms"];if("string"==typeof b[c])return!0;c=c.charAt(0).toUpperCase()+c.substr(1);for(var e=0;e<d.length;e++)if("string"==typeof b[d[e]+c])return!0;return!1}}}(window.jQuery),function(a){"use strict";var b=function(a,b){this.init(a,b)},c=function(a,b){this.init(a,b)};b.prototype={containerName:null,containerDataName:null,innerCss:null,containerClass:"editable-container editable-popup",defaults:{},init:function(c,d){this.$element=a(c),this.options=a.extend({},a.fn.editableContainer.defaults,d),this.splitOptions(),this.formOptions.scope=this.$element[0],this.initContainer(),this.delayedHide=!1,this.$element.on("destroyed",a.proxy(function(){this.destroy()},this)),a(document).data("editable-handlers-attached")||(a(document).on("keyup.editable",function(b){27===b.which&&a(".editable-open").editableContainer("hide")}),a(document).on("click.editable",function(c){var d,e=a(c.target),f=[".editable-container",".ui-datepicker-header",".datepicker",".modal-backdrop",".bootstrap-wysihtml5-insert-image-modal",".bootstrap-wysihtml5-insert-link-modal"];if(a.contains(document.documentElement,c.target)&&!e.is(document)){for(d=0;d<f.length;d++)if(e.is(f[d])||e.parents(f[d]).length)return;b.prototype.closeOthers(c.target)}}),a(document).data("editable-handlers-attached",!0))},splitOptions:function(){if(this.containerOptions={},this.formOptions={},!a.fn[this.containerName])throw new Error(this.containerName+" not found. Have you included corresponding js file?");for(var b in this.options)b in this.defaults?this.containerOptions[b]=this.options[b]:this.formOptions[b]=this.options[b]},tip:function(){return this.container()?this.container().$tip:null},container:function(){var a;return this.containerDataName&&(a=this.$element.data(this.containerDataName))?a:a=this.$element.data(this.containerName)},call:function(){this.$element[this.containerName].apply(this.$element,arguments)},initContainer:function(){this.call(this.containerOptions)},renderForm:function(){this.$form.editableform(this.formOptions).on({save:a.proxy(this.save,this),nochange:a.proxy(function(){this.hide("nochange")},this),cancel:a.proxy(function(){this.hide("cancel")},this),show:a.proxy(function(){this.delayedHide?(this.hide(this.delayedHide.reason),this.delayedHide=!1):this.setPosition()},this),rendering:a.proxy(this.setPosition,this),resize:a.proxy(this.setPosition,this),rendered:a.proxy(function(){this.$element.triggerHandler("shown",a(this.options.scope).data("editable"))},this)}).editableform("render")},show:function(b){this.$element.addClass("editable-open"),b!==!1&&this.closeOthers(this.$element[0]),this.innerShow(),this.tip().addClass(this.containerClass),this.$form,this.$form=a("<div>"),this.tip().is(this.innerCss)?this.tip().append(this.$form):this.tip().find(this.innerCss).append(this.$form),this.renderForm()},hide:function(a){if(this.tip()&&this.tip().is(":visible")&&this.$element.hasClass("editable-open")){if(this.$form.data("editableform").isSaving)return this.delayedHide={reason:a},void 0;this.delayedHide=!1,this.$element.removeClass("editable-open"),this.innerHide(),this.$element.triggerHandler("hidden",a||"manual")}},innerShow:function(){},innerHide:function(){},toggle:function(a){this.container()&&this.tip()&&this.tip().is(":visible")?this.hide():this.show(a)},setPosition:function(){},save:function(a,b){this.$element.triggerHandler("save",b),this.hide("save")},option:function(a,b){this.options[a]=b,a in this.containerOptions?(this.containerOptions[a]=b,this.setContainerOption(a,b)):(this.formOptions[a]=b,this.$form&&this.$form.editableform("option",a,b))},setContainerOption:function(a,b){this.call("option",a,b)},destroy:function(){this.hide(),this.innerDestroy(),this.$element.off("destroyed"),this.$element.removeData("editableContainer")},innerDestroy:function(){},closeOthers:function(b){a(".editable-open").each(function(c,d){if(d!==b&&!a(d).find(b).length){var e=a(d),f=e.data("editableContainer");f&&("cancel"===f.options.onblur?e.data("editableContainer").hide("onblur"):"submit"===f.options.onblur&&e.data("editableContainer").tip().find("form").submit())}})},activate:function(){this.tip&&this.tip().is(":visible")&&this.$form&&this.$form.data("editableform").input.activate()}},a.fn.editableContainer=function(d){var e=arguments;return this.each(function(){var f=a(this),g="editableContainer",h=f.data(g),i="object"==typeof d&&d,j="inline"===i.mode?c:b;h||f.data(g,h=new j(this,i)),"string"==typeof d&&h[d].apply(h,Array.prototype.slice.call(e,1))})},a.fn.editableContainer.Popup=b,a.fn.editableContainer.Inline=c,a.fn.editableContainer.defaults={value:null,placement:"top",autohide:!0,onblur:"cancel",anim:!1,mode:"popup"},jQuery.event.special.destroyed={remove:function(a){a.handler&&a.handler()}}}(window.jQuery),function(a){"use strict";a.extend(a.fn.editableContainer.Inline.prototype,a.fn.editableContainer.Popup.prototype,{containerName:"editableform",innerCss:".editable-inline",containerClass:"editable-container editable-inline",initContainer:function(){this.$tip=a("<span></span>"),this.options.anim||(this.options.anim=0)},splitOptions:function(){this.containerOptions={},this.formOptions=this.options},tip:function(){return this.$tip},innerShow:function(){this.$element.hide(),this.tip().insertAfter(this.$element).show()},innerHide:function(){this.$tip.hide(this.options.anim,a.proxy(function(){this.$element.show(),this.innerDestroy()},this))},innerDestroy:function(){this.tip()&&this.tip().empty().remove()}})}(window.jQuery),function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.editable.defaults,c,a.fn.editableutils.getConfigData(this.$element)),this.options.selector?this.initLive():this.init(),this.options.highlight&&!a.fn.editableutils.supportsTransitions()&&(this.options.highlight=!1)};b.prototype={constructor:b,init:function(){var b,c=!1;if(this.options.name=this.options.name||this.$element.attr("id"),this.options.scope=this.$element[0],this.input=a.fn.editableutils.createInput(this.options),this.input){switch(void 0===this.options.value||null===this.options.value?(this.value=this.input.html2value(a.trim(this.$element.html())),c=!0):(this.options.value=a.fn.editableutils.tryParseJson(this.options.value,!0),this.value="string"==typeof this.options.value?this.input.str2value(this.options.value):this.options.value),this.$element.addClass("editable"),"textarea"===this.input.type&&this.$element.addClass("editable-pre-wrapped"),"manual"!==this.options.toggle?(this.$element.addClass("editable-click"),this.$element.on(this.options.toggle+".editable",a.proxy(function(a){if(this.options.disabled||a.preventDefault(),"mouseenter"===this.options.toggle)this.show();else{var b="click"!==this.options.toggle;this.toggle(b)}},this))):this.$element.attr("tabindex",-1),"function"==typeof this.options.display&&(this.options.autotext="always"),this.options.autotext){case"always":b=!0;break;case"auto":b=!a.trim(this.$element.text()).length&&null!==this.value&&void 0!==this.value&&!c;break;default:b=!1}a.when(b?this.render():!0).then(a.proxy(function(){this.options.disabled?this.disable():this.enable(),this.$element.triggerHandler("init",this)},this))}},initLive:function(){var b=this.options.selector;this.options.selector=!1,this.options.autotext="never",this.$element.on(this.options.toggle+".editable",b,a.proxy(function(b){var c=a(b.target);c.data("editable")||(c.hasClass(this.options.emptyclass)&&c.empty(),c.editable(this.options).trigger(b))},this))},render:function(a){return this.options.display!==!1?this.input.value2htmlFinal?this.input.value2html(this.value,this.$element[0],this.options.display,a):"function"==typeof this.options.display?this.options.display.call(this.$element[0],this.value,a):this.input.value2html(this.value,this.$element[0]):void 0},enable:function(){this.options.disabled=!1,this.$element.removeClass("editable-disabled"),this.handleEmpty(this.isEmpty),"manual"!==this.options.toggle&&"-1"===this.$element.attr("tabindex")&&this.$element.removeAttr("tabindex")},disable:function(){this.options.disabled=!0,this.hide(),this.$element.addClass("editable-disabled"),this.handleEmpty(this.isEmpty),this.$element.attr("tabindex",-1)},toggleDisabled:function(){this.options.disabled?this.enable():this.disable()},option:function(b,c){return b&&"object"==typeof b?(a.each(b,a.proxy(function(b,c){this.option(a.trim(b),c)},this)),void 0):(this.options[b]=c,"disabled"===b?c?this.disable():this.enable():("value"===b&&this.setValue(c),this.container&&this.container.option(b,c),this.input.option&&this.input.option(b,c),void 0))},handleEmpty:function(b){this.options.display!==!1&&(this.isEmpty=void 0!==b?b:"function"==typeof this.input.isEmpty?this.input.isEmpty(this.$element):""===a.trim(this.$element.html()),this.options.disabled?this.isEmpty&&(this.$element.empty(),this.options.emptyclass&&this.$element.removeClass(this.options.emptyclass)):this.isEmpty?(this.$element.html(this.options.emptytext),this.options.emptyclass&&this.$element.addClass(this.options.emptyclass)):this.options.emptyclass&&this.$element.removeClass(this.options.emptyclass))},show:function(b){if(!this.options.disabled){if(this.container){if(this.container.tip().is(":visible"))return}else{var c=a.extend({},this.options,{value:this.value,input:this.input});this.$element.editableContainer(c),this.$element.on("save.internal",a.proxy(this.save,this)),this.container=this.$element.data("editableContainer")}this.container.show(b)}},hide:function(){this.container&&this.container.hide()},toggle:function(a){this.container&&this.container.tip().is(":visible")?this.hide():this.show(a)},save:function(a,b){if(this.options.unsavedclass){var c=!1;c=c||"function"==typeof this.options.url,c=c||this.options.display===!1,c=c||void 0!==b.response,c=c||this.options.savenochange&&this.input.value2str(this.value)!==this.input.value2str(b.newValue),c?this.$element.removeClass(this.options.unsavedclass):this.$element.addClass(this.options.unsavedclass)}if(this.options.highlight){var d=this.$element,e=d.css("background-color");d.css("background-color",this.options.highlight),setTimeout(function(){"transparent"===e&&(e=""),d.css("background-color",e),d.addClass("editable-bg-transition"),setTimeout(function(){d.removeClass("editable-bg-transition")},1700)},10)}this.setValue(b.newValue,!1,b.response)},validate:function(){return"function"==typeof this.options.validate?this.options.validate.call(this,this.value):void 0},setValue:function(b,c,d){this.value=c?this.input.str2value(b):b,this.container&&this.container.option("value",this.value),a.when(this.render(d)).then(a.proxy(function(){this.handleEmpty()},this))},activate:function(){this.container&&this.container.activate()},destroy:function(){this.disable(),this.container&&this.container.destroy(),this.input.destroy(),"manual"!==this.options.toggle&&(this.$element.removeClass("editable-click"),this.$element.off(this.options.toggle+".editable")),this.$element.off("save.internal"),this.$element.removeClass("editable editable-open editable-disabled"),this.$element.removeData("editable")}},a.fn.editable=function(c){var d={},e=arguments,f="editable";switch(c){case"validate":return this.each(function(){var b,c=a(this),e=c.data(f);e&&(b=e.validate())&&(d[e.options.name]=b)}),d;case"getValue":return 2===arguments.length&&arguments[1]===!0?d=this.eq(0).data(f).value:this.each(function(){var b=a(this),c=b.data(f);c&&void 0!==c.value&&null!==c.value&&(d[c.options.name]=c.input.value2submit(c.value))}),d;case"submit":var g=arguments[1]||{},h=this,i=this.editable("validate");if(a.isEmptyObject(i)){var j={};if(1===h.length){var k=h.data("editable"),l={name:k.options.name||"",value:k.input.value2submit(k.value),pk:"function"==typeof k.options.pk?k.options.pk.call(k.options.scope):k.options.pk};"function"==typeof k.options.params?l=k.options.params.call(k.options.scope,l):(k.options.params=a.fn.editableutils.tryParseJson(k.options.params,!0),a.extend(l,k.options.params)),j={url:k.options.url,data:l,type:"POST"},g.success=g.success||k.options.success,g.error=g.error||k.options.error}else{var m=this.editable("getValue");j={url:g.url,data:m,type:"POST"}}j.success="function"==typeof g.success?function(a){g.success.call(h,a,g)}:a.noop,j.error="function"==typeof g.error?function(){g.error.apply(h,arguments)}:a.noop,g.ajaxOptions&&a.extend(j,g.ajaxOptions),g.data&&a.extend(j.data,g.data),a.ajax(j)}else"function"==typeof g.error&&g.error.call(h,i);return this}return this.each(function(){var d=a(this),g=d.data(f),h="object"==typeof c&&c;return h&&h.selector?(g=new b(this,h),void 0):(g||d.data(f,g=new b(this,h)),"string"==typeof c&&g[c].apply(g,Array.prototype.slice.call(e,1)),void 0)})},a.fn.editable.defaults={type:"text",disabled:!1,toggle:"click",emptytext:"Empty",autotext:"auto",value:null,display:null,emptyclass:"editable-empty",unsavedclass:"editable-unsaved",selector:null,highlight:"#FFFF80"}}(window.jQuery),function(a){"use strict";a.fn.editabletypes={};var b=function(){};b.prototype={init:function(b,c,d){this.type=b,this.options=a.extend({},d,c)},prerender:function(){this.$tpl=a(this.options.tpl),this.$input=this.$tpl,this.$clear=null,this.error=null},render:function(){},value2html:function(b,c){a(c)[this.options.escape?"text":"html"](a.trim(b))},html2value:function(b){return a("<div>").html(b).text()},value2str:function(a){return a},str2value:function(a){return a},value2submit:function(a){return a},value2input:function(a){this.$input.val(a)},input2value:function(){return this.$input.val()},activate:function(){this.$input.is(":visible")&&this.$input.focus()},clear:function(){this.$input.val(null)},escape:function(b){return a("<div>").text(b).html()},autosubmit:function(){},destroy:function(){},setClass:function(){this.options.inputclass&&this.$input.addClass(this.options.inputclass)},setAttr:function(a){void 0!==this.options[a]&&null!==this.options[a]&&this.$input.attr(a,this.options[a])},option:function(a,b){this.options[a]=b}},b.defaults={tpl:"",inputclass:null,escape:!0,scope:null,showbuttons:!0},a.extend(a.fn.editabletypes,{abstractinput:b})}(window.jQuery),function(a){"use strict";var b=function(){};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){var b=a.Deferred();return this.error=null,this.onSourceReady(function(){this.renderList(),b.resolve()},function(){this.error=this.options.sourceError,b.resolve()}),b.promise()},html2value:function(){return null},value2html:function(b,c,d,e){var f=a.Deferred(),g=function(){"function"==typeof d?d.call(c,b,this.sourceData,e):this.value2htmlFinal(b,c),f.resolve()};return null===b?g.call(this):this.onSourceReady(g,function(){f.resolve()}),f.promise()},onSourceReady:function(b,c){var d;if(a.isFunction(this.options.source)?(d=this.options.source.call(this.options.scope),this.sourceData=null):d=this.options.source,this.options.sourceCache&&a.isArray(this.sourceData))return b.call(this),void 0;try{d=a.fn.editableutils.tryParseJson(d,!1)}catch(e){return c.call(this),void 0}if("string"==typeof d){if(this.options.sourceCache){var f,g=d;if(a(document).data(g)||a(document).data(g,{}),f=a(document).data(g),f.loading===!1&&f.sourceData)return this.sourceData=f.sourceData,this.doPrepend(),b.call(this),void 0;if(f.loading===!0)return f.callbacks.push(a.proxy(function(){this.sourceData=f.sourceData,this.doPrepend(),b.call(this)},this)),f.err_callbacks.push(a.proxy(c,this)),void 0;f.loading=!0,f.callbacks=[],f.err_callbacks=[]}var h=a.extend({url:d,type:"get",cache:!1,dataType:"json",success:a.proxy(function(d){f&&(f.loading=!1),this.sourceData=this.makeArray(d),a.isArray(this.sourceData)?(f&&(f.sourceData=this.sourceData,a.each(f.callbacks,function(){this.call()})),this.doPrepend(),b.call(this)):(c.call(this),f&&a.each(f.err_callbacks,function(){this.call()}))},this),error:a.proxy(function(){c.call(this),f&&(f.loading=!1,a.each(f.err_callbacks,function(){this.call()}))},this)},this.options.sourceOptions);a.ajax(h)}else this.sourceData=this.makeArray(d),a.isArray(this.sourceData)?(this.doPrepend(),b.call(this)):c.call(this)},doPrepend:function(){null!==this.options.prepend&&void 0!==this.options.prepend&&(a.isArray(this.prependData)||(a.isFunction(this.options.prepend)&&(this.options.prepend=this.options.prepend.call(this.options.scope)),this.options.prepend=a.fn.editableutils.tryParseJson(this.options.prepend,!0),"string"==typeof this.options.prepend&&(this.options.prepend={"":this.options.prepend}),this.prependData=this.makeArray(this.options.prepend)),a.isArray(this.prependData)&&a.isArray(this.sourceData)&&(this.sourceData=this.prependData.concat(this.sourceData)))},renderList:function(){},value2htmlFinal:function(){},makeArray:function(b){var c,d,e,f,g=[];if(!b||"string"==typeof b)return null;if(a.isArray(b)){f=function(a,b){return d={value:a,text:b},c++>=2?!1:void 0};for(var h=0;h<b.length;h++)e=b[h],"object"==typeof e?(c=0,a.each(e,f),1===c?g.push(d):c>1&&(e.children&&(e.children=this.makeArray(e.children)),g.push(e))):g.push({value:e,text:e})}else a.each(b,function(a,b){g.push({value:a,text:b})});return g},option:function(a,b){this.options[a]=b,"source"===a&&(this.sourceData=null),"prepend"===a&&(this.prependData=null)}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{source:null,prepend:!1,sourceError:"Error when loading list",sourceCache:!0,sourceOptions:null}),a.fn.editabletypes.list=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("text",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){this.renderClear(),this.setClass(),this.setAttr("placeholder")},activate:function(){this.$input.is(":visible")&&(this.$input.focus(),a.fn.editableutils.setCursorPosition(this.$input.get(0),this.$input.val().length),this.toggleClear&&this.toggleClear())},renderClear:function(){this.options.clear&&(this.$clear=a('<span class="editable-clear-x"></span>'),this.$input.after(this.$clear).css("padding-right",24).keyup(a.proxy(function(b){if(!~a.inArray(b.keyCode,[40,38,9,13,27])){clearTimeout(this.t);var c=this;this.t=setTimeout(function(){c.toggleClear(b)},100)}},this)).parent().css("position","relative"),this.$clear.click(a.proxy(this.clear,this)))},postrender:function(){},toggleClear:function(){if(this.$clear){var a=this.$input.val().length,b=this.$clear.is(":visible");a&&!b&&this.$clear.show(),!a&&b&&this.$clear.hide()}},clear:function(){this.$clear.hide(),this.$input.val("").focus()}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:'<input type="text">',placeholder:null,clear:!0}),a.fn.editabletypes.text=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("textarea",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){this.setClass(),this.setAttr("placeholder"),this.setAttr("rows"),this.$input.keydown(function(b){b.ctrlKey&&13===b.which&&a(this).closest("form").submit()})},activate:function(){a.fn.editabletypes.text.prototype.activate.call(this)}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:"<textarea></textarea>",inputclass:"input-large",placeholder:null,rows:7}),a.fn.editabletypes.textarea=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("select",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.list),a.extend(b.prototype,{renderList:function(){this.$input.empty();var b=function(c,d){var e;if(a.isArray(d))for(var f=0;f<d.length;f++)e={},d[f].children?(e.label=d[f].text,c.append(b(a("<optgroup>",e),d[f].children))):(e.value=d[f].value,d[f].disabled&&(e.disabled=!0),c.append(a("<option>",e).text(d[f].text)));return c};b(this.$input,this.sourceData),this.setClass(),this.$input.on("keydown.editable",function(b){13===b.which&&a(this).closest("form").submit()})},value2htmlFinal:function(b,c){var d="",e=a.fn.editableutils.itemsByValue(b,this.sourceData);e.length&&(d=e[0].text),a.fn.editabletypes.abstractinput.prototype.value2html.call(this,d,c)},autosubmit:function(){this.$input.off("keydown.editable").on("change.editable",function(){a(this).closest("form").submit()})}}),b.defaults=a.extend({},a.fn.editabletypes.list.defaults,{tpl:"<select></select>"}),a.fn.editabletypes.select=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("checklist",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.list),a.extend(b.prototype,{renderList:function(){var b;if(this.$tpl.empty(),a.isArray(this.sourceData)){for(var c=0;c<this.sourceData.length;c++)b=a("<label>").append(a("<input>",{type:"checkbox",value:this.sourceData[c].value})).append(a("<span>").text(" "+this.sourceData[c].text)),a("<div>").append(b).appendTo(this.$tpl);this.$input=this.$tpl.find('input[type="checkbox"]'),this.setClass()}},value2str:function(b){return a.isArray(b)?b.sort().join(a.trim(this.options.separator)):""},str2value:function(b){var c,d=null;return"string"==typeof b&&b.length?(c=new RegExp("\\s*"+a.trim(this.options.separator)+"\\s*"),d=b.split(c)):d=a.isArray(b)?b:[b],d},value2input:function(b){this.$input.prop("checked",!1),a.isArray(b)&&b.length&&this.$input.each(function(c,d){var e=a(d);a.each(b,function(a,b){e.val()==b&&e.prop("checked",!0)})})},input2value:function(){var b=[];return this.$input.filter(":checked").each(function(c,d){b.push(a(d).val())}),b},value2htmlFinal:function(b,c){var d=[],e=a.fn.editableutils.itemsByValue(b,this.sourceData),f=this.options.escape;e.length?(a.each(e,function(b,c){var e=f?a.fn.editableutils.escape(c.text):c.text;d.push(e)}),a(c).html(d.join("<br>"))):a(c).empty()},activate:function(){this.$input.first().focus()},autosubmit:function(){this.$input.on("keydown",function(b){13===b.which&&a(this).closest("form").submit()})}}),b.defaults=a.extend({},a.fn.editabletypes.list.defaults,{tpl:'<div class="editable-checklist"></div>',inputclass:null,separator:","}),a.fn.editabletypes.checklist=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("password",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.text),a.extend(b.prototype,{value2html:function(b,c){b?a(c).text("[hidden]"):a(c).empty()},html2value:function(){return null}}),b.defaults=a.extend({},a.fn.editabletypes.text.defaults,{tpl:'<input type="password">'}),a.fn.editabletypes.password=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("email",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.text),b.defaults=a.extend({},a.fn.editabletypes.text.defaults,{tpl:'<input type="email">'}),a.fn.editabletypes.email=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("url",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.text),b.defaults=a.extend({},a.fn.editabletypes.text.defaults,{tpl:'<input type="url">'}),a.fn.editabletypes.url=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("tel",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.text),b.defaults=a.extend({},a.fn.editabletypes.text.defaults,{tpl:'<input type="tel">'}),a.fn.editabletypes.tel=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("number",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.text),a.extend(b.prototype,{render:function(){b.superclass.render.call(this),this.setAttr("min"),this.setAttr("max"),this.setAttr("step")},postrender:function(){this.$clear&&this.$clear.css({right:24})}}),b.defaults=a.extend({},a.fn.editabletypes.text.defaults,{tpl:'<input type="number">',inputclass:"input-mini",min:null,max:null,step:null}),a.fn.editabletypes.number=b}(window.jQuery),function(a){"use strict";
6
- var b=function(a){this.init("range",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.number),a.extend(b.prototype,{render:function(){this.$input=this.$tpl.filter("input"),this.setClass(),this.setAttr("min"),this.setAttr("max"),this.setAttr("step"),this.$input.on("input",function(){a(this).siblings("output").text(a(this).val())})},activate:function(){this.$input.focus()}}),b.defaults=a.extend({},a.fn.editabletypes.number.defaults,{tpl:'<input type="range"><output style="width: 30px; display: inline-block"></output>',inputclass:"input-medium"}),a.fn.editabletypes.range=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("time",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){this.setClass()}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:'<input type="time">'}),a.fn.editabletypes.time=b}(window.jQuery),function(a){"use strict";var b=function(c){if(this.init("select2",c,b.defaults),c.select2=c.select2||{},this.sourceData=null,c.placeholder&&(c.select2.placeholder=c.placeholder),!c.select2.tags&&c.source){var d=c.source;a.isFunction(c.source)&&(d=c.source.call(c.scope)),"string"==typeof d?(c.select2.ajax=c.select2.ajax||{},c.select2.ajax.data||(c.select2.ajax.data=function(a){return{query:a}}),c.select2.ajax.results||(c.select2.ajax.results=function(a){return{results:a}}),c.select2.ajax.url=d):(this.sourceData=this.convertSource(d),c.select2.data=this.sourceData)}if(this.options.select2=a.extend({},b.defaults.select2,c.select2),this.isMultiple=this.options.select2.tags||this.options.select2.multiple,this.isRemote="ajax"in this.options.select2,this.idFunc=this.options.select2.id,"function"!=typeof this.idFunc){var e=this.idFunc||"id";this.idFunc=function(a){return a[e]}}this.formatSelection=this.options.select2.formatSelection,"function"!=typeof this.formatSelection&&(this.formatSelection=function(a){return a.text})};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){this.setClass(),this.isRemote&&this.$input.on("select2-loaded",a.proxy(function(a){this.sourceData=a.items.results},this)),this.isMultiple&&this.$input.on("change",function(){a(this).closest("form").parent().triggerHandler("resize")})},value2html:function(c,d){var e,f="",g=this;this.options.select2.tags?e=c:this.sourceData&&(e=a.fn.editableutils.itemsByValue(c,this.sourceData,this.idFunc)),a.isArray(e)?(f=[],a.each(e,function(a,b){f.push(b&&"object"==typeof b?g.formatSelection(b):b)})):e&&(f=g.formatSelection(e)),f=a.isArray(f)?f.join(this.options.viewseparator):f,b.superclass.value2html.call(this,f,d)},html2value:function(a){return this.options.select2.tags?this.str2value(a,this.options.viewseparator):null},value2input:function(b){if(a.isArray(b)&&(b=b.join(this.getSeparator())),this.$input.data("select2")?this.$input.val(b).trigger("change",!0):(this.$input.val(b),this.$input.select2(this.options.select2)),this.isRemote&&!this.isMultiple&&!this.options.select2.initSelection){var c=this.options.select2.id,d=this.options.select2.formatSelection;if(!c&&!d){var e=a(this.options.scope);if(!e.data("editable").isEmpty){var f={id:b,text:e.text()};this.$input.select2("data",f)}}}},input2value:function(){return this.$input.select2("val")},str2value:function(b,c){if("string"!=typeof b||!this.isMultiple)return b;c=c||this.getSeparator();var d,e,f;if(null===b||b.length<1)return null;for(d=b.split(c),e=0,f=d.length;f>e;e+=1)d[e]=a.trim(d[e]);return d},autosubmit:function(){this.$input.on("change",function(b,c){c||a(this).closest("form").submit()})},getSeparator:function(){return this.options.select2.separator||a.fn.select2.defaults.separator},convertSource:function(b){if(a.isArray(b)&&b.length&&void 0!==b[0].value)for(var c=0;c<b.length;c++)void 0!==b[c].value&&(b[c].id=b[c].value,delete b[c].value);return b},destroy:function(){this.$input.data("select2")&&this.$input.select2("destroy")}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:'<input type="hidden">',select2:null,placeholder:null,source:null,viewseparator:", "}),a.fn.editabletypes.select2=b}(window.jQuery),function(a){var b=function(b,c){return this.$element=a(b),this.$element.is("input")?(this.options=a.extend({},a.fn.combodate.defaults,c,this.$element.data()),this.init(),void 0):(a.error("Combodate should be applied to INPUT element"),void 0)};b.prototype={constructor:b,init:function(){this.map={day:["D","date"],month:["M","month"],year:["Y","year"],hour:["[Hh]","hours"],minute:["m","minutes"],second:["s","seconds"],ampm:["[Aa]",""]},this.$widget=a('<span class="combodate"></span>').html(this.getTemplate()),this.initCombos(),this.$widget.on("change","select",a.proxy(function(b){this.$element.val(this.getValue()).change(),this.options.smartDays&&(a(b.target).is(".month")||a(b.target).is(".year"))&&this.fillCombo("day")},this)),this.$widget.find("select").css("width","auto"),this.$element.hide().after(this.$widget),this.setValue(this.$element.val()||this.options.value)},getTemplate:function(){var b=this.options.template;return a.each(this.map,function(a,c){c=c[0];var d=new RegExp(c+"+"),e=c.length>1?c.substring(1,2):c;b=b.replace(d,"{"+e+"}")}),b=b.replace(/ /g,"&nbsp;"),a.each(this.map,function(a,c){c=c[0];var d=c.length>1?c.substring(1,2):c;b=b.replace("{"+d+"}",'<select class="'+a+'"></select>')}),b},initCombos:function(){for(var a in this.map){var b=this.$widget.find("."+a);this["$"+a]=b.length?b:null,this.fillCombo(a)}},fillCombo:function(a){var b=this["$"+a];if(b){var c="fill"+a.charAt(0).toUpperCase()+a.slice(1),d=this[c](),e=b.val();b.empty();for(var f=0;f<d.length;f++)b.append('<option value="'+d[f][0]+'">'+d[f][1]+"</option>");b.val(e)}},fillCommon:function(a){var b,c=[];if("name"===this.options.firstItem){b=moment.relativeTime||moment.langData()._relativeTime;var d="function"==typeof b[a]?b[a](1,!0,a,!1):b[a];d=d.split(" ").reverse()[0],c.push(["",d])}else"empty"===this.options.firstItem&&c.push(["",""]);return c},fillDay:function(){var a,b,c=this.fillCommon("d"),d=-1!==this.options.template.indexOf("DD"),e=31;if(this.options.smartDays&&this.$month&&this.$year){var f=parseInt(this.$month.val(),10),g=parseInt(this.$year.val(),10);isNaN(f)||isNaN(g)||(e=moment([g,f]).daysInMonth())}for(b=1;e>=b;b++)a=d?this.leadZero(b):b,c.push([b,a]);return c},fillMonth:function(){var a,b,c=this.fillCommon("M"),d=-1!==this.options.template.indexOf("MMMM"),e=-1!==this.options.template.indexOf("MMM"),f=-1!==this.options.template.indexOf("MM");for(b=0;11>=b;b++)a=d?moment().date(1).month(b).format("MMMM"):e?moment().date(1).month(b).format("MMM"):f?this.leadZero(b+1):b+1,c.push([b,a]);return c},fillYear:function(){var a,b,c=[],d=-1!==this.options.template.indexOf("YYYY");for(b=this.options.maxYear;b>=this.options.minYear;b--)a=d?b:(b+"").substring(2),c[this.options.yearDescending?"push":"unshift"]([b,a]);return c=this.fillCommon("y").concat(c)},fillHour:function(){var a,b,c=this.fillCommon("h"),d=-1!==this.options.template.indexOf("h"),e=(-1!==this.options.template.indexOf("H"),-1!==this.options.template.toLowerCase().indexOf("hh")),f=d?1:0,g=d?12:23;for(b=f;g>=b;b++)a=e?this.leadZero(b):b,c.push([b,a]);return c},fillMinute:function(){var a,b,c=this.fillCommon("m"),d=-1!==this.options.template.indexOf("mm");for(b=0;59>=b;b+=this.options.minuteStep)a=d?this.leadZero(b):b,c.push([b,a]);return c},fillSecond:function(){var a,b,c=this.fillCommon("s"),d=-1!==this.options.template.indexOf("ss");for(b=0;59>=b;b+=this.options.secondStep)a=d?this.leadZero(b):b,c.push([b,a]);return c},fillAmpm:function(){var a=-1!==this.options.template.indexOf("a"),b=(-1!==this.options.template.indexOf("A"),[["am",a?"am":"AM"],["pm",a?"pm":"PM"]]);return b},getValue:function(b){var c,d={},e=this,f=!1;return a.each(this.map,function(a){if("ampm"!==a){var b="day"===a?1:0;return d[a]=e["$"+a]?parseInt(e["$"+a].val(),10):b,isNaN(d[a])?(f=!0,!1):void 0}}),f?"":(this.$ampm&&(d.hour=12===d.hour?"am"===this.$ampm.val()?0:12:"am"===this.$ampm.val()?d.hour:d.hour+12),c=moment([d.year,d.month,d.day,d.hour,d.minute,d.second]),this.highlight(c),b=void 0===b?this.options.format:b,null===b?c.isValid()?c:null:c.isValid()?c.format(b):"")},setValue:function(b){function c(b,c){var d={};return b.children("option").each(function(b,e){var f,g=a(e).attr("value");""!==g&&(f=Math.abs(g-c),("undefined"==typeof d.distance||f<d.distance)&&(d={value:g,distance:f}))}),d.value}if(b){var d="string"==typeof b?moment(b,this.options.format):moment(b),e=this,f={};d.isValid()&&(a.each(this.map,function(a,b){"ampm"!==a&&(f[a]=d[b[1]]())}),this.$ampm&&(f.hour>=12?(f.ampm="pm",f.hour>12&&(f.hour-=12)):(f.ampm="am",0===f.hour&&(f.hour=12))),a.each(f,function(a,b){e["$"+a]&&("minute"===a&&e.options.minuteStep>1&&e.options.roundTime&&(b=c(e["$"+a],b)),"second"===a&&e.options.secondStep>1&&e.options.roundTime&&(b=c(e["$"+a],b)),e["$"+a].val(b))}),this.options.smartDays&&this.fillCombo("day"),this.$element.val(d.format(this.options.format)).change())}},highlight:function(a){a.isValid()?this.options.errorClass?this.$widget.removeClass(this.options.errorClass):this.$widget.find("select").css("border-color",this.borderColor):this.options.errorClass?this.$widget.addClass(this.options.errorClass):(this.borderColor||(this.borderColor=this.$widget.find("select").css("border-color")),this.$widget.find("select").css("border-color","red"))},leadZero:function(a){return 9>=a?"0"+a:a},destroy:function(){this.$widget.remove(),this.$element.removeData("combodate").show()}},a.fn.combodate=function(c){var d,e=Array.apply(null,arguments);return e.shift(),"getValue"===c&&this.length&&(d=this.eq(0).data("combodate"))?d.getValue.apply(d,e):this.each(function(){var d=a(this),f=d.data("combodate"),g="object"==typeof c&&c;f||d.data("combodate",f=new b(this,g)),"string"==typeof c&&"function"==typeof f[c]&&f[c].apply(f,e)})},a.fn.combodate.defaults={format:"DD-MM-YYYY HH:mm",template:"D / MMM / YYYY H : mm",value:null,minYear:1970,maxYear:2015,yearDescending:!0,minuteStep:5,secondStep:1,firstItem:"empty",errorClass:null,roundTime:!0,smartDays:!1}}(window.jQuery),function(a){"use strict";var b=function(c){this.init("combodate",c,b.defaults),this.options.viewformat||(this.options.viewformat=this.options.format),c.combodate=a.fn.editableutils.tryParseJson(c.combodate,!0),this.options.combodate=a.extend({},b.defaults.combodate,c.combodate,{format:this.options.format,template:this.options.template})};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){this.$input.combodate(this.options.combodate),"bs3"===a.fn.editableform.engine&&this.$input.siblings().find("select").addClass("form-control"),this.options.inputclass&&this.$input.siblings().find("select").addClass(this.options.inputclass)},value2html:function(a,c){var d=a?a.format(this.options.viewformat):"";b.superclass.value2html.call(this,d,c)},html2value:function(a){return a?moment(a,this.options.viewformat):null},value2str:function(a){return a?a.format(this.options.format):""},str2value:function(a){return a?moment(a,this.options.format):null},value2submit:function(a){return this.value2str(a)},value2input:function(a){this.$input.combodate("setValue",a)},input2value:function(){return this.$input.combodate("getValue",null)},activate:function(){this.$input.siblings(".combodate").find("select").eq(0).focus()},autosubmit:function(){}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:'<input type="text">',inputclass:null,format:"YYYY-MM-DD",viewformat:null,template:"D / MMM / YYYY",combodate:null}),a.fn.editabletypes.combodate=b}(window.jQuery),function(a){"use strict";var b=a.fn.editableform.Constructor.prototype.initInput;a.extend(a.fn.editableform.Constructor.prototype,{initTemplate:function(){this.$form=a(a.fn.editableform.template),this.$form.find(".control-group").addClass("form-group"),this.$form.find(".editable-error-block").addClass("help-block")},initInput:function(){b.apply(this);var c=null===this.input.options.inputclass||this.input.options.inputclass===!1,d="input-sm",e="text,select,textarea,password,email,url,tel,number,range,time,typeaheadjs".split(",");~a.inArray(this.input.type,e)&&(this.input.$input.addClass("form-control"),c&&(this.input.options.inputclass=d,this.input.$input.addClass(d)));for(var f=this.$form.find(".editable-buttons"),g=c?[d]:this.input.options.inputclass.split(" "),h=0;h<g.length;h++)"input-lg"===g[h].toLowerCase()&&f.find("button").removeClass("btn-sm").addClass("btn-lg")}}),a.fn.editableform.buttons='<button type="submit" class="btn btn-primary btn-sm editable-submit"><i class="glyphicon glyphicon-ok"></i></button><button type="button" class="btn btn-default btn-sm editable-cancel"><i class="glyphicon glyphicon-remove"></i></button>',a.fn.editableform.errorGroupClass="has-error",a.fn.editableform.errorBlockClass=null,a.fn.editableform.engine="bs3"}(window.jQuery),function(a){"use strict";a.extend(a.fn.editableContainer.Popup.prototype,{containerName:"popover",containerDataName:"bs.popover",innerCss:".popover-content",defaults:a.fn.popover.Constructor.DEFAULTS,initContainer:function(){a.extend(this.containerOptions,{trigger:"manual",selector:!1,content:" ",template:this.defaults.template});var b;this.$element.data("template")&&(b=this.$element.data("template"),this.$element.removeData("template")),this.call(this.containerOptions),b&&this.$element.data("template",b)},innerShow:function(){this.call("show")},innerHide:function(){this.call("hide")},innerDestroy:function(){this.call("destroy")},setContainerOption:function(a,b){this.container().options[a]=b},setPosition:function(){!function(){var a=this.tip(),b="function"==typeof this.options.placement?this.options.placement.call(this,a[0],this.$element[0]):this.options.placement,c=/\s?auto?\s?/i,d=c.test(b);d&&(b=b.replace(c,"")||"top");var e=this.getPosition(),f=a[0].offsetWidth,g=a[0].offsetHeight;if(d){var h=this.$element.parent(),i=b,j=document.documentElement.scrollTop||document.body.scrollTop,k="body"==this.options.container?window.innerWidth:h.outerWidth(),l="body"==this.options.container?window.innerHeight:h.outerHeight(),m="body"==this.options.container?0:h.offset().left;b="bottom"==b&&e.top+e.height+g-j>l?"top":"top"==b&&e.top-j-g<0?"bottom":"right"==b&&e.right+f>k?"left":"left"==b&&e.left-f<m?"right":b,a.removeClass(i).addClass(b)}var n=this.getCalculatedOffset(b,e,f,g);this.applyPlacement(n,b)}.call(this.container())}})}(window.jQuery),function(a){function b(){return new Date(Date.UTC.apply(Date,arguments))}function c(b,c){var d,e=a(b).data(),f={},g=new RegExp("^"+c.toLowerCase()+"([A-Z])"),c=new RegExp("^"+c.toLowerCase());for(var h in e)c.test(h)&&(d=h.replace(g,function(a,b){return b.toLowerCase()}),f[d]=e[h]);return f}function d(b){var c={};if(k[b]||(b=b.split("-")[0],k[b])){var d=k[b];return a.each(j,function(a,b){b in d&&(c[b]=d[b])}),c}}var e=function(b,c){this._process_options(c),this.element=a(b),this.isInline=!1,this.isInput=this.element.is("input"),this.component=this.element.is(".date")?this.element.find(".add-on, .btn"):!1,this.hasInput=this.component&&this.element.find("input").length,this.component&&0===this.component.length&&(this.component=!1),this.picker=a(l.template),this._buildEvents(),this._attachEvents(),this.isInline?this.picker.addClass("datepicker-inline").appendTo(this.element):this.picker.addClass("datepicker-dropdown dropdown-menu"),this.o.rtl&&(this.picker.addClass("datepicker-rtl"),this.picker.find(".prev i, .next i").toggleClass("icon-arrow-left icon-arrow-right")),this.viewMode=this.o.startView,this.o.calendarWeeks&&this.picker.find("tfoot th.today").attr("colspan",function(a,b){return parseInt(b)+1}),this._allow_update=!1,this.setStartDate(this.o.startDate),this.setEndDate(this.o.endDate),this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled),this.fillDow(),this.fillMonths(),this._allow_update=!0,this.update(),this.showMode(),this.isInline&&this.show()};e.prototype={constructor:e,_process_options:function(b){this._o=a.extend({},this._o,b);var c=this.o=a.extend({},this._o),d=c.language;switch(k[d]||(d=d.split("-")[0],k[d]||(d=i.language)),c.language=d,c.startView){case 2:case"decade":c.startView=2;break;case 1:case"year":c.startView=1;break;default:c.startView=0}switch(c.minViewMode){case 1:case"months":c.minViewMode=1;break;case 2:case"years":c.minViewMode=2;break;default:c.minViewMode=0}c.startView=Math.max(c.startView,c.minViewMode),c.weekStart%=7,c.weekEnd=(c.weekStart+6)%7;var e=l.parseFormat(c.format);c.startDate!==-1/0&&(c.startDate=l.parseDate(c.startDate,e,c.language)),1/0!==c.endDate&&(c.endDate=l.parseDate(c.endDate,e,c.language)),c.daysOfWeekDisabled=c.daysOfWeekDisabled||[],a.isArray(c.daysOfWeekDisabled)||(c.daysOfWeekDisabled=c.daysOfWeekDisabled.split(/[,\s]*/)),c.daysOfWeekDisabled=a.map(c.daysOfWeekDisabled,function(a){return parseInt(a,10)})},_events:[],_secondaryEvents:[],_applyEvents:function(a){for(var b,c,d=0;d<a.length;d++)b=a[d][0],c=a[d][1],b.on(c)},_unapplyEvents:function(a){for(var b,c,d=0;d<a.length;d++)b=a[d][0],c=a[d][1],b.off(c)},_buildEvents:function(){this.isInput?this._events=[[this.element,{focus:a.proxy(this.show,this),keyup:a.proxy(this.update,this),keydown:a.proxy(this.keydown,this)}]]:this.component&&this.hasInput?this._events=[[this.element.find("input"),{focus:a.proxy(this.show,this),keyup:a.proxy(this.update,this),keydown:a.proxy(this.keydown,this)}],[this.component,{click:a.proxy(this.show,this)}]]:this.element.is("div")?this.isInline=!0:this._events=[[this.element,{click:a.proxy(this.show,this)}]],this._secondaryEvents=[[this.picker,{click:a.proxy(this.click,this)}],[a(window),{resize:a.proxy(this.place,this)}],[a(document),{mousedown:a.proxy(function(a){this.element.is(a.target)||this.element.find(a.target).size()||this.picker.is(a.target)||this.picker.find(a.target).size()||this.hide()},this)}]]},_attachEvents:function(){this._detachEvents(),this._applyEvents(this._events)},_detachEvents:function(){this._unapplyEvents(this._events)},_attachSecondaryEvents:function(){this._detachSecondaryEvents(),this._applyEvents(this._secondaryEvents)},_detachSecondaryEvents:function(){this._unapplyEvents(this._secondaryEvents)},_trigger:function(b,c){var d=c||this.date,e=new Date(d.getTime()+6e4*d.getTimezoneOffset());this.element.trigger({type:b,date:e,format:a.proxy(function(a){var b=a||this.o.format;return l.formatDate(d,b,this.o.language)},this)})},show:function(a){this.isInline||this.picker.appendTo("body"),this.picker.show(),this.height=this.component?this.component.outerHeight():this.element.outerHeight(),this.place(),this._attachSecondaryEvents(),a&&a.preventDefault(),this._trigger("show")},hide:function(){this.isInline||this.picker.is(":visible")&&(this.picker.hide().detach(),this._detachSecondaryEvents(),this.viewMode=this.o.startView,this.showMode(),this.o.forceParse&&(this.isInput&&this.element.val()||this.hasInput&&this.element.find("input").val())&&this.setValue(),this._trigger("hide"))},remove:function(){this.hide(),this._detachEvents(),this._detachSecondaryEvents(),this.picker.remove(),delete this.element.data().datepicker,this.isInput||delete this.element.data().date},getDate:function(){var a=this.getUTCDate();return new Date(a.getTime()+6e4*a.getTimezoneOffset())},getUTCDate:function(){return this.date},setDate:function(a){this.setUTCDate(new Date(a.getTime()-6e4*a.getTimezoneOffset()))},setUTCDate:function(a){this.date=a,this.setValue()},setValue:function(){var a=this.getFormattedDate();this.isInput?this.element.val(a):this.component&&this.element.find("input").val(a)},getFormattedDate:function(a){return void 0===a&&(a=this.o.format),l.formatDate(this.date,a,this.o.language)},setStartDate:function(a){this._process_options({startDate:a}),this.update(),this.updateNavArrows()},setEndDate:function(a){this._process_options({endDate:a}),this.update(),this.updateNavArrows()},setDaysOfWeekDisabled:function(a){this._process_options({daysOfWeekDisabled:a}),this.update(),this.updateNavArrows()},place:function(){if(!this.isInline){var b=parseInt(this.element.parents().filter(function(){return"auto"!=a(this).css("z-index")}).first().css("z-index"))+10,c=this.component?this.component.parent().offset():this.element.offset(),d=this.component?this.component.outerHeight(!0):this.element.outerHeight(!0);this.picker.css({top:c.top+d,left:c.left,zIndex:b})}},_allow_update:!0,update:function(){if(this._allow_update){var a,b=!1;arguments&&arguments.length&&("string"==typeof arguments[0]||arguments[0]instanceof Date)?(a=arguments[0],b=!0):(a=this.isInput?this.element.val():this.element.data("date")||this.element.find("input").val(),delete this.element.data().date),this.date=l.parseDate(a,this.o.format,this.o.language),b&&this.setValue(),this.viewDate=this.date<this.o.startDate?new Date(this.o.startDate):this.date>this.o.endDate?new Date(this.o.endDate):new Date(this.date),this.fill()}},fillDow:function(){var a=this.o.weekStart,b="<tr>";if(this.o.calendarWeeks){var c='<th class="cw">&nbsp;</th>';b+=c,this.picker.find(".datepicker-days thead tr:first-child").prepend(c)}for(;a<this.o.weekStart+7;)b+='<th class="dow">'+k[this.o.language].daysMin[a++%7]+"</th>";b+="</tr>",this.picker.find(".datepicker-days thead").append(b)},fillMonths:function(){for(var a="",b=0;12>b;)a+='<span class="month">'+k[this.o.language].monthsShort[b++]+"</span>";this.picker.find(".datepicker-months td").html(a)},setRange:function(b){b&&b.length?this.range=a.map(b,function(a){return a.valueOf()}):delete this.range,this.fill()},getClassNames:function(b){var c=[],d=this.viewDate.getUTCFullYear(),e=this.viewDate.getUTCMonth(),f=this.date.valueOf(),g=new Date;return b.getUTCFullYear()<d||b.getUTCFullYear()==d&&b.getUTCMonth()<e?c.push("old"):(b.getUTCFullYear()>d||b.getUTCFullYear()==d&&b.getUTCMonth()>e)&&c.push("new"),this.o.todayHighlight&&b.getUTCFullYear()==g.getFullYear()&&b.getUTCMonth()==g.getMonth()&&b.getUTCDate()==g.getDate()&&c.push("today"),f&&b.valueOf()==f&&c.push("active"),(b.valueOf()<this.o.startDate||b.valueOf()>this.o.endDate||-1!==a.inArray(b.getUTCDay(),this.o.daysOfWeekDisabled))&&c.push("disabled"),this.range&&(b>this.range[0]&&b<this.range[this.range.length-1]&&c.push("range"),-1!=a.inArray(b.valueOf(),this.range)&&c.push("selected")),c},fill:function(){var c,d=new Date(this.viewDate),e=d.getUTCFullYear(),f=d.getUTCMonth(),g=this.o.startDate!==-1/0?this.o.startDate.getUTCFullYear():-1/0,h=this.o.startDate!==-1/0?this.o.startDate.getUTCMonth():-1/0,i=1/0!==this.o.endDate?this.o.endDate.getUTCFullYear():1/0,j=1/0!==this.o.endDate?this.o.endDate.getUTCMonth():1/0;this.date&&this.date.valueOf(),this.picker.find(".datepicker-days thead th.datepicker-switch").text(k[this.o.language].months[f]+" "+e),this.picker.find("tfoot th.today").text(k[this.o.language].today).toggle(this.o.todayBtn!==!1),this.picker.find("tfoot th.clear").text(k[this.o.language].clear).toggle(this.o.clearBtn!==!1),this.updateNavArrows(),this.fillMonths();var m=b(e,f-1,28,0,0,0,0),n=l.getDaysInMonth(m.getUTCFullYear(),m.getUTCMonth());m.setUTCDate(n),m.setUTCDate(n-(m.getUTCDay()-this.o.weekStart+7)%7);var o=new Date(m);o.setUTCDate(o.getUTCDate()+42),o=o.valueOf();for(var p,q=[];m.valueOf()<o;){if(m.getUTCDay()==this.o.weekStart&&(q.push("<tr>"),this.o.calendarWeeks)){var r=new Date(+m+864e5*((this.o.weekStart-m.getUTCDay()-7)%7)),s=new Date(+r+864e5*((11-r.getUTCDay())%7)),t=new Date(+(t=b(s.getUTCFullYear(),0,1))+864e5*((11-t.getUTCDay())%7)),u=(s-t)/864e5/7+1;q.push('<td class="cw">'+u+"</td>")}p=this.getClassNames(m),p.push("day");var v=this.o.beforeShowDay(m);void 0===v?v={}:"boolean"==typeof v?v={enabled:v}:"string"==typeof v&&(v={classes:v}),v.enabled===!1&&p.push("disabled"),v.classes&&(p=p.concat(v.classes.split(/\s+/))),v.tooltip&&(c=v.tooltip),p=a.unique(p),q.push('<td class="'+p.join(" ")+'"'+(c?' title="'+c+'"':"")+">"+m.getUTCDate()+"</td>"),m.getUTCDay()==this.o.weekEnd&&q.push("</tr>"),m.setUTCDate(m.getUTCDate()+1)}this.picker.find(".datepicker-days tbody").empty().append(q.join(""));var w=this.date&&this.date.getUTCFullYear(),x=this.picker.find(".datepicker-months").find("th:eq(1)").text(e).end().find("span").removeClass("active");w&&w==e&&x.eq(this.date.getUTCMonth()).addClass("active"),(g>e||e>i)&&x.addClass("disabled"),e==g&&x.slice(0,h).addClass("disabled"),e==i&&x.slice(j+1).addClass("disabled"),q="",e=10*parseInt(e/10,10);var y=this.picker.find(".datepicker-years").find("th:eq(1)").text(e+"-"+(e+9)).end().find("td");e-=1;for(var z=-1;11>z;z++)q+='<span class="year'+(-1==z?" old":10==z?" new":"")+(w==e?" active":"")+(g>e||e>i?" disabled":"")+'">'+e+"</span>",e+=1;y.html(q)},updateNavArrows:function(){if(this._allow_update){var a=new Date(this.viewDate),b=a.getUTCFullYear(),c=a.getUTCMonth();switch(this.viewMode){case 0:this.o.startDate!==-1/0&&b<=this.o.startDate.getUTCFullYear()&&c<=this.o.startDate.getUTCMonth()?this.picker.find(".prev").css({visibility:"hidden"}):this.picker.find(".prev").css({visibility:"visible"}),1/0!==this.o.endDate&&b>=this.o.endDate.getUTCFullYear()&&c>=this.o.endDate.getUTCMonth()?this.picker.find(".next").css({visibility:"hidden"}):this.picker.find(".next").css({visibility:"visible"});break;case 1:case 2:this.o.startDate!==-1/0&&b<=this.o.startDate.getUTCFullYear()?this.picker.find(".prev").css({visibility:"hidden"}):this.picker.find(".prev").css({visibility:"visible"}),1/0!==this.o.endDate&&b>=this.o.endDate.getUTCFullYear()?this.picker.find(".next").css({visibility:"hidden"}):this.picker.find(".next").css({visibility:"visible"})}}},click:function(c){c.preventDefault();var d=a(c.target).closest("span, td, th");if(1==d.length)switch(d[0].nodeName.toLowerCase()){case"th":switch(d[0].className){case"datepicker-switch":this.showMode(1);break;case"prev":case"next":var e=l.modes[this.viewMode].navStep*("prev"==d[0].className?-1:1);switch(this.viewMode){case 0:this.viewDate=this.moveMonth(this.viewDate,e);break;case 1:case 2:this.viewDate=this.moveYear(this.viewDate,e)}this.fill();break;case"today":var f=new Date;f=b(f.getFullYear(),f.getMonth(),f.getDate(),0,0,0),this.showMode(-2);var g="linked"==this.o.todayBtn?null:"view";this._setDate(f,g);break;case"clear":var h;this.isInput?h=this.element:this.component&&(h=this.element.find("input")),h&&h.val("").change(),this._trigger("changeDate"),this.update(),this.o.autoclose&&this.hide()}break;case"span":if(!d.is(".disabled")){if(this.viewDate.setUTCDate(1),d.is(".month")){var i=1,j=d.parent().find("span").index(d),k=this.viewDate.getUTCFullYear();this.viewDate.setUTCMonth(j),this._trigger("changeMonth",this.viewDate),1===this.o.minViewMode&&this._setDate(b(k,j,i,0,0,0,0))}else{var k=parseInt(d.text(),10)||0,i=1,j=0;this.viewDate.setUTCFullYear(k),this._trigger("changeYear",this.viewDate),2===this.o.minViewMode&&this._setDate(b(k,j,i,0,0,0,0))}this.showMode(-1),this.fill()}break;case"td":if(d.is(".day")&&!d.is(".disabled")){var i=parseInt(d.text(),10)||1,k=this.viewDate.getUTCFullYear(),j=this.viewDate.getUTCMonth();d.is(".old")?0===j?(j=11,k-=1):j-=1:d.is(".new")&&(11==j?(j=0,k+=1):j+=1),this._setDate(b(k,j,i,0,0,0,0))}}},_setDate:function(a,b){b&&"date"!=b||(this.date=new Date(a)),b&&"view"!=b||(this.viewDate=new Date(a)),this.fill(),this.setValue(),this._trigger("changeDate");var c;this.isInput?c=this.element:this.component&&(c=this.element.find("input")),c&&(c.change(),!this.o.autoclose||b&&"date"!=b||this.hide())},moveMonth:function(a,b){if(!b)return a;var c,d,e=new Date(a.valueOf()),f=e.getUTCDate(),g=e.getUTCMonth(),h=Math.abs(b);if(b=b>0?1:-1,1==h)d=-1==b?function(){return e.getUTCMonth()==g}:function(){return e.getUTCMonth()!=c},c=g+b,e.setUTCMonth(c),(0>c||c>11)&&(c=(c+12)%12);else{for(var i=0;h>i;i++)e=this.moveMonth(e,b);c=e.getUTCMonth(),e.setUTCDate(f),d=function(){return c!=e.getUTCMonth()}}for(;d();)e.setUTCDate(--f),e.setUTCMonth(c);return e},moveYear:function(a,b){return this.moveMonth(a,12*b)},dateWithinRange:function(a){return a>=this.o.startDate&&a<=this.o.endDate},keydown:function(a){if(this.picker.is(":not(:visible)"))return 27==a.keyCode&&this.show(),void 0;var b,c,d,e=!1;switch(a.keyCode){case 27:this.hide(),a.preventDefault();break;case 37:case 39:if(!this.o.keyboardNavigation)break;b=37==a.keyCode?-1:1,a.ctrlKey?(c=this.moveYear(this.date,b),d=this.moveYear(this.viewDate,b)):a.shiftKey?(c=this.moveMonth(this.date,b),d=this.moveMonth(this.viewDate,b)):(c=new Date(this.date),c.setUTCDate(this.date.getUTCDate()+b),d=new Date(this.viewDate),d.setUTCDate(this.viewDate.getUTCDate()+b)),this.dateWithinRange(c)&&(this.date=c,this.viewDate=d,this.setValue(),this.update(),a.preventDefault(),e=!0);break;case 38:case 40:if(!this.o.keyboardNavigation)break;b=38==a.keyCode?-1:1,a.ctrlKey?(c=this.moveYear(this.date,b),d=this.moveYear(this.viewDate,b)):a.shiftKey?(c=this.moveMonth(this.date,b),d=this.moveMonth(this.viewDate,b)):(c=new Date(this.date),c.setUTCDate(this.date.getUTCDate()+7*b),d=new Date(this.viewDate),d.setUTCDate(this.viewDate.getUTCDate()+7*b)),this.dateWithinRange(c)&&(this.date=c,this.viewDate=d,this.setValue(),this.update(),a.preventDefault(),e=!0);break;case 13:this.hide(),a.preventDefault();break;case 9:this.hide()}if(e){this._trigger("changeDate");var f;this.isInput?f=this.element:this.component&&(f=this.element.find("input")),f&&f.change()}},showMode:function(a){a&&(this.viewMode=Math.max(this.o.minViewMode,Math.min(2,this.viewMode+a))),this.picker.find(">div").hide().filter(".datepicker-"+l.modes[this.viewMode].clsName).css("display","block"),this.updateNavArrows()}};var f=function(b,c){this.element=a(b),this.inputs=a.map(c.inputs,function(a){return a.jquery?a[0]:a}),delete c.inputs,a(this.inputs).datepicker(c).bind("changeDate",a.proxy(this.dateUpdated,this)),this.pickers=a.map(this.inputs,function(b){return a(b).data("datepicker")}),this.updateDates()};f.prototype={updateDates:function(){this.dates=a.map(this.pickers,function(a){return a.date}),this.updateRanges()},updateRanges:function(){var b=a.map(this.dates,function(a){return a.valueOf()});a.each(this.pickers,function(a,c){c.setRange(b)})},dateUpdated:function(b){var c=a(b.target).data("datepicker"),d=c.getUTCDate(),e=a.inArray(b.target,this.inputs),f=this.inputs.length;if(-1!=e){if(d<this.dates[e])for(;e>=0&&d<this.dates[e];)this.pickers[e--].setUTCDate(d);else if(d>this.dates[e])for(;f>e&&d>this.dates[e];)this.pickers[e++].setUTCDate(d);this.updateDates()}},remove:function(){a.map(this.pickers,function(a){a.remove()}),delete this.element.data().datepicker}};var g=a.fn.datepicker,h=a.fn.datepicker=function(b){var g=Array.apply(null,arguments);g.shift();var h;return this.each(function(){var j=a(this),k=j.data("datepicker"),l="object"==typeof b&&b;if(!k){var m=c(this,"date"),n=a.extend({},i,m,l),o=d(n.language),p=a.extend({},i,o,m,l);if(j.is(".input-daterange")||p.inputs){var q={inputs:p.inputs||j.find("input").toArray()};j.data("datepicker",k=new f(this,a.extend(p,q)))}else j.data("datepicker",k=new e(this,p))}return"string"==typeof b&&"function"==typeof k[b]&&(h=k[b].apply(k,g),void 0!==h)?!1:void 0}),void 0!==h?h:this},i=a.fn.datepicker.defaults={autoclose:!1,beforeShowDay:a.noop,calendarWeeks:!1,clearBtn:!1,daysOfWeekDisabled:[],endDate:1/0,forceParse:!0,format:"mm/dd/yyyy",keyboardNavigation:!0,language:"en",minViewMode:0,rtl:!1,startDate:-1/0,startView:0,todayBtn:!1,todayHighlight:!1,weekStart:0},j=a.fn.datepicker.locale_opts=["format","rtl","weekStart"];a.fn.datepicker.Constructor=e;var k=a.fn.datepicker.dates={en:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat","Sun"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa","Su"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],today:"Today",clear:"Clear"}},l={modes:[{clsName:"days",navFnc:"Month",navStep:1},{clsName:"months",navFnc:"FullYear",navStep:1},{clsName:"years",navFnc:"FullYear",navStep:10}],isLeapYear:function(a){return 0===a%4&&0!==a%100||0===a%400
7
  },getDaysInMonth:function(a,b){return[31,l.isLeapYear(a)?29:28,31,30,31,30,31,31,30,31,30,31][b]},validParts:/dd?|DD?|mm?|MM?|yy(?:yy)?/g,nonpunctuation:/[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,parseFormat:function(a){var b=a.replace(this.validParts,"\0").split("\0"),c=a.match(this.validParts);if(!b||!b.length||!c||0===c.length)throw new Error("Invalid date format.");return{separators:b,parts:c}},parseDate:function(c,d,f){if(c instanceof Date)return c;if("string"==typeof d&&(d=l.parseFormat(d)),/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(c)){var g,h,i=/([\-+]\d+)([dmwy])/,j=c.match(/([\-+]\d+)([dmwy])/g);c=new Date;for(var m=0;m<j.length;m++)switch(g=i.exec(j[m]),h=parseInt(g[1]),g[2]){case"d":c.setUTCDate(c.getUTCDate()+h);break;case"m":c=e.prototype.moveMonth.call(e.prototype,c,h);break;case"w":c.setUTCDate(c.getUTCDate()+7*h);break;case"y":c=e.prototype.moveYear.call(e.prototype,c,h)}return b(c.getUTCFullYear(),c.getUTCMonth(),c.getUTCDate(),0,0,0)}var n,o,g,j=c&&c.match(this.nonpunctuation)||[],c=new Date,p={},q=["yyyy","yy","M","MM","m","mm","d","dd"],r={yyyy:function(a,b){return a.setUTCFullYear(b)},yy:function(a,b){return a.setUTCFullYear(2e3+b)},m:function(a,b){for(b-=1;0>b;)b+=12;for(b%=12,a.setUTCMonth(b);a.getUTCMonth()!=b;)a.setUTCDate(a.getUTCDate()-1);return a},d:function(a,b){return a.setUTCDate(b)}};r.M=r.MM=r.mm=r.m,r.dd=r.d,c=b(c.getFullYear(),c.getMonth(),c.getDate(),0,0,0);var s=d.parts.slice();if(j.length!=s.length&&(s=a(s).filter(function(b,c){return-1!==a.inArray(c,q)}).toArray()),j.length==s.length){for(var m=0,t=s.length;t>m;m++){if(n=parseInt(j[m],10),g=s[m],isNaN(n))switch(g){case"MM":o=a(k[f].months).filter(function(){var a=this.slice(0,j[m].length),b=j[m].slice(0,a.length);return a==b}),n=a.inArray(o[0],k[f].months)+1;break;case"M":o=a(k[f].monthsShort).filter(function(){var a=this.slice(0,j[m].length),b=j[m].slice(0,a.length);return a==b}),n=a.inArray(o[0],k[f].monthsShort)+1}p[g]=n}for(var u,m=0;m<q.length;m++)u=q[m],u in p&&!isNaN(p[u])&&r[u](c,p[u])}return c},formatDate:function(b,c,d){"string"==typeof c&&(c=l.parseFormat(c));var e={d:b.getUTCDate(),D:k[d].daysShort[b.getUTCDay()],DD:k[d].days[b.getUTCDay()],m:b.getUTCMonth()+1,M:k[d].monthsShort[b.getUTCMonth()],MM:k[d].months[b.getUTCMonth()],yy:b.getUTCFullYear().toString().substring(2),yyyy:b.getUTCFullYear()};e.dd=(e.d<10?"0":"")+e.d,e.mm=(e.m<10?"0":"")+e.m;for(var b=[],f=a.extend([],c.separators),g=0,h=c.parts.length;h>=g;g++)f.length&&b.push(f.shift()),b.push(e[c.parts[g]]);return b.join("")},headTemplate:'<thead><tr><th class="prev"><i class="icon-arrow-left"/></th><th colspan="5" class="datepicker-switch"></th><th class="next"><i class="icon-arrow-right"/></th></tr></thead>',contTemplate:'<tbody><tr><td colspan="7"></td></tr></tbody>',footTemplate:'<tfoot><tr><th colspan="7" class="today"></th></tr><tr><th colspan="7" class="clear"></th></tr></tfoot>'};l.template='<div class="datepicker"><div class="datepicker-days"><table class=" table-condensed">'+l.headTemplate+"<tbody></tbody>"+l.footTemplate+"</table>"+"</div>"+'<div class="datepicker-months">'+'<table class="table-condensed">'+l.headTemplate+l.contTemplate+l.footTemplate+"</table>"+"</div>"+'<div class="datepicker-years">'+'<table class="table-condensed">'+l.headTemplate+l.contTemplate+l.footTemplate+"</table>"+"</div>"+"</div>",a.fn.datepicker.DPGlobal=l,a.fn.datepicker.noConflict=function(){return a.fn.datepicker=g,this},a(document).on("focus.datepicker.data-api click.datepicker.data-api",'[data-provide="datepicker"]',function(b){var c=a(this);c.data("datepicker")||(b.preventDefault(),h.call(c,"show"))}),a(function(){h.call(a('[data-provide="datepicker-inline"]'))})}(window.jQuery),function(a){"use strict";a.fn.bdatepicker=a.fn.datepicker.noConflict(),a.fn.datepicker||(a.fn.datepicker=a.fn.bdatepicker);var b=function(a){this.init("date",a,b.defaults),this.initPicker(a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{initPicker:function(b,c){this.options.viewformat||(this.options.viewformat=this.options.format),b.datepicker=a.fn.editableutils.tryParseJson(b.datepicker,!0),this.options.datepicker=a.extend({},c.datepicker,b.datepicker,{format:this.options.viewformat}),this.options.datepicker.language=this.options.datepicker.language||"en",this.dpg=a.fn.bdatepicker.DPGlobal,this.parsedFormat=this.dpg.parseFormat(this.options.format),this.parsedViewFormat=this.dpg.parseFormat(this.options.viewformat)},render:function(){this.$input.bdatepicker(this.options.datepicker),this.options.clear&&(this.$clear=a('<a href="#"></a>').html(this.options.clear).click(a.proxy(function(a){a.preventDefault(),a.stopPropagation(),this.clear()},this)),this.$tpl.parent().append(a('<div class="editable-clear">').append(this.$clear)))},value2html:function(a,c){var d=a?this.dpg.formatDate(a,this.parsedViewFormat,this.options.datepicker.language):"";b.superclass.value2html.call(this,d,c)},html2value:function(a){return this.parseDate(a,this.parsedViewFormat)},value2str:function(a){return a?this.dpg.formatDate(a,this.parsedFormat,this.options.datepicker.language):""},str2value:function(a){return this.parseDate(a,this.parsedFormat)},value2submit:function(a){return this.value2str(a)},value2input:function(a){this.$input.bdatepicker("update",a)},input2value:function(){return this.$input.data("datepicker").date},activate:function(){},clear:function(){this.$input.data("datepicker").date=null,this.$input.find(".active").removeClass("active"),this.options.showbuttons||this.$input.closest("form").submit()},autosubmit:function(){this.$input.on("mouseup",".day",function(b){if(!a(b.currentTarget).is(".old")&&!a(b.currentTarget).is(".new")){var c=a(this).closest("form");setTimeout(function(){c.submit()},200)}})},parseDate:function(a,b){var c,d=null;return a&&(d=this.dpg.parseDate(a,b,this.options.datepicker.language),"string"==typeof a&&(c=this.dpg.formatDate(d,b,this.options.datepicker.language),a!==c&&(d=null))),d}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:'<div class="editable-date well"></div>',inputclass:null,format:"yyyy-mm-dd",viewformat:null,datepicker:{weekStart:0,startView:0,minViewMode:0,autoclose:!1},clear:"&times; clear"}),a.fn.editabletypes.date=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("datefield",a,b.defaults),this.initPicker(a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.date),a.extend(b.prototype,{render:function(){this.$input=this.$tpl.find("input"),this.setClass(),this.setAttr("placeholder"),this.$tpl.bdatepicker(this.options.datepicker),this.$input.off("focus keydown"),this.$input.keyup(a.proxy(function(){this.$tpl.removeData("date"),this.$tpl.bdatepicker("update")},this))},value2input:function(a){this.$input.val(a?this.dpg.formatDate(a,this.parsedViewFormat,this.options.datepicker.language):""),this.$tpl.bdatepicker("update")},input2value:function(){return this.html2value(this.$input.val())},activate:function(){a.fn.editabletypes.text.prototype.activate.call(this)},autosubmit:function(){}}),b.defaults=a.extend({},a.fn.editabletypes.date.defaults,{tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',inputclass:"input-small",datepicker:{weekStart:0,startView:0,minViewMode:0,autoclose:!0}}),a.fn.editabletypes.datefield=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("datetime",a,b.defaults),this.initPicker(a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{initPicker:function(b,c){this.options.viewformat||(this.options.viewformat=this.options.format),b.datetimepicker=a.fn.editableutils.tryParseJson(b.datetimepicker,!0),this.options.datetimepicker=a.extend({},c.datetimepicker,b.datetimepicker,{format:this.options.viewformat}),this.options.datetimepicker.language=this.options.datetimepicker.language||"en",this.dpg=a.fn.datetimepicker.DPGlobal,this.parsedFormat=this.dpg.parseFormat(this.options.format,this.options.formatType),this.parsedViewFormat=this.dpg.parseFormat(this.options.viewformat,this.options.formatType)},render:function(){this.$input.datetimepicker(this.options.datetimepicker),this.$input.on("changeMode",function(){var b=a(this).closest("form").parent();setTimeout(function(){b.triggerHandler("resize")},0)}),this.options.clear&&(this.$clear=a('<a href="#"></a>').html(this.options.clear).click(a.proxy(function(a){a.preventDefault(),a.stopPropagation(),this.clear()},this)),this.$tpl.parent().append(a('<div class="editable-clear">').append(this.$clear)))},value2html:function(a,c){var d=a?this.dpg.formatDate(this.toUTC(a),this.parsedViewFormat,this.options.datetimepicker.language,this.options.formatType):"";return c?(b.superclass.value2html.call(this,d,c),void 0):d},html2value:function(a){var b=this.parseDate(a,this.parsedViewFormat);return b?this.fromUTC(b):null},value2str:function(a){return a?this.dpg.formatDate(this.toUTC(a),this.parsedFormat,this.options.datetimepicker.language,this.options.formatType):""},str2value:function(a){var b=this.parseDate(a,this.parsedFormat);return b?this.fromUTC(b):null},value2submit:function(a){return this.value2str(a)},value2input:function(a){a&&this.$input.data("datetimepicker").setDate(a)},input2value:function(){var a=this.$input.data("datetimepicker");return a.date?a.getDate():null},activate:function(){},clear:function(){this.$input.data("datetimepicker").date=null,this.$input.find(".active").removeClass("active"),this.options.showbuttons||this.$input.closest("form").submit()},autosubmit:function(){this.$input.on("mouseup",".minute",function(){var b=a(this).closest("form");setTimeout(function(){b.submit()},200)})},toUTC:function(a){return a?new Date(a.valueOf()-6e4*a.getTimezoneOffset()):a},fromUTC:function(a){return a?new Date(a.valueOf()+6e4*a.getTimezoneOffset()):a},parseDate:function(a,b){var c,d=null;return a&&(d=this.dpg.parseDate(a,b,this.options.datetimepicker.language,this.options.formatType),"string"==typeof a&&(c=this.dpg.formatDate(d,b,this.options.datetimepicker.language,this.options.formatType),a!==c&&(d=null))),d}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:'<div class="editable-date well"></div>',inputclass:null,format:"yyyy-mm-dd hh:ii",formatType:"standard",viewformat:null,datetimepicker:{todayHighlight:!1,autoclose:!1},clear:"&times; clear"}),a.fn.editabletypes.datetime=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("datetimefield",a,b.defaults),this.initPicker(a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.datetime),a.extend(b.prototype,{render:function(){this.$input=this.$tpl.find("input"),this.setClass(),this.setAttr("placeholder"),this.$tpl.datetimepicker(this.options.datetimepicker),this.$input.off("focus keydown"),this.$input.keyup(a.proxy(function(){this.$tpl.removeData("date"),this.$tpl.datetimepicker("update")},this))},value2input:function(a){this.$input.val(this.value2html(a)),this.$tpl.datetimepicker("update")},input2value:function(){return this.html2value(this.$input.val())},activate:function(){a.fn.editabletypes.text.prototype.activate.call(this)},autosubmit:function(){}}),b.defaults=a.extend({},a.fn.editabletypes.datetime.defaults,{tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',inputclass:"input-medium",datetimepicker:{todayHighlight:!1,autoclose:!0}}),a.fn.editabletypes.datetimefield=b}(window.jQuery);
1
+ /*! X-editable - v1.5.1
2
+ * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
3
+ * http://github.com/vitalets/x-editable
4
+ * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
5
+ !function(a){"use strict";var b=function(b,c){this.options=a.extend({},a.fn.editableform.defaults,c),this.$div=a(b),this.options.scope||(this.options.scope=this)};b.prototype={constructor:b,initInput:function(){this.input=this.options.input,this.value=this.input.str2value(this.options.value),this.input.prerender()},initTemplate:function(){this.$form=a(a.fn.editableform.template)},initButtons:function(){var b=this.$form.find(".editable-buttons");b.append(a.fn.editableform.buttons),"bottom"===this.options.showbuttons&&b.addClass("editable-buttons-bottom")},render:function(){this.$loading=a(a.fn.editableform.loading),this.$div.empty().append(this.$loading),this.initTemplate(),this.options.showbuttons?this.initButtons():this.$form.find(".editable-buttons").remove(),this.showLoading(),this.isSaving=!1,this.$div.triggerHandler("rendering"),this.initInput(),this.$form.find("div.editable-input").append(this.input.$tpl),this.$div.append(this.$form),a.when(this.input.render()).then(a.proxy(function(){if(this.options.showbuttons||this.input.autosubmit(),this.$form.find(".editable-cancel").click(a.proxy(this.cancel,this)),this.input.error)this.error(this.input.error),this.$form.find(".editable-submit").attr("disabled",!0),this.input.$input.attr("disabled",!0),this.$form.submit(function(a){a.preventDefault()});else{this.error(!1),this.input.$input.removeAttr("disabled"),this.$form.find(".editable-submit").removeAttr("disabled");var b=null===this.value||void 0===this.value||""===this.value?this.options.defaultValue:this.value;this.input.value2input(b),this.$form.submit(a.proxy(this.submit,this))}this.$div.triggerHandler("rendered"),this.showForm(),this.input.postrender&&this.input.postrender()},this))},cancel:function(){this.$div.triggerHandler("cancel")},showLoading:function(){var a,b;this.$form?(a=this.$form.outerWidth(),b=this.$form.outerHeight(),a&&this.$loading.width(a),b&&this.$loading.height(b),this.$form.hide()):(a=this.$loading.parent().width(),a&&this.$loading.width(a)),this.$loading.show()},showForm:function(a){this.$loading.hide(),this.$form.show(),a!==!1&&this.input.activate(),this.$div.triggerHandler("show")},error:function(b){var c,d=this.$form.find(".control-group"),e=this.$form.find(".editable-error-block");if(b===!1)d.removeClass(a.fn.editableform.errorGroupClass),e.removeClass(a.fn.editableform.errorBlockClass).empty().hide();else{if(b){c=(""+b).split("\n");for(var f=0;f<c.length;f++)c[f]=a("<div>").text(c[f]).html();b=c.join("<br>")}d.addClass(a.fn.editableform.errorGroupClass),e.addClass(a.fn.editableform.errorBlockClass).html(b).show()}},submit:function(b){b.stopPropagation(),b.preventDefault();var c=this.input.input2value(),d=this.validate(c);if("object"===a.type(d)&&void 0!==d.newValue){if(c=d.newValue,this.input.value2input(c),"string"==typeof d.msg)return this.error(d.msg),this.showForm(),void 0}else if(d)return this.error(d),this.showForm(),void 0;if(!this.options.savenochange&&this.input.value2str(c)==this.input.value2str(this.value))return this.$div.triggerHandler("nochange"),void 0;var e=this.input.value2submit(c);this.isSaving=!0,a.when(this.save(e)).done(a.proxy(function(a){this.isSaving=!1;var b="function"==typeof this.options.success?this.options.success.call(this.options.scope,a,c):null;return b===!1?(this.error(!1),this.showForm(!1),void 0):"string"==typeof b?(this.error(b),this.showForm(),void 0):(b&&"object"==typeof b&&b.hasOwnProperty("newValue")&&(c=b.newValue),this.error(!1),this.value=c,this.$div.triggerHandler("save",{newValue:c,submitValue:e,response:a}),void 0)},this)).fail(a.proxy(function(a){this.isSaving=!1;var b;b="function"==typeof this.options.error?this.options.error.call(this.options.scope,a,c):"string"==typeof a?a:a.responseText||a.statusText||"Unknown error!",this.error(b),this.showForm()},this))},save:function(b){this.options.pk=a.fn.editableutils.tryParseJson(this.options.pk,!0);var c,d="function"==typeof this.options.pk?this.options.pk.call(this.options.scope):this.options.pk,e=!!("function"==typeof this.options.url||this.options.url&&("always"===this.options.send||"auto"===this.options.send&&null!==d&&void 0!==d));return e?(this.showLoading(),c={name:this.options.name||"",value:b,pk:d},"function"==typeof this.options.params?c=this.options.params.call(this.options.scope,c):(this.options.params=a.fn.editableutils.tryParseJson(this.options.params,!0),a.extend(c,this.options.params)),"function"==typeof this.options.url?this.options.url.call(this.options.scope,c):a.ajax(a.extend({url:this.options.url,data:c,type:"POST"},this.options.ajaxOptions))):void 0},validate:function(a){return void 0===a&&(a=this.value),"function"==typeof this.options.validate?this.options.validate.call(this.options.scope,a):void 0},option:function(a,b){a in this.options&&(this.options[a]=b),"value"===a&&this.setValue(b)},setValue:function(a,b){this.value=b?this.input.str2value(a):a,this.$form&&this.$form.is(":visible")&&this.input.value2input(this.value)}},a.fn.editableform=function(c){var d=arguments;return this.each(function(){var e=a(this),f=e.data("editableform"),g="object"==typeof c&&c;f||e.data("editableform",f=new b(this,g)),"string"==typeof c&&f[c].apply(f,Array.prototype.slice.call(d,1))})},a.fn.editableform.Constructor=b,a.fn.editableform.defaults={type:"text",url:null,params:null,name:null,pk:null,value:null,defaultValue:null,send:"auto",validate:null,success:null,error:null,ajaxOptions:null,showbuttons:!0,scope:null,savenochange:!1},a.fn.editableform.template='<form class="form-inline editableform"><div class="control-group"><div><div class="editable-input"></div><div class="editable-buttons"></div></div><div class="editable-error-block"></div></div></form>',a.fn.editableform.loading='<div class="editableform-loading"></div>',a.fn.editableform.buttons='<button type="submit" class="editable-submit">ok</button><button type="button" class="editable-cancel">cancel</button>',a.fn.editableform.errorGroupClass=null,a.fn.editableform.errorBlockClass="editable-error",a.fn.editableform.engine="jquery"}(window.jQuery),function(a){"use strict";a.fn.editableutils={inherit:function(a,b){var c=function(){};c.prototype=b.prototype,a.prototype=new c,a.prototype.constructor=a,a.superclass=b.prototype},setCursorPosition:function(a,b){if(a.setSelectionRange)a.setSelectionRange(b,b);else if(a.createTextRange){var c=a.createTextRange();c.collapse(!0),c.moveEnd("character",b),c.moveStart("character",b),c.select()}},tryParseJson:function(a,b){if("string"==typeof a&&a.length&&a.match(/^[\{\[].*[\}\]]$/))if(b)try{a=new Function("return "+a)()}catch(c){}finally{return a}else a=new Function("return "+a)();return a},sliceObj:function(b,c,d){var e,f,g={};if(!a.isArray(c)||!c.length)return g;for(var h=0;h<c.length;h++)e=c[h],b.hasOwnProperty(e)&&(g[e]=b[e]),d!==!0&&(f=e.toLowerCase(),b.hasOwnProperty(f)&&(g[e]=b[f]));return g},getConfigData:function(b){var c={};return a.each(b.data(),function(a,b){("object"!=typeof b||b&&"object"==typeof b&&(b.constructor===Object||b.constructor===Array))&&(c[a]=b)}),c},objectKeys:function(a){if(Object.keys)return Object.keys(a);if(a!==Object(a))throw new TypeError("Object.keys called on a non-object");var b,c=[];for(b in a)Object.prototype.hasOwnProperty.call(a,b)&&c.push(b);return c},escape:function(b){return a("<div>").text(b).html()},itemsByValue:function(b,c,d){if(!c||null===b)return[];if("function"!=typeof d){var e=d||"value";d=function(a){return a[e]}}var f=a.isArray(b),g=[],h=this;return a.each(c,function(c,e){if(e.children)g=g.concat(h.itemsByValue(b,e.children,d));else if(f)a.grep(b,function(a){return a==(e&&"object"==typeof e?d(e):e)}).length&&g.push(e);else{var i=e&&"object"==typeof e?d(e):e;b==i&&g.push(e)}}),g},createInput:function(b){var c,d,e,f=b.type;return"date"===f&&("inline"===b.mode?a.fn.editabletypes.datefield?f="datefield":a.fn.editabletypes.dateuifield&&(f="dateuifield"):a.fn.editabletypes.date?f="date":a.fn.editabletypes.dateui&&(f="dateui"),"date"!==f||a.fn.editabletypes.date||(f="combodate")),"datetime"===f&&"inline"===b.mode&&(f="datetimefield"),"wysihtml5"!==f||a.fn.editabletypes[f]||(f="textarea"),"function"==typeof a.fn.editabletypes[f]?(c=a.fn.editabletypes[f],d=this.sliceObj(b,this.objectKeys(c.defaults)),e=new c(d)):(a.error("Unknown type: "+f),!1)},supportsTransitions:function(){var a=document.body||document.documentElement,b=a.style,c="transition",d=["Moz","Webkit","Khtml","O","ms"];if("string"==typeof b[c])return!0;c=c.charAt(0).toUpperCase()+c.substr(1);for(var e=0;e<d.length;e++)if("string"==typeof b[d[e]+c])return!0;return!1}}}(window.jQuery),function(a){"use strict";var b=function(a,b){this.init(a,b)},c=function(a,b){this.init(a,b)};b.prototype={containerName:null,containerDataName:null,innerCss:null,containerClass:"editable-container editable-popup",defaults:{},init:function(c,d){this.$element=a(c),this.options=a.extend({},a.fn.editableContainer.defaults,d),this.splitOptions(),this.formOptions.scope=this.$element[0],this.initContainer(),this.delayedHide=!1,this.$element.on("destroyed",a.proxy(function(){this.destroy()},this)),a(document).data("editable-handlers-attached")||(a(document).on("keyup.editable",function(b){27===b.which&&a(".editable-open").editableContainer("hide")}),a(document).on("click.editable",function(c){var d,e=a(c.target),f=[".editable-container",".ui-datepicker-header",".datepicker",".modal-backdrop",".bootstrap-wysihtml5-insert-image-modal",".bootstrap-wysihtml5-insert-link-modal"];if(a.contains(document.documentElement,c.target)&&!e.is(document)){for(d=0;d<f.length;d++)if(e.is(f[d])||e.parents(f[d]).length)return;b.prototype.closeOthers(c.target)}}),a(document).data("editable-handlers-attached",!0))},splitOptions:function(){if(this.containerOptions={},this.formOptions={},!a.fn[this.containerName])throw new Error(this.containerName+" not found. Have you included corresponding js file?");for(var b in this.options)b in this.defaults?this.containerOptions[b]=this.options[b]:this.formOptions[b]=this.options[b]},tip:function(){return this.container()?this.container().$tip:null},container:function(){var a;return this.containerDataName&&(a=this.$element.data(this.containerDataName))?a:a=this.$element.data(this.containerName)},call:function(){this.$element[this.containerName].apply(this.$element,arguments)},initContainer:function(){this.call(this.containerOptions)},renderForm:function(){this.$form.editableform(this.formOptions).on({save:a.proxy(this.save,this),nochange:a.proxy(function(){this.hide("nochange")},this),cancel:a.proxy(function(){this.hide("cancel")},this),show:a.proxy(function(){this.delayedHide?(this.hide(this.delayedHide.reason),this.delayedHide=!1):this.setPosition()},this),rendering:a.proxy(this.setPosition,this),resize:a.proxy(this.setPosition,this),rendered:a.proxy(function(){this.$element.triggerHandler("shown",a(this.options.scope).data("editable"))},this)}).editableform("render")},show:function(b){this.$element.addClass("editable-open"),b!==!1&&this.closeOthers(this.$element[0]),this.innerShow(),this.tip().addClass(this.containerClass),this.$form,this.$form=a("<div>"),this.tip().is(this.innerCss)?this.tip().append(this.$form):this.tip().find(this.innerCss).append(this.$form),this.renderForm()},hide:function(a){if(this.tip()&&this.tip().is(":visible")&&this.$element.hasClass("editable-open")){if(this.$form.data("editableform").isSaving)return this.delayedHide={reason:a},void 0;this.delayedHide=!1,this.$element.removeClass("editable-open"),this.innerHide(),this.$element.triggerHandler("hidden",a||"manual")}},innerShow:function(){},innerHide:function(){},toggle:function(a){this.container()&&this.tip()&&this.tip().is(":visible")?this.hide():this.show(a)},setPosition:function(){},save:function(a,b){this.$element.triggerHandler("save",b),this.hide("save")},option:function(a,b){this.options[a]=b,a in this.containerOptions?(this.containerOptions[a]=b,this.setContainerOption(a,b)):(this.formOptions[a]=b,this.$form&&this.$form.editableform("option",a,b))},setContainerOption:function(a,b){this.call("option",a,b)},destroy:function(){this.hide(),this.innerDestroy(),this.$element.off("destroyed"),this.$element.removeData("editableContainer")},innerDestroy:function(){},closeOthers:function(b){a(".editable-open").each(function(c,d){if(d!==b&&!a(d).find(b).length){var e=a(d),f=e.data("editableContainer");f&&("cancel"===f.options.onblur?e.data("editableContainer").hide("onblur"):"submit"===f.options.onblur&&e.data("editableContainer").tip().find("form").submit())}})},activate:function(){this.tip&&this.tip().is(":visible")&&this.$form&&this.$form.data("editableform").input.activate()}},a.fn.editableContainer=function(d){var e=arguments;return this.each(function(){var f=a(this),g="editableContainer",h=f.data(g),i="object"==typeof d&&d,j="inline"===i.mode?c:b;h||f.data(g,h=new j(this,i)),"string"==typeof d&&h[d].apply(h,Array.prototype.slice.call(e,1))})},a.fn.editableContainer.Popup=b,a.fn.editableContainer.Inline=c,a.fn.editableContainer.defaults={value:null,placement:"top",autohide:!0,onblur:"cancel",anim:!1,mode:"popup"},jQuery.event.special.destroyed={remove:function(a){a.handler&&a.handler()}}}(window.jQuery),function(a){"use strict";a.extend(a.fn.editableContainer.Inline.prototype,a.fn.editableContainer.Popup.prototype,{containerName:"editableform",innerCss:".editable-inline",containerClass:"editable-container editable-inline",initContainer:function(){this.$tip=a("<span></span>"),this.options.anim||(this.options.anim=0)},splitOptions:function(){this.containerOptions={},this.formOptions=this.options},tip:function(){return this.$tip},innerShow:function(){this.$element.hide(),this.tip().insertAfter(this.$element).show()},innerHide:function(){this.$tip.hide(this.options.anim,a.proxy(function(){this.$element.show(),this.innerDestroy()},this))},innerDestroy:function(){this.tip()&&this.tip().empty().remove()}})}(window.jQuery),function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.editable.defaults,c,a.fn.editableutils.getConfigData(this.$element)),this.options.selector?this.initLive():this.init(),this.options.highlight&&!a.fn.editableutils.supportsTransitions()&&(this.options.highlight=!1)};b.prototype={constructor:b,init:function(){var b,c=!1;if(this.options.name=this.options.name||this.$element.attr("id"),this.options.scope=this.$element[0],this.input=a.fn.editableutils.createInput(this.options),this.input){switch(void 0===this.options.value||null===this.options.value?(this.value=this.input.html2value(a.trim(this.$element.html())),c=!0):(this.options.value=a.fn.editableutils.tryParseJson(this.options.value,!0),this.value="string"==typeof this.options.value?this.input.str2value(this.options.value):this.options.value),this.$element.addClass("editable"),"textarea"===this.input.type&&this.$element.addClass("editable-pre-wrapped"),"manual"!==this.options.toggle?(this.$element.addClass("editable-click"),this.$element.on(this.options.toggle+".editable",a.proxy(function(a){if(this.options.disabled||a.preventDefault(),"mouseenter"===this.options.toggle)this.show();else{var b="click"!==this.options.toggle;this.toggle(b)}},this))):this.$element.attr("tabindex",-1),"function"==typeof this.options.display&&(this.options.autotext="always"),this.options.autotext){case"always":b=!0;break;case"auto":b=!a.trim(this.$element.text()).length&&null!==this.value&&void 0!==this.value&&!c;break;default:b=!1}a.when(b?this.render():!0).then(a.proxy(function(){this.options.disabled?this.disable():this.enable(),this.$element.triggerHandler("init",this)},this))}},initLive:function(){var b=this.options.selector;this.options.selector=!1,this.options.autotext="never",this.$element.on(this.options.toggle+".editable",b,a.proxy(function(b){var c=a(b.target);c.data("editable")||(c.hasClass(this.options.emptyclass)&&c.empty(),c.editable(this.options).trigger(b))},this))},render:function(a){return this.options.display!==!1?this.input.value2htmlFinal?this.input.value2html(this.value,this.$element[0],this.options.display,a):"function"==typeof this.options.display?this.options.display.call(this.$element[0],this.value,a):this.input.value2html(this.value,this.$element[0]):void 0},enable:function(){this.options.disabled=!1,this.$element.removeClass("editable-disabled"),this.handleEmpty(this.isEmpty),"manual"!==this.options.toggle&&"-1"===this.$element.attr("tabindex")&&this.$element.removeAttr("tabindex")},disable:function(){this.options.disabled=!0,this.hide(),this.$element.addClass("editable-disabled"),this.handleEmpty(this.isEmpty),this.$element.attr("tabindex",-1)},toggleDisabled:function(){this.options.disabled?this.enable():this.disable()},option:function(b,c){return b&&"object"==typeof b?(a.each(b,a.proxy(function(b,c){this.option(a.trim(b),c)},this)),void 0):(this.options[b]=c,"disabled"===b?c?this.disable():this.enable():("value"===b&&this.setValue(c),this.container&&this.container.option(b,c),this.input.option&&this.input.option(b,c),void 0))},handleEmpty:function(b){this.options.display!==!1&&(this.isEmpty=void 0!==b?b:"function"==typeof this.input.isEmpty?this.input.isEmpty(this.$element):""===a.trim(this.$element.html()),this.options.disabled?this.isEmpty&&(this.$element.empty(),this.options.emptyclass&&this.$element.removeClass(this.options.emptyclass)):this.isEmpty?(this.$element.html(this.options.emptytext),this.options.emptyclass&&this.$element.addClass(this.options.emptyclass)):this.options.emptyclass&&this.$element.removeClass(this.options.emptyclass))},show:function(b){if(!this.options.disabled){if(this.container){if(this.container.tip().is(":visible"))return}else{var c=a.extend({},this.options,{value:this.value,input:this.input});this.$element.editableContainer(c),this.$element.on("save.internal",a.proxy(this.save,this)),this.container=this.$element.data("editableContainer")}this.container.show(b)}},hide:function(){this.container&&this.container.hide()},toggle:function(a){this.container&&this.container.tip().is(":visible")?this.hide():this.show(a)},save:function(a,b){if(this.options.unsavedclass){var c=!1;c=c||"function"==typeof this.options.url,c=c||this.options.display===!1,c=c||void 0!==b.response,c=c||this.options.savenochange&&this.input.value2str(this.value)!==this.input.value2str(b.newValue),c?this.$element.removeClass(this.options.unsavedclass):this.$element.addClass(this.options.unsavedclass)}if(this.options.highlight){var d=this.$element,e=d.css("background-color");d.css("background-color",this.options.highlight),setTimeout(function(){"transparent"===e&&(e=""),d.css("background-color",e),d.addClass("editable-bg-transition"),setTimeout(function(){d.removeClass("editable-bg-transition")},1700)},10)}this.setValue(b.newValue,!1,b.response)},validate:function(){return"function"==typeof this.options.validate?this.options.validate.call(this,this.value):void 0},setValue:function(b,c,d){this.value=c?this.input.str2value(b):b,this.container&&this.container.option("value",this.value),a.when(this.render(d)).then(a.proxy(function(){this.handleEmpty()},this))},activate:function(){this.container&&this.container.activate()},destroy:function(){this.disable(),this.container&&this.container.destroy(),this.input.destroy(),"manual"!==this.options.toggle&&(this.$element.removeClass("editable-click"),this.$element.off(this.options.toggle+".editable")),this.$element.off("save.internal"),this.$element.removeClass("editable editable-open editable-disabled"),this.$element.removeData("editable")}},a.fn.editable=function(c){var d={},e=arguments,f="editable";switch(c){case"validate":return this.each(function(){var b,c=a(this),e=c.data(f);e&&(b=e.validate())&&(d[e.options.name]=b)}),d;case"getValue":return 2===arguments.length&&arguments[1]===!0?d=this.eq(0).data(f).value:this.each(function(){var b=a(this),c=b.data(f);c&&void 0!==c.value&&null!==c.value&&(d[c.options.name]=c.input.value2submit(c.value))}),d;case"submit":var g=arguments[1]||{},h=this,i=this.editable("validate");if(a.isEmptyObject(i)){var j={};if(1===h.length){var k=h.data("editable"),l={name:k.options.name||"",value:k.input.value2submit(k.value),pk:"function"==typeof k.options.pk?k.options.pk.call(k.options.scope):k.options.pk};"function"==typeof k.options.params?l=k.options.params.call(k.options.scope,l):(k.options.params=a.fn.editableutils.tryParseJson(k.options.params,!0),a.extend(l,k.options.params)),j={url:k.options.url,data:l,type:"POST"},g.success=g.success||k.options.success,g.error=g.error||k.options.error}else{var m=this.editable("getValue");j={url:g.url,data:m,type:"POST"}}j.success="function"==typeof g.success?function(a){g.success.call(h,a,g)}:a.noop,j.error="function"==typeof g.error?function(){g.error.apply(h,arguments)}:a.noop,g.ajaxOptions&&a.extend(j,g.ajaxOptions),g.data&&a.extend(j.data,g.data),a.ajax(j)}else"function"==typeof g.error&&g.error.call(h,i);return this}return this.each(function(){var d=a(this),g=d.data(f),h="object"==typeof c&&c;return h&&h.selector?(g=new b(this,h),void 0):(g||d.data(f,g=new b(this,h)),"string"==typeof c&&g[c].apply(g,Array.prototype.slice.call(e,1)),void 0)})},a.fn.editable.defaults={type:"text",disabled:!1,toggle:"click",emptytext:"Empty",autotext:"auto",value:null,display:null,emptyclass:"editable-empty",unsavedclass:"editable-unsaved",selector:null,highlight:"#FFFF80"}}(window.jQuery),function(a){"use strict";a.fn.editabletypes={};var b=function(){};b.prototype={init:function(b,c,d){this.type=b,this.options=a.extend({},d,c)},prerender:function(){this.$tpl=a(this.options.tpl),this.$input=this.$tpl,this.$clear=null,this.error=null},render:function(){},value2html:function(b,c){a(c)[this.options.escape?"text":"html"](a.trim(b))},html2value:function(b){return a("<div>").html(b).text()},value2str:function(a){return a},str2value:function(a){return a},value2submit:function(a){return a},value2input:function(a){this.$input.val(a)},input2value:function(){return this.$input.val()},activate:function(){this.$input.is(":visible")&&this.$input.focus()},clear:function(){this.$input.val(null)},escape:function(b){return a("<div>").text(b).html()},autosubmit:function(){},destroy:function(){},setClass:function(){this.options.inputclass&&this.$input.addClass(this.options.inputclass)},setAttr:function(a){void 0!==this.options[a]&&null!==this.options[a]&&this.$input.attr(a,this.options[a])},option:function(a,b){this.options[a]=b}},b.defaults={tpl:"",inputclass:null,escape:!0,scope:null,showbuttons:!0},a.extend(a.fn.editabletypes,{abstractinput:b})}(window.jQuery),function(a){"use strict";var b=function(){};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){var b=a.Deferred();return this.error=null,this.onSourceReady(function(){this.renderList(),b.resolve()},function(){this.error=this.options.sourceError,b.resolve()}),b.promise()},html2value:function(){return null},value2html:function(b,c,d,e){var f=a.Deferred(),g=function(){"function"==typeof d?d.call(c,b,this.sourceData,e):this.value2htmlFinal(b,c),f.resolve()};return null===b?g.call(this):this.onSourceReady(g,function(){f.resolve()}),f.promise()},onSourceReady:function(b,c){var d;if(a.isFunction(this.options.source)?(d=this.options.source.call(this.options.scope),this.sourceData=null):d=this.options.source,this.options.sourceCache&&a.isArray(this.sourceData))return b.call(this),void 0;try{d=a.fn.editableutils.tryParseJson(d,!1)}catch(e){return c.call(this),void 0}if("string"==typeof d){if(this.options.sourceCache){var f,g=d;if(a(document).data(g)||a(document).data(g,{}),f=a(document).data(g),f.loading===!1&&f.sourceData)return this.sourceData=f.sourceData,this.doPrepend(),b.call(this),void 0;if(f.loading===!0)return f.callbacks.push(a.proxy(function(){this.sourceData=f.sourceData,this.doPrepend(),b.call(this)},this)),f.err_callbacks.push(a.proxy(c,this)),void 0;f.loading=!0,f.callbacks=[],f.err_callbacks=[]}var h=a.extend({url:d,type:"get",cache:!1,dataType:"json",success:a.proxy(function(d){f&&(f.loading=!1),this.sourceData=this.makeArray(d),a.isArray(this.sourceData)?(f&&(f.sourceData=this.sourceData,a.each(f.callbacks,function(){this.call()})),this.doPrepend(),b.call(this)):(c.call(this),f&&a.each(f.err_callbacks,function(){this.call()}))},this),error:a.proxy(function(){c.call(this),f&&(f.loading=!1,a.each(f.err_callbacks,function(){this.call()}))},this)},this.options.sourceOptions);a.ajax(h)}else this.sourceData=this.makeArray(d),a.isArray(this.sourceData)?(this.doPrepend(),b.call(this)):c.call(this)},doPrepend:function(){null!==this.options.prepend&&void 0!==this.options.prepend&&(a.isArray(this.prependData)||(a.isFunction(this.options.prepend)&&(this.options.prepend=this.options.prepend.call(this.options.scope)),this.options.prepend=a.fn.editableutils.tryParseJson(this.options.prepend,!0),"string"==typeof this.options.prepend&&(this.options.prepend={"":this.options.prepend}),this.prependData=this.makeArray(this.options.prepend)),a.isArray(this.prependData)&&a.isArray(this.sourceData)&&(this.sourceData=this.prependData.concat(this.sourceData)))},renderList:function(){},value2htmlFinal:function(){},makeArray:function(b){var c,d,e,f,g=[];if(!b||"string"==typeof b)return null;if(a.isArray(b)){f=function(a,b){return d={value:a,text:b},c++>=2?!1:void 0};for(var h=0;h<b.length;h++)e=b[h],"object"==typeof e?(c=0,a.each(e,f),1===c?g.push(d):c>1&&(e.children&&(e.children=this.makeArray(e.children)),g.push(e))):g.push({value:e,text:e})}else a.each(b,function(a,b){g.push({value:a,text:b})});return g},option:function(a,b){this.options[a]=b,"source"===a&&(this.sourceData=null),"prepend"===a&&(this.prependData=null)}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{source:null,prepend:!1,sourceError:"Error when loading list",sourceCache:!0,sourceOptions:null}),a.fn.editabletypes.list=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("text",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){this.renderClear(),this.setClass(),this.setAttr("placeholder")},activate:function(){this.$input.is(":visible")&&(this.$input.focus(),a.fn.editableutils.setCursorPosition(this.$input.get(0),this.$input.val().length),this.toggleClear&&this.toggleClear())},renderClear:function(){this.options.clear&&(this.$clear=a('<span class="editable-clear-x"></span>'),this.$input.after(this.$clear).css("padding-right",24).keyup(a.proxy(function(b){if(!~a.inArray(b.keyCode,[40,38,9,13,27])){clearTimeout(this.t);var c=this;this.t=setTimeout(function(){c.toggleClear(b)},100)}},this)).parent().css("position","relative"),this.$clear.click(a.proxy(this.clear,this)))},postrender:function(){},toggleClear:function(){if(this.$clear){var a=this.$input.val().length,b=this.$clear.is(":visible");a&&!b&&this.$clear.show(),!a&&b&&this.$clear.hide()}},clear:function(){this.$clear.hide(),this.$input.val("").focus()}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:'<input type="text">',placeholder:null,clear:!0}),a.fn.editabletypes.text=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("textarea",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){this.setClass(),this.setAttr("placeholder"),this.setAttr("rows"),this.$input.keydown(function(b){b.ctrlKey&&13===b.which&&a(this).closest("form").submit()})},activate:function(){a.fn.editabletypes.text.prototype.activate.call(this)}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:"<textarea></textarea>",inputclass:"input-large",placeholder:null,rows:7}),a.fn.editabletypes.textarea=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("select",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.list),a.extend(b.prototype,{renderList:function(){this.$input.empty();var b=function(c,d){var e;if(a.isArray(d))for(var f=0;f<d.length;f++)e={},d[f].children?(e.label=d[f].text,c.append(b(a("<optgroup>",e),d[f].children))):(e.value=d[f].value,d[f].disabled&&(e.disabled=!0),c.append(a("<option>",e).text(d[f].text)));return c};b(this.$input,this.sourceData),this.setClass(),this.$input.on("keydown.editable",function(b){13===b.which&&a(this).closest("form").submit()})},value2htmlFinal:function(b,c){var d="",e=a.fn.editableutils.itemsByValue(b,this.sourceData);e.length&&(d=e[0].text),a.fn.editabletypes.abstractinput.prototype.value2html.call(this,d,c)},autosubmit:function(){this.$input.off("keydown.editable").on("change.editable",function(){a(this).closest("form").submit()})}}),b.defaults=a.extend({},a.fn.editabletypes.list.defaults,{tpl:"<select></select>"}),a.fn.editabletypes.select=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("checklist",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.list),a.extend(b.prototype,{renderList:function(){var b;if(this.$tpl.empty(),a.isArray(this.sourceData)){for(var c=0;c<this.sourceData.length;c++)b=a("<label>").append(a("<input>",{type:"checkbox",value:this.sourceData[c].value})).append(a("<span>").text(" "+this.sourceData[c].text)),a("<div>").append(b).appendTo(this.$tpl);this.$input=this.$tpl.find('input[type="checkbox"]'),this.setClass()}},value2str:function(b){return a.isArray(b)?b.sort().join(a.trim(this.options.separator)):""},str2value:function(b){var c,d=null;return"string"==typeof b&&b.length?(c=new RegExp("\\s*"+a.trim(this.options.separator)+"\\s*"),d=b.split(c)):d=a.isArray(b)?b:[b],d},value2input:function(b){this.$input.prop("checked",!1),a.isArray(b)&&b.length&&this.$input.each(function(c,d){var e=a(d);a.each(b,function(a,b){e.val()==b&&e.prop("checked",!0)})})},input2value:function(){var b=[];return this.$input.filter(":checked").each(function(c,d){b.push(a(d).val())}),b},value2htmlFinal:function(b,c){var d=[],e=a.fn.editableutils.itemsByValue(b,this.sourceData),f=this.options.escape;e.length?(a.each(e,function(b,c){var e=f?a.fn.editableutils.escape(c.text):c.text;d.push(e)}),a(c).html(d.join("<br>"))):a(c).empty()},activate:function(){this.$input.first().focus()},autosubmit:function(){this.$input.on("keydown",function(b){13===b.which&&a(this).closest("form").submit()})}}),b.defaults=a.extend({},a.fn.editabletypes.list.defaults,{tpl:'<div class="editable-checklist"></div>',inputclass:null,separator:","}),a.fn.editabletypes.checklist=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("password",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.text),a.extend(b.prototype,{value2html:function(b,c){b?a(c).text("[hidden]"):a(c).empty()},html2value:function(){return null}}),b.defaults=a.extend({},a.fn.editabletypes.text.defaults,{tpl:'<input type="password">'}),a.fn.editabletypes.password=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("email",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.text),b.defaults=a.extend({},a.fn.editabletypes.text.defaults,{tpl:'<input type="email">'}),a.fn.editabletypes.email=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("url",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.text),b.defaults=a.extend({},a.fn.editabletypes.text.defaults,{tpl:'<input type="url">'}),a.fn.editabletypes.url=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("tel",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.text),b.defaults=a.extend({},a.fn.editabletypes.text.defaults,{tpl:'<input type="tel">'}),a.fn.editabletypes.tel=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("number",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.text),a.extend(b.prototype,{render:function(){b.superclass.render.call(this),this.setAttr("min"),this.setAttr("max"),this.setAttr("step")},postrender:function(){this.$clear&&this.$clear.css({right:24})}}),b.defaults=a.extend({},a.fn.editabletypes.text.defaults,{tpl:'<input type="number">',inputclass:"input-mini",min:null,max:null,step:null}),a.fn.editabletypes.number=b}(window.jQuery),function(a){"use strict";
6
+ var b=function(a){this.init("range",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.number),a.extend(b.prototype,{render:function(){this.$input=this.$tpl.filter("input"),this.setClass(),this.setAttr("min"),this.setAttr("max"),this.setAttr("step"),this.$input.on("input",function(){a(this).siblings("output").text(a(this).val())})},activate:function(){this.$input.focus()}}),b.defaults=a.extend({},a.fn.editabletypes.number.defaults,{tpl:'<input type="range"><output style="width: 30px; display: inline-block"></output>',inputclass:"input-medium"}),a.fn.editabletypes.range=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("time",a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){this.setClass()}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:'<input type="time">'}),a.fn.editabletypes.time=b}(window.jQuery),function(a){"use strict";var b=function(c){if(this.init("select2",c,b.defaults),c.select2=c.select2||{},this.sourceData=null,c.placeholder&&(c.select2.placeholder=c.placeholder),!c.select2.tags&&c.source){var d=c.source;a.isFunction(c.source)&&(d=c.source.call(c.scope)),"string"==typeof d?(c.select2.ajax=c.select2.ajax||{},c.select2.ajax.data||(c.select2.ajax.data=function(a){return{query:a}}),c.select2.ajax.results||(c.select2.ajax.results=function(a){return{results:a}}),c.select2.ajax.url=d):(this.sourceData=this.convertSource(d),c.select2.data=this.sourceData)}if(this.options.select2=a.extend({},b.defaults.select2,c.select2),this.isMultiple=this.options.select2.tags||this.options.select2.multiple,this.isRemote="ajax"in this.options.select2,this.idFunc=this.options.select2.id,"function"!=typeof this.idFunc){var e=this.idFunc||"id";this.idFunc=function(a){return a[e]}}this.formatSelection=this.options.select2.formatSelection,"function"!=typeof this.formatSelection&&(this.formatSelection=function(a){return a.text})};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){this.setClass(),this.isRemote&&this.$input.on("select2-loaded",a.proxy(function(a){this.sourceData=a.items.results},this)),this.isMultiple&&this.$input.on("change",function(){a(this).closest("form").parent().triggerHandler("resize")})},value2html:function(c,d){var e,f="",g=this;this.options.select2.tags?e=c:this.sourceData&&(e=a.fn.editableutils.itemsByValue(c,this.sourceData,this.idFunc)),a.isArray(e)?(f=[],a.each(e,function(a,b){f.push(b&&"object"==typeof b?g.formatSelection(b):b)})):e&&(f=g.formatSelection(e)),f=a.isArray(f)?f.join(this.options.viewseparator):f,b.superclass.value2html.call(this,f,d)},html2value:function(a){return this.options.select2.tags?this.str2value(a,this.options.viewseparator):null},value2input:function(b){if(a.isArray(b)&&(b=b.join(this.getSeparator())),this.$input.data("select2")?this.$input.val(b).trigger("change",!0):(this.$input.val(b),this.$input.select2(this.options.select2)),this.isRemote&&!this.isMultiple&&!this.options.select2.initSelection){var c=this.options.select2.id,d=this.options.select2.formatSelection;if(!c&&!d){var e=a(this.options.scope);if(!e.data("editable").isEmpty){var f={id:b,text:e.text()};this.$input.select2("data",f)}}}},input2value:function(){return this.$input.select2("val")},str2value:function(b,c){if("string"!=typeof b||!this.isMultiple)return b;c=c||this.getSeparator();var d,e,f;if(null===b||b.length<1)return null;for(d=b.split(c),e=0,f=d.length;f>e;e+=1)d[e]=a.trim(d[e]);return d},autosubmit:function(){this.$input.on("change",function(b,c){c||a(this).closest("form").submit()})},getSeparator:function(){return this.options.select2.separator||a.fn.select2.defaults.separator},convertSource:function(b){if(a.isArray(b)&&b.length&&void 0!==b[0].value)for(var c=0;c<b.length;c++)void 0!==b[c].value&&(b[c].id=b[c].value,delete b[c].value);return b},destroy:function(){this.$input.data("select2")&&this.$input.select2("destroy")}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:'<input type="hidden">',select2:null,placeholder:null,source:null,viewseparator:", "}),a.fn.editabletypes.select2=b}(window.jQuery),function(a){var b=function(b,c){return this.$element=a(b),this.$element.is("input")?(this.options=a.extend({},a.fn.combodate.defaults,c,this.$element.data()),this.init(),void 0):(a.error("Combodate should be applied to INPUT element"),void 0)};b.prototype={constructor:b,init:function(){this.map={day:["D","date"],month:["M","month"],year:["Y","year"],hour:["[Hh]","hours"],minute:["m","minutes"],second:["s","seconds"],ampm:["[Aa]",""]},this.$widget=a('<span class="combodate"></span>').html(this.getTemplate()),this.initCombos(),this.$widget.on("change","select",a.proxy(function(b){this.$element.val(this.getValue()).change(),this.options.smartDays&&(a(b.target).is(".month")||a(b.target).is(".year"))&&this.fillCombo("day")},this)),this.$widget.find("select").css("width","auto"),this.$element.hide().after(this.$widget),this.setValue(this.$element.val()||this.options.value)},getTemplate:function(){var b=this.options.template;return a.each(this.map,function(a,c){c=c[0];var d=new RegExp(c+"+"),e=c.length>1?c.substring(1,2):c;b=b.replace(d,"{"+e+"}")}),b=b.replace(/ /g,"&nbsp;"),a.each(this.map,function(a,c){c=c[0];var d=c.length>1?c.substring(1,2):c;b=b.replace("{"+d+"}",'<select class="'+a+'"></select>')}),b},initCombos:function(){for(var a in this.map){var b=this.$widget.find("."+a);this["$"+a]=b.length?b:null,this.fillCombo(a)}},fillCombo:function(a){var b=this["$"+a];if(b){var c="fill"+a.charAt(0).toUpperCase()+a.slice(1),d=this[c](),e=b.val();b.empty();for(var f=0;f<d.length;f++)b.append('<option value="'+d[f][0]+'">'+d[f][1]+"</option>");b.val(e)}},fillCommon:function(a){var b,c=[];if("name"===this.options.firstItem){b=moment.relativeTime||moment.langData()._relativeTime;var d="function"==typeof b[a]?b[a](1,!0,a,!1):b[a];d=d.split(" ").reverse()[0],c.push(["",d])}else"empty"===this.options.firstItem&&c.push(["",""]);return c},fillDay:function(){var a,b,c=this.fillCommon("d"),d=-1!==this.options.template.indexOf("DD"),e=31;if(this.options.smartDays&&this.$month&&this.$year){var f=parseInt(this.$month.val(),10),g=parseInt(this.$year.val(),10);isNaN(f)||isNaN(g)||(e=moment([g,f]).daysInMonth())}for(b=1;e>=b;b++)a=d?this.leadZero(b):b,c.push([b,a]);return c},fillMonth:function(){var a,b,c=this.fillCommon("M"),d=-1!==this.options.template.indexOf("MMMM"),e=-1!==this.options.template.indexOf("MMM"),f=-1!==this.options.template.indexOf("MM");for(b=0;11>=b;b++)a=d?moment().date(1).month(b).format("MMMM"):e?moment().date(1).month(b).format("MMM"):f?this.leadZero(b+1):b+1,c.push([b,a]);return c},fillYear:function(){var a,b,c=[],d=-1!==this.options.template.indexOf("YYYY");for(b=this.options.maxYear;b>=this.options.minYear;b--)a=d?b:(b+"").substring(2),c[this.options.yearDescending?"push":"unshift"]([b,a]);return c=this.fillCommon("y").concat(c)},fillHour:function(){var a,b,c=this.fillCommon("h"),d=-1!==this.options.template.indexOf("h"),e=(-1!==this.options.template.indexOf("H"),-1!==this.options.template.toLowerCase().indexOf("hh")),f=d?1:0,g=d?12:23;for(b=f;g>=b;b++)a=e?this.leadZero(b):b,c.push([b,a]);return c},fillMinute:function(){var a,b,c=this.fillCommon("m"),d=-1!==this.options.template.indexOf("mm");for(b=0;59>=b;b+=this.options.minuteStep)a=d?this.leadZero(b):b,c.push([b,a]);return c},fillSecond:function(){var a,b,c=this.fillCommon("s"),d=-1!==this.options.template.indexOf("ss");for(b=0;59>=b;b+=this.options.secondStep)a=d?this.leadZero(b):b,c.push([b,a]);return c},fillAmpm:function(){var a=-1!==this.options.template.indexOf("a"),b=(-1!==this.options.template.indexOf("A"),[["am",a?"am":"AM"],["pm",a?"pm":"PM"]]);return b},getValue:function(b){var c,d={},e=this,f=!1;return a.each(this.map,function(a){if("ampm"!==a){var b="day"===a?1:0;return d[a]=e["$"+a]?parseInt(e["$"+a].val(),10):b,isNaN(d[a])?(f=!0,!1):void 0}}),f?"":(this.$ampm&&(d.hour=12===d.hour?"am"===this.$ampm.val()?0:12:"am"===this.$ampm.val()?d.hour:d.hour+12),c=moment([d.year,d.month,d.day,d.hour,d.minute,d.second]),this.highlight(c),b=void 0===b?this.options.format:b,null===b?c.isValid()?c:null:c.isValid()?c.format(b):"")},setValue:function(b){function c(b,c){var d={};return b.children("option").each(function(b,e){var f,g=a(e).attr("value");""!==g&&(f=Math.abs(g-c),("undefined"==typeof d.distance||f<d.distance)&&(d={value:g,distance:f}))}),d.value}if(b){var d="string"==typeof b?moment(b,this.options.format):moment(b),e=this,f={};d.isValid()&&(a.each(this.map,function(a,b){"ampm"!==a&&(f[a]=d[b[1]]())}),this.$ampm&&(f.hour>=12?(f.ampm="pm",f.hour>12&&(f.hour-=12)):(f.ampm="am",0===f.hour&&(f.hour=12))),a.each(f,function(a,b){e["$"+a]&&("minute"===a&&e.options.minuteStep>1&&e.options.roundTime&&(b=c(e["$"+a],b)),"second"===a&&e.options.secondStep>1&&e.options.roundTime&&(b=c(e["$"+a],b)),e["$"+a].val(b))}),this.options.smartDays&&this.fillCombo("day"),this.$element.val(d.format(this.options.format)).change())}},highlight:function(a){a.isValid()?this.options.errorClass?this.$widget.removeClass(this.options.errorClass):this.$widget.find("select").css("border-color",this.borderColor):this.options.errorClass?this.$widget.addClass(this.options.errorClass):(this.borderColor||(this.borderColor=this.$widget.find("select").css("border-color")),this.$widget.find("select").css("border-color","red"))},leadZero:function(a){return 9>=a?"0"+a:a},destroy:function(){this.$widget.remove(),this.$element.removeData("combodate").show()}},a.fn.combodate=function(c){var d,e=Array.apply(null,arguments);return e.shift(),"getValue"===c&&this.length&&(d=this.eq(0).data("combodate"))?d.getValue.apply(d,e):this.each(function(){var d=a(this),f=d.data("combodate"),g="object"==typeof c&&c;f||d.data("combodate",f=new b(this,g)),"string"==typeof c&&"function"==typeof f[c]&&f[c].apply(f,e)})},a.fn.combodate.defaults={format:"DD-MM-YYYY HH:mm",template:"D / MMM / YYYY H : mm",value:null,minYear:1970,maxYear:2015,yearDescending:!0,minuteStep:5,secondStep:1,firstItem:"empty",errorClass:null,roundTime:!0,smartDays:!1}}(window.jQuery),function(a){"use strict";var b=function(c){this.init("combodate",c,b.defaults),this.options.viewformat||(this.options.viewformat=this.options.format),c.combodate=a.fn.editableutils.tryParseJson(c.combodate,!0),this.options.combodate=a.extend({},b.defaults.combodate,c.combodate,{format:this.options.format,template:this.options.template})};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{render:function(){this.$input.combodate(this.options.combodate),"bs3"===a.fn.editableform.engine&&this.$input.siblings().find("select").addClass("form-control"),this.options.inputclass&&this.$input.siblings().find("select").addClass(this.options.inputclass)},value2html:function(a,c){var d=a?a.format(this.options.viewformat):"";b.superclass.value2html.call(this,d,c)},html2value:function(a){return a?moment(a,this.options.viewformat):null},value2str:function(a){return a?a.format(this.options.format):""},str2value:function(a){return a?moment(a,this.options.format):null},value2submit:function(a){return this.value2str(a)},value2input:function(a){this.$input.combodate("setValue",a)},input2value:function(){return this.$input.combodate("getValue",null)},activate:function(){this.$input.siblings(".combodate").find("select").eq(0).focus()},autosubmit:function(){}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:'<input type="text">',inputclass:null,format:"YYYY-MM-DD",viewformat:null,template:"D / MMM / YYYY",combodate:null}),a.fn.editabletypes.combodate=b}(window.jQuery),function(a){"use strict";var b=a.fn.editableform.Constructor.prototype.initInput;a.extend(a.fn.editableform.Constructor.prototype,{initTemplate:function(){this.$form=a(a.fn.editableform.template),this.$form.find(".control-group").addClass("form-group"),this.$form.find(".editable-error-block").addClass("help-block")},initInput:function(){b.apply(this);var c=null===this.input.options.inputclass||this.input.options.inputclass===!1,d="input-sm",e="text,select,textarea,password,email,url,tel,number,range,time,typeaheadjs".split(",");~a.inArray(this.input.type,e)&&(this.input.$input.addClass("form-control"),c&&(this.input.options.inputclass=d,this.input.$input.addClass(d)));for(var f=this.$form.find(".editable-buttons"),g=c?[d]:this.input.options.inputclass.split(" "),h=0;h<g.length;h++)"input-lg"===g[h].toLowerCase()&&f.find("button").removeClass("btn-sm").addClass("btn-lg")}}),a.fn.editableform.buttons='<button type="submit" class="btn btn-primary btn-sm editable-submit"><i class="glyphicon glyphicon-ok"></i></button><button type="button" class="btn btn-default btn-sm editable-cancel"><i class="glyphicon glyphicon-remove"></i></button>',a.fn.editableform.errorGroupClass="has-error",a.fn.editableform.errorBlockClass=null,a.fn.editableform.engine="bs3"}(window.jQuery),function(a){"use strict";a.extend(a.fn.editableContainer.Popup.prototype,{containerName:"popover",containerDataName:"bs.popover",innerCss:".popover-content",defaults:a.fn.popover.Constructor.DEFAULTS,initContainer:function(){a.extend(this.containerOptions,{trigger:"manual",selector:!1,content:" ",template:this.defaults.template});var b;this.$element.data("template")&&(b=this.$element.data("template"),this.$element.removeData("template")),this.call(this.containerOptions),b&&this.$element.data("template",b)},innerShow:function(){this.call("show")},innerHide:function(){this.call("hide")},innerDestroy:function(){this.call("destroy")},setContainerOption:function(a,b){this.container().options[a]=b},setPosition:function(){!function(){var a=this.tip(),b="function"==typeof this.options.placement?this.options.placement.call(this,a[0],this.$element[0]):this.options.placement,c=/\s?auto?\s?/i,d=c.test(b);d&&(b=b.replace(c,"")||"top");var e=this.getPosition(),f=a[0].offsetWidth,g=a[0].offsetHeight;if(d){var h=this.$element.parent(),i=b,j=document.documentElement.scrollTop||document.body.scrollTop,k="body"==this.options.container?window.innerWidth:h.outerWidth(),l="body"==this.options.container?window.innerHeight:h.outerHeight(),m="body"==this.options.container?0:h.offset().left;b="bottom"==b&&e.top+e.height+g-j>l?"top":"top"==b&&e.top-j-g<0?"bottom":"right"==b&&e.right+f>k?"left":"left"==b&&e.left-f<m?"right":b,a.removeClass(i).addClass(b)}var n=this.getCalculatedOffset(b,e,f,g);this.applyPlacement(n,b)}.call(this.container())}})}(window.jQuery),function(a){function b(){return new Date(Date.UTC.apply(Date,arguments))}function c(b,c){var d,e=a(b).data(),f={},g=new RegExp("^"+c.toLowerCase()+"([A-Z])"),c=new RegExp("^"+c.toLowerCase());for(var h in e)c.test(h)&&(d=h.replace(g,function(a,b){return b.toLowerCase()}),f[d]=e[h]);return f}function d(b){var c={};if(k[b]||(b=b.split("-")[0],k[b])){var d=k[b];return a.each(j,function(a,b){b in d&&(c[b]=d[b])}),c}}var e=function(b,c){this._process_options(c),this.element=a(b),this.isInline=!1,this.isInput=this.element.is("input"),this.component=this.element.is(".date")?this.element.find(".add-on, .btn"):!1,this.hasInput=this.component&&this.element.find("input").length,this.component&&0===this.component.length&&(this.component=!1),this.picker=a(l.template),this._buildEvents(),this._attachEvents(),this.isInline?this.picker.addClass("datepicker-inline").appendTo(this.element):this.picker.addClass("datepicker-dropdown dropdown-menu"),this.o.rtl&&(this.picker.addClass("datepicker-rtl"),this.picker.find(".prev i, .next i").toggleClass("icon-arrow-left icon-arrow-right")),this.viewMode=this.o.startView,this.o.calendarWeeks&&this.picker.find("tfoot th.today").attr("colspan",function(a,b){return parseInt(b)+1}),this._allow_update=!1,this.setStartDate(this.o.startDate),this.setEndDate(this.o.endDate),this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled),this.fillDow(),this.fillMonths(),this._allow_update=!0,this.update(),this.showMode(),this.isInline&&this.show()};e.prototype={constructor:e,_process_options:function(b){this._o=a.extend({},this._o,b);var c=this.o=a.extend({},this._o),d=c.language;switch(k[d]||(d=d.split("-")[0],k[d]||(d=i.language)),c.language=d,c.startView){case 2:case"decade":c.startView=2;break;case 1:case"year":c.startView=1;break;default:c.startView=0}switch(c.minViewMode){case 1:case"months":c.minViewMode=1;break;case 2:case"years":c.minViewMode=2;break;default:c.minViewMode=0}c.startView=Math.max(c.startView,c.minViewMode),c.weekStart%=7,c.weekEnd=(c.weekStart+6)%7;var e=l.parseFormat(c.format);c.startDate!==-1/0&&(c.startDate=l.parseDate(c.startDate,e,c.language)),1/0!==c.endDate&&(c.endDate=l.parseDate(c.endDate,e,c.language)),c.daysOfWeekDisabled=c.daysOfWeekDisabled||[],a.isArray(c.daysOfWeekDisabled)||(c.daysOfWeekDisabled=c.daysOfWeekDisabled.split(/[,\s]*/)),c.daysOfWeekDisabled=a.map(c.daysOfWeekDisabled,function(a){return parseInt(a,10)})},_events:[],_secondaryEvents:[],_applyEvents:function(a){for(var b,c,d=0;d<a.length;d++)b=a[d][0],c=a[d][1],b.on(c)},_unapplyEvents:function(a){for(var b,c,d=0;d<a.length;d++)b=a[d][0],c=a[d][1],b.off(c)},_buildEvents:function(){this.isInput?this._events=[[this.element,{focus:a.proxy(this.show,this),keyup:a.proxy(this.update,this),keydown:a.proxy(this.keydown,this)}]]:this.component&&this.hasInput?this._events=[[this.element.find("input"),{focus:a.proxy(this.show,this),keyup:a.proxy(this.update,this),keydown:a.proxy(this.keydown,this)}],[this.component,{click:a.proxy(this.show,this)}]]:this.element.is("div")?this.isInline=!0:this._events=[[this.element,{click:a.proxy(this.show,this)}]],this._secondaryEvents=[[this.picker,{click:a.proxy(this.click,this)}],[a(window),{resize:a.proxy(this.place,this)}],[a(document),{mousedown:a.proxy(function(a){this.element.is(a.target)||this.element.find(a.target).size()||this.picker.is(a.target)||this.picker.find(a.target).size()||this.hide()},this)}]]},_attachEvents:function(){this._detachEvents(),this._applyEvents(this._events)},_detachEvents:function(){this._unapplyEvents(this._events)},_attachSecondaryEvents:function(){this._detachSecondaryEvents(),this._applyEvents(this._secondaryEvents)},_detachSecondaryEvents:function(){this._unapplyEvents(this._secondaryEvents)},_trigger:function(b,c){var d=c||this.date,e=new Date(d.getTime()+6e4*d.getTimezoneOffset());this.element.trigger({type:b,date:e,format:a.proxy(function(a){var b=a||this.o.format;return l.formatDate(d,b,this.o.language)},this)})},show:function(a){this.isInline||this.picker.appendTo("body"),this.picker.show(),this.height=this.component?this.component.outerHeight():this.element.outerHeight(),this.place(),this._attachSecondaryEvents(),a&&a.preventDefault(),this._trigger("show")},hide:function(){this.isInline||this.picker.is(":visible")&&(this.picker.hide().detach(),this._detachSecondaryEvents(),this.viewMode=this.o.startView,this.showMode(),this.o.forceParse&&(this.isInput&&this.element.val()||this.hasInput&&this.element.find("input").val())&&this.setValue(),this._trigger("hide"))},remove:function(){this.hide(),this._detachEvents(),this._detachSecondaryEvents(),this.picker.remove(),delete this.element.data().datepicker,this.isInput||delete this.element.data().date},getDate:function(){var a=this.getUTCDate();return new Date(a.getTime()+6e4*a.getTimezoneOffset())},getUTCDate:function(){return this.date},setDate:function(a){this.setUTCDate(new Date(a.getTime()-6e4*a.getTimezoneOffset()))},setUTCDate:function(a){this.date=a,this.setValue()},setValue:function(){var a=this.getFormattedDate();this.isInput?this.element.val(a):this.component&&this.element.find("input").val(a)},getFormattedDate:function(a){return void 0===a&&(a=this.o.format),l.formatDate(this.date,a,this.o.language)},setStartDate:function(a){this._process_options({startDate:a}),this.update(),this.updateNavArrows()},setEndDate:function(a){this._process_options({endDate:a}),this.update(),this.updateNavArrows()},setDaysOfWeekDisabled:function(a){this._process_options({daysOfWeekDisabled:a}),this.update(),this.updateNavArrows()},place:function(){if(!this.isInline){var b=parseInt(this.element.parents().filter(function(){return"auto"!=a(this).css("z-index")}).first().css("z-index"))+10,c=this.component?this.component.parent().offset():this.element.offset(),d=this.component?this.component.outerHeight(!0):this.element.outerHeight(!0);this.picker.css({top:c.top+d,left:c.left,zIndex:b})}},_allow_update:!0,update:function(){if(this._allow_update){var a,b=!1;arguments&&arguments.length&&("string"==typeof arguments[0]||arguments[0]instanceof Date)?(a=arguments[0],b=!0):(a=this.isInput?this.element.val():this.element.data("date")||this.element.find("input").val(),delete this.element.data().date),this.date=l.parseDate(a,this.o.format,this.o.language),b&&this.setValue(),this.viewDate=this.date<this.o.startDate?new Date(this.o.startDate):this.date>this.o.endDate?new Date(this.o.endDate):new Date(this.date),this.fill()}},fillDow:function(){var a=this.o.weekStart,b="<tr>";if(this.o.calendarWeeks){var c='<th class="cw">&nbsp;</th>';b+=c,this.picker.find(".datepicker-days thead tr:first-child").prepend(c)}for(;a<this.o.weekStart+7;)b+='<th class="dow">'+k[this.o.language].daysMin[a++%7]+"</th>";b+="</tr>",this.picker.find(".datepicker-days thead").append(b)},fillMonths:function(){for(var a="",b=0;12>b;)a+='<span class="month">'+k[this.o.language].monthsShort[b++]+"</span>";this.picker.find(".datepicker-months td").html(a)},setRange:function(b){b&&b.length?this.range=a.map(b,function(a){return a.valueOf()}):delete this.range,this.fill()},getClassNames:function(b){var c=[],d=this.viewDate.getUTCFullYear(),e=this.viewDate.getUTCMonth(),f=this.date.valueOf(),g=new Date;return b.getUTCFullYear()<d||b.getUTCFullYear()==d&&b.getUTCMonth()<e?c.push("old"):(b.getUTCFullYear()>d||b.getUTCFullYear()==d&&b.getUTCMonth()>e)&&c.push("new"),this.o.todayHighlight&&b.getUTCFullYear()==g.getFullYear()&&b.getUTCMonth()==g.getMonth()&&b.getUTCDate()==g.getDate()&&c.push("today"),f&&b.valueOf()==f&&c.push("active"),(b.valueOf()<this.o.startDate||b.valueOf()>this.o.endDate||-1!==a.inArray(b.getUTCDay(),this.o.daysOfWeekDisabled))&&c.push("disabled"),this.range&&(b>this.range[0]&&b<this.range[this.range.length-1]&&c.push("range"),-1!=a.inArray(b.valueOf(),this.range)&&c.push("selected")),c},fill:function(){var c,d=new Date(this.viewDate),e=d.getUTCFullYear(),f=d.getUTCMonth(),g=this.o.startDate!==-1/0?this.o.startDate.getUTCFullYear():-1/0,h=this.o.startDate!==-1/0?this.o.startDate.getUTCMonth():-1/0,i=1/0!==this.o.endDate?this.o.endDate.getUTCFullYear():1/0,j=1/0!==this.o.endDate?this.o.endDate.getUTCMonth():1/0;this.date&&this.date.valueOf(),this.picker.find(".datepicker-days thead th.datepicker-switch").text(k[this.o.language].months[f]+" "+e),this.picker.find("tfoot th.today").text(k[this.o.language].today).toggle(this.o.todayBtn!==!1),this.picker.find("tfoot th.clear").text(k[this.o.language].clear).toggle(this.o.clearBtn!==!1),this.updateNavArrows(),this.fillMonths();var m=b(e,f-1,28,0,0,0,0),n=l.getDaysInMonth(m.getUTCFullYear(),m.getUTCMonth());m.setUTCDate(n),m.setUTCDate(n-(m.getUTCDay()-this.o.weekStart+7)%7);var o=new Date(m);o.setUTCDate(o.getUTCDate()+42),o=o.valueOf();for(var p,q=[];m.valueOf()<o;){if(m.getUTCDay()==this.o.weekStart&&(q.push("<tr>"),this.o.calendarWeeks)){var r=new Date(+m+864e5*((this.o.weekStart-m.getUTCDay()-7)%7)),s=new Date(+r+864e5*((11-r.getUTCDay())%7)),t=new Date(+(t=b(s.getUTCFullYear(),0,1))+864e5*((11-t.getUTCDay())%7)),u=(s-t)/864e5/7+1;q.push('<td class="cw">'+u+"</td>")}p=this.getClassNames(m),p.push("day");var v=this.o.beforeShowDay(m);void 0===v?v={}:"boolean"==typeof v?v={enabled:v}:"string"==typeof v&&(v={classes:v}),v.enabled===!1&&p.push("disabled"),v.classes&&(p=p.concat(v.classes.split(/\s+/))),v.tooltip&&(c=v.tooltip),p=a.unique(p),q.push('<td class="'+p.join(" ")+'"'+(c?' title="'+c+'"':"")+">"+m.getUTCDate()+"</td>"),m.getUTCDay()==this.o.weekEnd&&q.push("</tr>"),m.setUTCDate(m.getUTCDate()+1)}this.picker.find(".datepicker-days tbody").empty().append(q.join(""));var w=this.date&&this.date.getUTCFullYear(),x=this.picker.find(".datepicker-months").find("th:eq(1)").text(e).end().find("span").removeClass("active");w&&w==e&&x.eq(this.date.getUTCMonth()).addClass("active"),(g>e||e>i)&&x.addClass("disabled"),e==g&&x.slice(0,h).addClass("disabled"),e==i&&x.slice(j+1).addClass("disabled"),q="",e=10*parseInt(e/10,10);var y=this.picker.find(".datepicker-years").find("th:eq(1)").text(e+"-"+(e+9)).end().find("td");e-=1;for(var z=-1;11>z;z++)q+='<span class="year'+(-1==z?" old":10==z?" new":"")+(w==e?" active":"")+(g>e||e>i?" disabled":"")+'">'+e+"</span>",e+=1;y.html(q)},updateNavArrows:function(){if(this._allow_update){var a=new Date(this.viewDate),b=a.getUTCFullYear(),c=a.getUTCMonth();switch(this.viewMode){case 0:this.o.startDate!==-1/0&&b<=this.o.startDate.getUTCFullYear()&&c<=this.o.startDate.getUTCMonth()?this.picker.find(".prev").css({visibility:"hidden"}):this.picker.find(".prev").css({visibility:"visible"}),1/0!==this.o.endDate&&b>=this.o.endDate.getUTCFullYear()&&c>=this.o.endDate.getUTCMonth()?this.picker.find(".next").css({visibility:"hidden"}):this.picker.find(".next").css({visibility:"visible"});break;case 1:case 2:this.o.startDate!==-1/0&&b<=this.o.startDate.getUTCFullYear()?this.picker.find(".prev").css({visibility:"hidden"}):this.picker.find(".prev").css({visibility:"visible"}),1/0!==this.o.endDate&&b>=this.o.endDate.getUTCFullYear()?this.picker.find(".next").css({visibility:"hidden"}):this.picker.find(".next").css({visibility:"visible"})}}},click:function(c){c.preventDefault();var d=a(c.target).closest("span, td, th");if(1==d.length)switch(d[0].nodeName.toLowerCase()){case"th":switch(d[0].className){case"datepicker-switch":this.showMode(1);break;case"prev":case"next":var e=l.modes[this.viewMode].navStep*("prev"==d[0].className?-1:1);switch(this.viewMode){case 0:this.viewDate=this.moveMonth(this.viewDate,e);break;case 1:case 2:this.viewDate=this.moveYear(this.viewDate,e)}this.fill();break;case"today":var f=new Date;f=b(f.getFullYear(),f.getMonth(),f.getDate(),0,0,0),this.showMode(-2);var g="linked"==this.o.todayBtn?null:"view";this._setDate(f,g);break;case"clear":var h;this.isInput?h=this.element:this.component&&(h=this.element.find("input")),h&&h.val("").change(),this._trigger("changeDate"),this.update(),this.o.autoclose&&this.hide()}break;case"span":if(!d.is(".disabled")){if(this.viewDate.setUTCDate(1),d.is(".month")){var i=1,j=d.parent().find("span").index(d),k=this.viewDate.getUTCFullYear();this.viewDate.setUTCMonth(j),this._trigger("changeMonth",this.viewDate),1===this.o.minViewMode&&this._setDate(b(k,j,i,0,0,0,0))}else{var k=parseInt(d.text(),10)||0,i=1,j=0;this.viewDate.setUTCFullYear(k),this._trigger("changeYear",this.viewDate),2===this.o.minViewMode&&this._setDate(b(k,j,i,0,0,0,0))}this.showMode(-1),this.fill()}break;case"td":if(d.is(".day")&&!d.is(".disabled")){var i=parseInt(d.text(),10)||1,k=this.viewDate.getUTCFullYear(),j=this.viewDate.getUTCMonth();d.is(".old")?0===j?(j=11,k-=1):j-=1:d.is(".new")&&(11==j?(j=0,k+=1):j+=1),this._setDate(b(k,j,i,0,0,0,0))}}},_setDate:function(a,b){b&&"date"!=b||(this.date=new Date(a)),b&&"view"!=b||(this.viewDate=new Date(a)),this.fill(),this.setValue(),this._trigger("changeDate");var c;this.isInput?c=this.element:this.component&&(c=this.element.find("input")),c&&(c.change(),!this.o.autoclose||b&&"date"!=b||this.hide())},moveMonth:function(a,b){if(!b)return a;var c,d,e=new Date(a.valueOf()),f=e.getUTCDate(),g=e.getUTCMonth(),h=Math.abs(b);if(b=b>0?1:-1,1==h)d=-1==b?function(){return e.getUTCMonth()==g}:function(){return e.getUTCMonth()!=c},c=g+b,e.setUTCMonth(c),(0>c||c>11)&&(c=(c+12)%12);else{for(var i=0;h>i;i++)e=this.moveMonth(e,b);c=e.getUTCMonth(),e.setUTCDate(f),d=function(){return c!=e.getUTCMonth()}}for(;d();)e.setUTCDate(--f),e.setUTCMonth(c);return e},moveYear:function(a,b){return this.moveMonth(a,12*b)},dateWithinRange:function(a){return a>=this.o.startDate&&a<=this.o.endDate},keydown:function(a){if(this.picker.is(":not(:visible)"))return 27==a.keyCode&&this.show(),void 0;var b,c,d,e=!1;switch(a.keyCode){case 27:this.hide(),a.preventDefault();break;case 37:case 39:if(!this.o.keyboardNavigation)break;b=37==a.keyCode?-1:1,a.ctrlKey?(c=this.moveYear(this.date,b),d=this.moveYear(this.viewDate,b)):a.shiftKey?(c=this.moveMonth(this.date,b),d=this.moveMonth(this.viewDate,b)):(c=new Date(this.date),c.setUTCDate(this.date.getUTCDate()+b),d=new Date(this.viewDate),d.setUTCDate(this.viewDate.getUTCDate()+b)),this.dateWithinRange(c)&&(this.date=c,this.viewDate=d,this.setValue(),this.update(),a.preventDefault(),e=!0);break;case 38:case 40:if(!this.o.keyboardNavigation)break;b=38==a.keyCode?-1:1,a.ctrlKey?(c=this.moveYear(this.date,b),d=this.moveYear(this.viewDate,b)):a.shiftKey?(c=this.moveMonth(this.date,b),d=this.moveMonth(this.viewDate,b)):(c=new Date(this.date),c.setUTCDate(this.date.getUTCDate()+7*b),d=new Date(this.viewDate),d.setUTCDate(this.viewDate.getUTCDate()+7*b)),this.dateWithinRange(c)&&(this.date=c,this.viewDate=d,this.setValue(),this.update(),a.preventDefault(),e=!0);break;case 13:this.hide(),a.preventDefault();break;case 9:this.hide()}if(e){this._trigger("changeDate");var f;this.isInput?f=this.element:this.component&&(f=this.element.find("input")),f&&f.change()}},showMode:function(a){a&&(this.viewMode=Math.max(this.o.minViewMode,Math.min(2,this.viewMode+a))),this.picker.find(">div").hide().filter(".datepicker-"+l.modes[this.viewMode].clsName).css("display","block"),this.updateNavArrows()}};var f=function(b,c){this.element=a(b),this.inputs=a.map(c.inputs,function(a){return a.jquery?a[0]:a}),delete c.inputs,a(this.inputs).datepicker(c).bind("changeDate",a.proxy(this.dateUpdated,this)),this.pickers=a.map(this.inputs,function(b){return a(b).data("datepicker")}),this.updateDates()};f.prototype={updateDates:function(){this.dates=a.map(this.pickers,function(a){return a.date}),this.updateRanges()},updateRanges:function(){var b=a.map(this.dates,function(a){return a.valueOf()});a.each(this.pickers,function(a,c){c.setRange(b)})},dateUpdated:function(b){var c=a(b.target).data("datepicker"),d=c.getUTCDate(),e=a.inArray(b.target,this.inputs),f=this.inputs.length;if(-1!=e){if(d<this.dates[e])for(;e>=0&&d<this.dates[e];)this.pickers[e--].setUTCDate(d);else if(d>this.dates[e])for(;f>e&&d>this.dates[e];)this.pickers[e++].setUTCDate(d);this.updateDates()}},remove:function(){a.map(this.pickers,function(a){a.remove()}),delete this.element.data().datepicker}};var g=a.fn.datepicker,h=a.fn.datepicker=function(b){var g=Array.apply(null,arguments);g.shift();var h;return this.each(function(){var j=a(this),k=j.data("datepicker"),l="object"==typeof b&&b;if(!k){var m=c(this,"date"),n=a.extend({},i,m,l),o=d(n.language),p=a.extend({},i,o,m,l);if(j.is(".input-daterange")||p.inputs){var q={inputs:p.inputs||j.find("input").toArray()};j.data("datepicker",k=new f(this,a.extend(p,q)))}else j.data("datepicker",k=new e(this,p))}return"string"==typeof b&&"function"==typeof k[b]&&(h=k[b].apply(k,g),void 0!==h)?!1:void 0}),void 0!==h?h:this},i=a.fn.datepicker.defaults={autoclose:!1,beforeShowDay:a.noop,calendarWeeks:!1,clearBtn:!1,daysOfWeekDisabled:[],endDate:1/0,forceParse:!0,format:"mm/dd/yyyy",keyboardNavigation:!0,language:"en",minViewMode:0,rtl:!1,startDate:-1/0,startView:0,todayBtn:!1,todayHighlight:!1,weekStart:0},j=a.fn.datepicker.locale_opts=["format","rtl","weekStart"];a.fn.datepicker.Constructor=e;var k=a.fn.datepicker.dates={en:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat","Sun"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa","Su"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],today:"Today",clear:"Clear"}},l={modes:[{clsName:"days",navFnc:"Month",navStep:1},{clsName:"months",navFnc:"FullYear",navStep:1},{clsName:"years",navFnc:"FullYear",navStep:10}],isLeapYear:function(a){return 0===a%4&&0!==a%100||0===a%400
7
  },getDaysInMonth:function(a,b){return[31,l.isLeapYear(a)?29:28,31,30,31,30,31,31,30,31,30,31][b]},validParts:/dd?|DD?|mm?|MM?|yy(?:yy)?/g,nonpunctuation:/[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,parseFormat:function(a){var b=a.replace(this.validParts,"\0").split("\0"),c=a.match(this.validParts);if(!b||!b.length||!c||0===c.length)throw new Error("Invalid date format.");return{separators:b,parts:c}},parseDate:function(c,d,f){if(c instanceof Date)return c;if("string"==typeof d&&(d=l.parseFormat(d)),/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(c)){var g,h,i=/([\-+]\d+)([dmwy])/,j=c.match(/([\-+]\d+)([dmwy])/g);c=new Date;for(var m=0;m<j.length;m++)switch(g=i.exec(j[m]),h=parseInt(g[1]),g[2]){case"d":c.setUTCDate(c.getUTCDate()+h);break;case"m":c=e.prototype.moveMonth.call(e.prototype,c,h);break;case"w":c.setUTCDate(c.getUTCDate()+7*h);break;case"y":c=e.prototype.moveYear.call(e.prototype,c,h)}return b(c.getUTCFullYear(),c.getUTCMonth(),c.getUTCDate(),0,0,0)}var n,o,g,j=c&&c.match(this.nonpunctuation)||[],c=new Date,p={},q=["yyyy","yy","M","MM","m","mm","d","dd"],r={yyyy:function(a,b){return a.setUTCFullYear(b)},yy:function(a,b){return a.setUTCFullYear(2e3+b)},m:function(a,b){for(b-=1;0>b;)b+=12;for(b%=12,a.setUTCMonth(b);a.getUTCMonth()!=b;)a.setUTCDate(a.getUTCDate()-1);return a},d:function(a,b){return a.setUTCDate(b)}};r.M=r.MM=r.mm=r.m,r.dd=r.d,c=b(c.getFullYear(),c.getMonth(),c.getDate(),0,0,0);var s=d.parts.slice();if(j.length!=s.length&&(s=a(s).filter(function(b,c){return-1!==a.inArray(c,q)}).toArray()),j.length==s.length){for(var m=0,t=s.length;t>m;m++){if(n=parseInt(j[m],10),g=s[m],isNaN(n))switch(g){case"MM":o=a(k[f].months).filter(function(){var a=this.slice(0,j[m].length),b=j[m].slice(0,a.length);return a==b}),n=a.inArray(o[0],k[f].months)+1;break;case"M":o=a(k[f].monthsShort).filter(function(){var a=this.slice(0,j[m].length),b=j[m].slice(0,a.length);return a==b}),n=a.inArray(o[0],k[f].monthsShort)+1}p[g]=n}for(var u,m=0;m<q.length;m++)u=q[m],u in p&&!isNaN(p[u])&&r[u](c,p[u])}return c},formatDate:function(b,c,d){"string"==typeof c&&(c=l.parseFormat(c));var e={d:b.getUTCDate(),D:k[d].daysShort[b.getUTCDay()],DD:k[d].days[b.getUTCDay()],m:b.getUTCMonth()+1,M:k[d].monthsShort[b.getUTCMonth()],MM:k[d].months[b.getUTCMonth()],yy:b.getUTCFullYear().toString().substring(2),yyyy:b.getUTCFullYear()};e.dd=(e.d<10?"0":"")+e.d,e.mm=(e.m<10?"0":"")+e.m;for(var b=[],f=a.extend([],c.separators),g=0,h=c.parts.length;h>=g;g++)f.length&&b.push(f.shift()),b.push(e[c.parts[g]]);return b.join("")},headTemplate:'<thead><tr><th class="prev"><i class="icon-arrow-left"/></th><th colspan="5" class="datepicker-switch"></th><th class="next"><i class="icon-arrow-right"/></th></tr></thead>',contTemplate:'<tbody><tr><td colspan="7"></td></tr></tbody>',footTemplate:'<tfoot><tr><th colspan="7" class="today"></th></tr><tr><th colspan="7" class="clear"></th></tr></tfoot>'};l.template='<div class="datepicker"><div class="datepicker-days"><table class=" table-condensed">'+l.headTemplate+"<tbody></tbody>"+l.footTemplate+"</table>"+"</div>"+'<div class="datepicker-months">'+'<table class="table-condensed">'+l.headTemplate+l.contTemplate+l.footTemplate+"</table>"+"</div>"+'<div class="datepicker-years">'+'<table class="table-condensed">'+l.headTemplate+l.contTemplate+l.footTemplate+"</table>"+"</div>"+"</div>",a.fn.datepicker.DPGlobal=l,a.fn.datepicker.noConflict=function(){return a.fn.datepicker=g,this},a(document).on("focus.datepicker.data-api click.datepicker.data-api",'[data-provide="datepicker"]',function(b){var c=a(this);c.data("datepicker")||(b.preventDefault(),h.call(c,"show"))}),a(function(){h.call(a('[data-provide="datepicker-inline"]'))})}(window.jQuery),function(a){"use strict";a.fn.bdatepicker=a.fn.datepicker.noConflict(),a.fn.datepicker||(a.fn.datepicker=a.fn.bdatepicker);var b=function(a){this.init("date",a,b.defaults),this.initPicker(a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{initPicker:function(b,c){this.options.viewformat||(this.options.viewformat=this.options.format),b.datepicker=a.fn.editableutils.tryParseJson(b.datepicker,!0),this.options.datepicker=a.extend({},c.datepicker,b.datepicker,{format:this.options.viewformat}),this.options.datepicker.language=this.options.datepicker.language||"en",this.dpg=a.fn.bdatepicker.DPGlobal,this.parsedFormat=this.dpg.parseFormat(this.options.format),this.parsedViewFormat=this.dpg.parseFormat(this.options.viewformat)},render:function(){this.$input.bdatepicker(this.options.datepicker),this.options.clear&&(this.$clear=a('<a href="#"></a>').html(this.options.clear).click(a.proxy(function(a){a.preventDefault(),a.stopPropagation(),this.clear()},this)),this.$tpl.parent().append(a('<div class="editable-clear">').append(this.$clear)))},value2html:function(a,c){var d=a?this.dpg.formatDate(a,this.parsedViewFormat,this.options.datepicker.language):"";b.superclass.value2html.call(this,d,c)},html2value:function(a){return this.parseDate(a,this.parsedViewFormat)},value2str:function(a){return a?this.dpg.formatDate(a,this.parsedFormat,this.options.datepicker.language):""},str2value:function(a){return this.parseDate(a,this.parsedFormat)},value2submit:function(a){return this.value2str(a)},value2input:function(a){this.$input.bdatepicker("update",a)},input2value:function(){return this.$input.data("datepicker").date},activate:function(){},clear:function(){this.$input.data("datepicker").date=null,this.$input.find(".active").removeClass("active"),this.options.showbuttons||this.$input.closest("form").submit()},autosubmit:function(){this.$input.on("mouseup",".day",function(b){if(!a(b.currentTarget).is(".old")&&!a(b.currentTarget).is(".new")){var c=a(this).closest("form");setTimeout(function(){c.submit()},200)}})},parseDate:function(a,b){var c,d=null;return a&&(d=this.dpg.parseDate(a,b,this.options.datepicker.language),"string"==typeof a&&(c=this.dpg.formatDate(d,b,this.options.datepicker.language),a!==c&&(d=null))),d}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:'<div class="editable-date well"></div>',inputclass:null,format:"yyyy-mm-dd",viewformat:null,datepicker:{weekStart:0,startView:0,minViewMode:0,autoclose:!1},clear:"&times; clear"}),a.fn.editabletypes.date=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("datefield",a,b.defaults),this.initPicker(a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.date),a.extend(b.prototype,{render:function(){this.$input=this.$tpl.find("input"),this.setClass(),this.setAttr("placeholder"),this.$tpl.bdatepicker(this.options.datepicker),this.$input.off("focus keydown"),this.$input.keyup(a.proxy(function(){this.$tpl.removeData("date"),this.$tpl.bdatepicker("update")},this))},value2input:function(a){this.$input.val(a?this.dpg.formatDate(a,this.parsedViewFormat,this.options.datepicker.language):""),this.$tpl.bdatepicker("update")},input2value:function(){return this.html2value(this.$input.val())},activate:function(){a.fn.editabletypes.text.prototype.activate.call(this)},autosubmit:function(){}}),b.defaults=a.extend({},a.fn.editabletypes.date.defaults,{tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',inputclass:"input-small",datepicker:{weekStart:0,startView:0,minViewMode:0,autoclose:!0}}),a.fn.editabletypes.datefield=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("datetime",a,b.defaults),this.initPicker(a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.abstractinput),a.extend(b.prototype,{initPicker:function(b,c){this.options.viewformat||(this.options.viewformat=this.options.format),b.datetimepicker=a.fn.editableutils.tryParseJson(b.datetimepicker,!0),this.options.datetimepicker=a.extend({},c.datetimepicker,b.datetimepicker,{format:this.options.viewformat}),this.options.datetimepicker.language=this.options.datetimepicker.language||"en",this.dpg=a.fn.datetimepicker.DPGlobal,this.parsedFormat=this.dpg.parseFormat(this.options.format,this.options.formatType),this.parsedViewFormat=this.dpg.parseFormat(this.options.viewformat,this.options.formatType)},render:function(){this.$input.datetimepicker(this.options.datetimepicker),this.$input.on("changeMode",function(){var b=a(this).closest("form").parent();setTimeout(function(){b.triggerHandler("resize")},0)}),this.options.clear&&(this.$clear=a('<a href="#"></a>').html(this.options.clear).click(a.proxy(function(a){a.preventDefault(),a.stopPropagation(),this.clear()},this)),this.$tpl.parent().append(a('<div class="editable-clear">').append(this.$clear)))},value2html:function(a,c){var d=a?this.dpg.formatDate(this.toUTC(a),this.parsedViewFormat,this.options.datetimepicker.language,this.options.formatType):"";return c?(b.superclass.value2html.call(this,d,c),void 0):d},html2value:function(a){var b=this.parseDate(a,this.parsedViewFormat);return b?this.fromUTC(b):null},value2str:function(a){return a?this.dpg.formatDate(this.toUTC(a),this.parsedFormat,this.options.datetimepicker.language,this.options.formatType):""},str2value:function(a){var b=this.parseDate(a,this.parsedFormat);return b?this.fromUTC(b):null},value2submit:function(a){return this.value2str(a)},value2input:function(a){a&&this.$input.data("datetimepicker").setDate(a)},input2value:function(){var a=this.$input.data("datetimepicker");return a.date?a.getDate():null},activate:function(){},clear:function(){this.$input.data("datetimepicker").date=null,this.$input.find(".active").removeClass("active"),this.options.showbuttons||this.$input.closest("form").submit()},autosubmit:function(){this.$input.on("mouseup",".minute",function(){var b=a(this).closest("form");setTimeout(function(){b.submit()},200)})},toUTC:function(a){return a?new Date(a.valueOf()-6e4*a.getTimezoneOffset()):a},fromUTC:function(a){return a?new Date(a.valueOf()+6e4*a.getTimezoneOffset()):a},parseDate:function(a,b){var c,d=null;return a&&(d=this.dpg.parseDate(a,b,this.options.datetimepicker.language,this.options.formatType),"string"==typeof a&&(c=this.dpg.formatDate(d,b,this.options.datetimepicker.language,this.options.formatType),a!==c&&(d=null))),d}}),b.defaults=a.extend({},a.fn.editabletypes.abstractinput.defaults,{tpl:'<div class="editable-date well"></div>',inputclass:null,format:"yyyy-mm-dd hh:ii",formatType:"standard",viewformat:null,datetimepicker:{todayHighlight:!1,autoclose:!1},clear:"&times; clear"}),a.fn.editabletypes.datetime=b}(window.jQuery),function(a){"use strict";var b=function(a){this.init("datetimefield",a,b.defaults),this.initPicker(a,b.defaults)};a.fn.editableutils.inherit(b,a.fn.editabletypes.datetime),a.extend(b.prototype,{render:function(){this.$input=this.$tpl.find("input"),this.setClass(),this.setAttr("placeholder"),this.$tpl.datetimepicker(this.options.datetimepicker),this.$input.off("focus keydown"),this.$input.keyup(a.proxy(function(){this.$tpl.removeData("date"),this.$tpl.datetimepicker("update")},this))},value2input:function(a){this.$input.val(this.value2html(a)),this.$tpl.datetimepicker("update")},input2value:function(){return this.html2value(this.$input.val())},activate:function(){a.fn.editabletypes.text.prototype.activate.call(this)},autosubmit:function(){}}),b.defaults=a.extend({},a.fn.editabletypes.datetime.defaults,{tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',inputclass:"input-medium",datetimepicker:{todayHighlight:!1,autoclose:!0}}),a.fn.editabletypes.datetimefield=b}(window.jQuery);
backend/modules/appearance/templates/_1_service.php CHANGED
@@ -1,166 +1,167 @@
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">
11
- <?php include '_progress_tracker.php' ?>
12
-
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>
27
- <option value="1">Cosmetic Dentistry</option>
28
- <option value="2">Invisalign</option>
29
- <option value="3">Orthodontics</option>
30
- <option value="4">Dentures</option>
31
- </select>
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',
39
- ) ) ?>
40
- <div>
41
- <select class="bookly-select-mobile bookly-js-select-service">
42
- <option value="0" class="bookly-js-option bookly_l10n_option_service"><?php echo esc_html( get_option( 'bookly_l10n_option_service' ) ) ?></option>
43
- <option value="1" class="service-name-duration">Crown and Bridge (<?php echo DateTime::secondsToInterval( 3600 ) ?>)</option>
44
- <option value="-1" class="service-name">Crown and Bridge</option>
45
- <option value="2" class="service-name-duration">Teeth Whitening (<?php echo DateTime::secondsToInterval( 3600 * 2 ) ?>)</option>
46
- <option value="-2" class="service-name">Teeth Whitening</option>
47
- <option value="3" class="service-name-duration">Veneers (<?php echo DateTime::secondsToInterval( 3600 * 12 ) ?>)</option>
48
- <option value="-3" class="service-name">Veneers</option>
49
- <option value="4" class="service-name-duration">Invisalign (invisable braces) (<?php echo DateTime::secondsToInterval( 3600 * 24 ) ?>)</option>
50
- <option value="-4" class="service-name">Invisalign (invisable braces)</option>
51
- <option value="5" class="service-name-duration">Orthodontics (braces) (<?php echo DateTime::secondsToInterval( 3600 * 8 ) ?>)</option>
52
- <option value="-5" class="service-name">Orthodontics (braces)</option>
53
- <option value="6" class="service-name-duration">Wisdom tooth Removal (<?php echo DateTime::secondsToInterval( 3600 * 6 ) ?>)</option>
54
- <option value="-6" class="service-name">Wisdom tooth Removal</option>
55
- <option value="7" class="service-name-duration">Root Canal Treatment (<?php echo DateTime::secondsToInterval( 3600 * 16 ) ?>)</option>
56
- <option value="-7" class="service-name">Root Canal Treatment</option>
57
- <option value="8" class="service-name-duration">Dentures (<?php echo DateTime::secondsToInterval( 3600 * 48 ) ?>)</option>
58
- <option value="-8" class="service-name">Dentures</option>
59
- </select>
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',
67
- ) ) ?>
68
- <div>
69
- <select class="bookly-select-mobile bookly-js-select-employee">
70
- <option value="0" class="bookly-js-option bookly_l10n_option_employee"><?php echo esc_html( get_option( 'bookly_l10n_option_employee' ) ) ?></option>
71
- <option value="1" class="employee-name-price">Nick Knight (<?php echo Price::format( 350 ) ?>)</option>
72
- <option value="-1" class="employee-name">Nick Knight</option>
73
- <option value="2" class="employee-name-price">Jane Howard (<?php echo Price::format( 375 ) ?>)</option>
74
- <option value="-2" class="employee-name">Jane Howard</option>
75
- <option value="3" class="employee-name-price">Ashley Stamp (<?php echo Price::format( 300 ) ?>)</option>
76
- <option value="-3" class="employee-name">Ashley Stamp</option>
77
- <option value="4" class="employee-name-price">Bradley Tannen (<?php echo Price::format( 400 ) ?>)</option>
78
- <option value="-4" class="employee-name">Bradley Tannen</option>
79
- <option value="5" class="employee-name-price">Wayne Turner (<?php echo Price::format( 350 ) ?>)</option>
80
- <option value="-5" class="employee-name">Wayne Turner</option>
81
- <option value="6" class="employee-name-price">Emily Taylor (<?php echo Price::format( 350 ) ?>)</option>
82
- <option value="-6" class="employee-name">Emily Taylor</option>
83
- <option value="7" class="employee-name-price">Hugh Canberg (<?php echo Price::format( 380 ) ?>)</option>
84
- <option value="-7" class="employee-name">Hugh Canberg</option>
85
- <option value="8" class="employee-name-price">Jim Gonzalez (<?php echo Price::format( 390 ) ?>)</option>
86
- <option value="-8" class="employee-name">Jim Gonzalez</option>
87
- <option value="9" class="employee-name-price">Nancy Stinson (<?php echo Price::format( 360 ) ?>)</option>
88
- <option value="-9" class="employee-name">Nancy Stinson</option>
89
- <option value="10" class="employee-name-price">Marry Murphy (<?php echo Price::format( 350 ) ?>)</option>
90
- <option value="-10" class="employee-name">Marry Murphy</option>
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">
105
- <div class="bookly-box">
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>
113
- </div>
114
- </div>
115
- <div class="bookly-week-days bookly-js-week-days bookly-table bookly-left">
116
- <?php foreach ( $wp_locale->weekday_abbrev as $weekday_abbrev ) : ?>
117
- <div>
118
- <div class="bookly-font-bold"><?php echo $weekday_abbrev ?></div>
119
- <label class="active">
120
- <input class="bookly-js-week-day" value="1" checked="checked" type="checkbox">
121
- </label>
122
- </div>
123
- <?php endforeach ?>
124
- </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 ) : ?>
132
- <option><?php echo DateTime::formatTime( $i ) ?></option>
133
- <?php endfor ?>
134
- </select>
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 ) : ?>
142
- <option<?php selected( $i == 64800 ) ?>><?php echo DateTime::formatTime( $i ) ?></option>
143
- <?php endfor ?>
144
- </select>
145
- </div>
146
- </div>
147
- </div>
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>
158
- </div>
159
- </div>
160
- </div>
161
- <div style="display: none">
162
- <?php foreach ( array( 'bookly_l10n_required_service', 'bookly_l10n_required_name', 'bookly_l10n_required_phone', 'bookly_l10n_required_email', 'bookly_l10n_required_employee', 'bookly_l10n_required_location' ) as $validator ) : ?>
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>
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\Backend\Components\Appearance\Editable;
6
+ /** @var WP_Locale $wp_locale */
7
+ global $wp_locale;
8
+ ?>
9
+ <div class="bookly-form">
10
+ <?php include '_progress_tracker.php' ?>
11
+
12
+ <div class="bookly-service-step">
13
+ <div class="bookly-box">
14
+ <span class="bookly-bold bookly-desc">
15
+ <?php Editable::renderText( 'bookly_l10n_info_service_step' ) ?>
16
+ </span>
17
+ </div>
18
+ <div class="bookly-mobile-step-1 bookly-js-mobile-step-1 bookly-box">
19
+ <div class="bookly-js-chain-item bookly-table bookly-box">
20
+ <?php Proxy\Locations::renderLocation() ?>
21
+ <div class="bookly-form-group">
22
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_category', 'bookly_l10n_option_category', ) ) ?>
23
+ <div>
24
+ <select class="bookly-select-mobile bookly-js-select-category">
25
+ <option value="" class="bookly-js-option bookly_l10n_option_category"><?php echo esc_html( get_option( 'bookly_l10n_option_category' ) ) ?></option>
26
+ <option value="1">Cosmetic Dentistry</option>
27
+ <option value="2">Invisalign</option>
28
+ <option value="3">Orthodontics</option>
29
+ <option value="4">Dentures</option>
30
+ </select>
31
+ </div>
32
+ </div>
33
+ <div class="bookly-form-group">
34
+ <?php Editable::renderLabel( array(
35
+ 'bookly_l10n_label_service',
36
+ 'bookly_l10n_option_service',
37
+ 'bookly_l10n_required_service',
38
+ ) ) ?>
39
+ <div>
40
+ <select class="bookly-select-mobile bookly-js-select-service">
41
+ <option value="0" class="bookly-js-option bookly_l10n_option_service"><?php echo esc_html( get_option( 'bookly_l10n_option_service' ) ) ?></option>
42
+ <option value="1" class="service-name-duration">Crown and Bridge (<?php echo DateTime::secondsToInterval( 3600 ) ?>)</option>
43
+ <option value="-1" class="service-name">Crown and Bridge</option>
44
+ <option value="2" class="service-name-duration">Teeth Whitening (<?php echo DateTime::secondsToInterval( 3600 * 2 ) ?>)</option>
45
+ <option value="-2" class="service-name">Teeth Whitening</option>
46
+ <option value="3" class="service-name-duration">Veneers (<?php echo DateTime::secondsToInterval( 3600 * 12 ) ?>)</option>
47
+ <option value="-3" class="service-name">Veneers</option>
48
+ <option value="4" class="service-name-duration">Invisalign (invisable braces) (<?php echo DateTime::secondsToInterval( 3600 * 24 ) ?>)</option>
49
+ <option value="-4" class="service-name">Invisalign (invisable braces)</option>
50
+ <option value="5" class="service-name-duration">Orthodontics (braces) (<?php echo DateTime::secondsToInterval( 3600 * 8 ) ?>)</option>
51
+ <option value="-5" class="service-name">Orthodontics (braces)</option>
52
+ <option value="6" class="service-name-duration">Wisdom tooth Removal (<?php echo DateTime::secondsToInterval( 3600 * 6 ) ?>)</option>
53
+ <option value="-6" class="service-name">Wisdom tooth Removal</option>
54
+ <option value="7" class="service-name-duration">Root Canal Treatment (<?php echo DateTime::secondsToInterval( 3600 * 16 ) ?>)</option>
55
+ <option value="-7" class="service-name">Root Canal Treatment</option>
56
+ <option value="8" class="service-name-duration">Dentures (<?php echo DateTime::secondsToInterval( 3600 * 48 ) ?>)</option>
57
+ <option value="-8" class="service-name">Dentures</option>
58
+ </select>
59
+ </div>
60
+ </div>
61
+ <div class="bookly-form-group">
62
+ <?php Editable::renderLabel( array(
63
+ 'bookly_l10n_label_employee',
64
+ 'bookly_l10n_option_employee',
65
+ 'bookly_l10n_required_employee',
66
+ ) ) ?>
67
+ <div>
68
+ <select class="bookly-select-mobile bookly-js-select-employee">
69
+ <option value="0" class="bookly-js-option bookly_l10n_option_employee"><?php echo esc_html( get_option( 'bookly_l10n_option_employee' ) ) ?></option>
70
+ <option value="1" class="employee-name-price">Nick Knight (<?php echo Price::format( 350 ) ?>)</option>
71
+ <option value="-1" class="employee-name">Nick Knight</option>
72
+ <option value="2" class="employee-name-price">Jane Howard (<?php echo Price::format( 375 ) ?>)</option>
73
+ <option value="-2" class="employee-name">Jane Howard</option>
74
+ <option value="3" class="employee-name-price">Ashley Stamp (<?php echo Price::format( 300 ) ?>)</option>
75
+ <option value="-3" class="employee-name">Ashley Stamp</option>
76
+ <option value="4" class="employee-name-price">Bradley Tannen (<?php echo Price::format( 400 ) ?>)</option>
77
+ <option value="-4" class="employee-name">Bradley Tannen</option>
78
+ <option value="5" class="employee-name-price">Wayne Turner (<?php echo Price::format( 350 ) ?>)</option>
79
+ <option value="-5" class="employee-name">Wayne Turner</option>
80
+ <option value="6" class="employee-name-price">Emily Taylor (<?php echo Price::format( 350 ) ?>)</option>
81
+ <option value="-6" class="employee-name">Emily Taylor</option>
82
+ <option value="7" class="employee-name-price">Hugh Canberg (<?php echo Price::format( 380 ) ?>)</option>
83
+ <option value="-7" class="employee-name">Hugh Canberg</option>
84
+ <option value="8" class="employee-name-price">Jim Gonzalez (<?php echo Price::format( 390 ) ?>)</option>
85
+ <option value="-8" class="employee-name">Jim Gonzalez</option>
86
+ <option value="9" class="employee-name-price">Nancy Stinson (<?php echo Price::format( 360 ) ?>)</option>
87
+ <option value="-9" class="employee-name">Nancy Stinson</option>
88
+ <option value="10" class="employee-name-price">Marry Murphy (<?php echo Price::format( 350 ) ?>)</option>
89
+ <option value="-10" class="employee-name">Marry Murphy</option>
90
+ </select>
91
+ </div>
92
+ </div>
93
+ <?php Proxy\CustomDuration::renderServiceDuration() ?>
94
+ <?php Proxy\GroupBooking::renderNOP() ?>
95
+ <?php Proxy\MultiplyAppointments::renderQuantity() ?>
96
+ <?php Proxy\ChainAppointments::renderChain() ?>
97
+
98
+ </div>
99
+ <div class="bookly-right bookly-mobile-next-step bookly-js-mobile-next-step bookly-btn bookly-none">
100
+ <?php Editable::renderString( array( 'bookly_l10n_step_service_mobile_button_next' ) ) ?>
101
+ </div>
102
+ </div>
103
+ <div class="bookly-mobile-step-2 bookly-js-mobile-step-2">
104
+ <div class="bookly-box">
105
+ <div class="bookly-left">
106
+ <div class="bookly-available-date bookly-js-available-date bookly-left">
107
+ <div class="bookly-form-group">
108
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_select_date', ) ) ?>
109
+ <div>
110
+ <input class="bookly-date-from bookly-js-date-from" style="background-color: #fff;" type="text" data-value="<?php echo date( 'Y-m-d' ) ?>" />
111
+ </div>
112
+ </div>
113
+ </div>
114
+ <div class="bookly-week-days bookly-js-week-days bookly-table bookly-left">
115
+ <?php foreach ( $wp_locale->weekday_abbrev as $weekday_abbrev ) : ?>
116
+ <div>
117
+ <div class="bookly-font-bold"><?php echo $weekday_abbrev ?></div>
118
+ <label class="active">
119
+ <input class="bookly-js-week-day" value="1" checked="checked" type="checkbox">
120
+ </label>
121
+ </div>
122
+ <?php endforeach ?>
123
+ </div>
124
+ </div>
125
+ <div class="bookly-time-range bookly-js-time-range bookly-left">
126
+ <div class="bookly-form-group bookly-time-from bookly-left">
127
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_start_from', ) ) ?>
128
+ <div>
129
+ <select class="bookly-js-select-time-from">
130
+ <?php for ( $i = 28800; $i <= 64800; $i += 3600 ) : ?>
131
+ <option><?php echo DateTime::formatTime( $i ) ?></option>
132
+ <?php endfor ?>
133
+ </select>
134
+ </div>
135
+ </div>
136
+ <div class="bookly-form-group bookly-time-to bookly-left">
137
+ <?php Editable::renderLabel( array( 'bookly_l10n_label_finish_by', ) ) ?>
138
+ <div>
139
+ <select class="bookly-js-select-time-to">
140
+ <?php for ( $i = 28800; $i <= 64800; $i += 3600 ) : ?>
141
+ <option<?php selected( $i == 64800 ) ?>><?php echo DateTime::formatTime( $i ) ?></option>
142
+ <?php endfor ?>
143
+ </select>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ <div class="bookly-box bookly-nav-steps">
149
+ <div class="bookly-right bookly-mobile-prev-step bookly-js-mobile-prev-step bookly-btn bookly-none">
150
+ <?php Editable::renderString( array( 'bookly_l10n_button_back' ) ) ?>
151
+ </div>
152
+ <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>
153
+ <div class="<?php echo get_option( 'bookly_app_align_buttons_left' ) ? 'bookly-left' : 'bookly-right' ?>">
154
+ <div class="bookly-next-step bookly-js-next-step bookly-btn">
155
+ <?php Editable::renderString( array( 'bookly_l10n_step_service_button_next' ) ) ?>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ <div style="display: none">
163
+ <?php foreach ( array( 'bookly_l10n_required_service', 'bookly_l10n_required_name', 'bookly_l10n_required_phone', 'bookly_l10n_required_email', 'bookly_l10n_required_employee', 'bookly_l10n_required_location' ) as $validator ) : ?>
164
+ <div class="bookly-js-option <?php echo $validator ?>"><?php echo get_option( $validator ) ?></div>
165
+ <?php endforeach ?>
166
+ </div>
167
  <style id="bookly-pickadate-style"></style>
backend/modules/appearance/templates/_3_time.php CHANGED
@@ -1,204 +1,207 @@
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>
195
- </button>
196
- <button class="bookly-time-prev bookly-btn bookly-right ladda-button">
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>
204
- </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
+ <div class="bookly-back-step bookly-js-back-step bookly-btn">
194
+ <?php Editable::renderString( array( 'bookly_l10n_button_back' ) ) ?>
195
+ </div>
196
+ <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>
197
+ <div class="<?php echo get_option( 'bookly_app_align_buttons_left' ) ? 'bookly-left' : 'bookly-right' ?>">
198
+ <button class="bookly-time-next bookly-btn bookly-right ladda-button">
199
+ <span class="bookly-label">&gt;</span>
200
+ </button>
201
+ <button class="bookly-time-prev bookly-btn bookly-right ladda-button">
202
+ <span class="bookly-label">&lt;</span>
203
+ </button>
204
+ <?php Proxy\Tasks::renderSkipButton() ?>
205
+ </div>
206
+ </div>
207
+ </div>
backend/modules/appearance/templates/_6_details.php CHANGED
@@ -1,89 +1,112 @@
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::renderInfoMe