WordPress Online Booking and Scheduling Plugin – Bookly - Version 16.4

Version Description

Download this release

Release Info

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

Code changes from version 16.3 to 16.4

backend/modules/notifications/lib/Codes.php CHANGED
@@ -68,6 +68,7 @@ class Codes
68
  ),
69
  'payment' => array(
70
  'payment_type' => __( 'payment type', 'bookly' ),
 
71
  'total_price' => __( 'total price of booking (sum of all cart items after applying coupon)' ),
72
  ),
73
  'service' => array(
68
  ),
69
  'payment' => array(
70
  'payment_type' => __( 'payment type', 'bookly' ),
71
+ 'payment_status' => __( 'payment status', 'bookly' ),
72
  'total_price' => __( 'total price of booking (sum of all cart items after applying coupon)' ),
73
  ),
74
  'service' => array(
backend/modules/staff/Ajax.php CHANGED
@@ -45,16 +45,18 @@ class Ajax extends Lib\Base\Ajax
45
  public static function updateStaffPosition()
46
  {
47
  $data = self::parameter( 'data' );
48
- foreach ( $data['staff'] as $position => $staff_data ) {
49
- $staff = Lib\Entities\Staff::find( $staff_data['staff_id'] );
50
- $staff
51
- ->setPosition( $position )
52
- ->setCategoryId( $staff_data['category_id'] !== '' ? $staff_data['category_id'] : null )
53
- ->save();
 
 
 
 
 
54
  }
55
-
56
- Proxy\Pro::updateCategoriesPositions( $data['categories'] );
57
-
58
  wp_send_json_success();
59
  }
60
 
45
  public static function updateStaffPosition()
46
  {
47
  $data = self::parameter( 'data' );
48
+ if ( isset( $data['staff'] ) ) {
49
+ foreach ( $data['staff'] as $position => $staff_data ) {
50
+ $staff = Lib\Entities\Staff::find( $staff_data['staff_id'] );
51
+ $staff
52
+ ->setPosition( $position )
53
+ ->setCategoryId( $staff_data['category_id'] !== '' ? $staff_data['category_id'] : null )
54
+ ->save();
55
+ }
56
+ }
57
+ if ( isset( $data['categories'] ) ) {
58
+ Proxy\Pro::updateCategoriesPositions( $data['categories'] );
59
  }
 
 
 
60
  wp_send_json_success();
61
  }
62
 
backend/modules/staff/forms/StaffServices.php CHANGED
@@ -77,14 +77,20 @@ class StaffServices extends Lib\Base\Form
77
  ->where( 'location_id', $location_id )
78
  ->whereNotIn( 'service_id', (array) $this->data['service'] )
79
  ->execute();
80
- if ( isset ( $this->data['service'] ) && ( ( ! isset ( $this->data['custom_services'] ) || $this->data['custom_services'] == '1' ) ) ) {
81
  foreach ( $this->data['service'] as $service_id ) {
82
  $staff_service = new Lib\Entities\StaffService();
83
  $staff_service->loadBy( compact( 'staff_id', 'service_id', 'location_id' ) );
 
 
 
 
 
 
 
 
 
84
  $staff_service
85
- ->setCapacityMin( $this->data['capacity_min'][ $service_id ] )
86
- ->setCapacityMax( $this->data['capacity_max'][ $service_id ] )
87
- ->setDeposit( isset ( $this->data['deposit'] ) ? $this->data['deposit'][ $service_id ] : '100%' )
88
  ->setPrice( $this->data['price'][ $service_id ] )
89
  ->setServiceId( $service_id )
90
  ->setStaffId( $staff_id )
77
  ->where( 'location_id', $location_id )
78
  ->whereNotIn( 'service_id', (array) $this->data['service'] )
79
  ->execute();
80
+ if ( isset ( $this->data['service'] ) && ( ! isset ( $this->data['custom_services'] ) || $this->data['custom_services'] == '1' ) ) {
81
  foreach ( $this->data['service'] as $service_id ) {
82
  $staff_service = new Lib\Entities\StaffService();
83
  $staff_service->loadBy( compact( 'staff_id', 'service_id', 'location_id' ) );
84
+ if ( isset( $this->data['capacity_min'][ $service_id ] ) ) {
85
+ $staff_service->setCapacityMin( $this->data['capacity_min'][ $service_id ] );
86
+ }
87
+ if ( isset( $this->data['capacity_max'][ $service_id ] ) ) {
88
+ $staff_service->setCapacityMax( $this->data['capacity_max'][ $service_id ] );
89
+ }
90
+ if ( isset( $this->data['deposit'][ $service_id ] ) ) {
91
+ $staff_service->setDeposit( $this->data['deposit'][ $service_id ] );
92
+ }
93
  $staff_service
 
 
 
94
  ->setPrice( $this->data['price'][ $service_id ] )
95
  ->setServiceId( $service_id )
96
  ->setStaffId( $staff_id )
backend/modules/staff/resources/js/staff.js CHANGED
@@ -405,7 +405,9 @@ jQuery(function ($) {
405
  function updateStaffPositions() {
406
  var data = {'categories': [], 'staff': []};
407
  $categories_list.find('.panel[data-category]').each(function () {
408
- data.categories.push($(this).data('category'));
 
 
409
  });
410
  $categories_list.find('.bookly-js-staff-members').children('li').each(function () {
411
  var $this = $(this),
405
  function updateStaffPositions() {
406
  var data = {'categories': [], 'staff': []};
407
  $categories_list.find('.panel[data-category]').each(function () {
408
+ if ($(this).data('category')) {
409
+ data.categories.push($(this).data('category'));
410
+ }
411
  });
412
  $categories_list.find('.bookly-js-staff-members').children('li').each(function () {
413
  var $this = $(this),
languages/bookly-ru_RU.mo CHANGED
Binary file
languages/bookly-ru_RU.po CHANGED
@@ -3757,18 +3757,6 @@ msgstr "Дорогой(ая) {client_name}.\n"
3757
  "{company_phone}\n"
3758
  "{company_website}"
3759
 
3760
- #:
3761
- msgid "New invoice #{invoice_number}"
3762
- msgstr "Новый счёт №{invoice_number}"
3763
-
3764
- #:
3765
- msgid "Hello.\n"
3766
- "\n"
3767
- "Attached please find invoice #{invoice_number} for an appointment scheduled by {client_first_name} {client_last_name}"
3768
- msgstr "Здравствуйте.\n"
3769
- "\n"
3770
- "В прикрепленном файле прилагается счёт №{invoice_number} за встречу, назначенную {client_first_name} {client_last_name}"
3771
-
3772
  #:
3773
  msgid "Invoice for your appointment"
3774
  msgstr "Счёт за вашу встречу"
@@ -5870,10 +5858,6 @@ msgstr "общий налог для бронирования (суммарно
5870
  msgid "total price without tax"
5871
  msgstr "общая стоимость бронирования без налога"
5872
 
5873
- #:
5874
- msgid "Note if you use price correction to change the service cost according to the payment gateway used, tax will not be calculated for the additional amount to the cost. If you need to report the exact tax amount to the payment system, do not use additional charge."
5875
- msgstr "Обратите внимание, если вы используете корректировку цены для изменения стоимости услуги в соответствии с используемой платежной системой, налог не будет рассчитываться для надбавки к стоимости. Если вам нужно сообщить точную сумму налога в платежную систему, не используйте надбавку к стоимости."
5876
-
5877
  #:
5878
  msgid "Dear {client_name}.\n"
5879
  "\n"
@@ -6155,7 +6139,7 @@ msgstr "Необходимо указать название группы"
6155
 
6156
  #:
6157
  msgid "Important: for two-way sync, your website must use HTTPS. Google Calendar API will be able to send notifications to HTTPS address only if there is a valid SSL certificate installed on your web server. Follow the steps in this <a href=\"%s\" target=\"_blank\">document</a> to <b>verify and register your domain</b>."
6158
- msgstr "Важно: для двусторонней синхронизации ваш сайт должен использовать HTTPS. API Google Календаря сможет отправлять уведомления на HTTPS-адрес только в том случае, если на вашем веб-сервере установлен действительный SSL сертификат. Выполните действия, описанные в этом <a href=\"%s\" target=\"_blank\"> документе </a>, чтобы <b> проверить и зарегистрировать свой домен </ b>."
6159
 
6160
  #:
6161
  msgid "Allows to set the start and end times for an appointment for services with the duration of 1 day or longer. This time will be displayed in notifications to customers, backend calendar and codes for booking form."
3757
  "{company_phone}\n"
3758
  "{company_website}"
3759
 
 
 
 
 
 
 
 
 
 
 
 
 
3760
  #:
3761
  msgid "Invoice for your appointment"
3762
  msgstr "Счёт за вашу встречу"
5858
  msgid "total price without tax"
5859
  msgstr "общая стоимость бронирования без налога"
5860
 
 
 
 
 
5861
  #:
5862
  msgid "Dear {client_name}.\n"
5863
  "\n"
6139
 
6140
  #:
6141
  msgid "Important: for two-way sync, your website must use HTTPS. Google Calendar API will be able to send notifications to HTTPS address only if there is a valid SSL certificate installed on your web server. Follow the steps in this <a href=\"%s\" target=\"_blank\">document</a> to <b>verify and register your domain</b>."
6142
+ msgstr "Важно: для двусторонней синхронизации ваш сайт должен использовать HTTPS. API Google Календаря сможет отправлять уведомления на HTTPS-адрес только в том случае, если на вашем веб-сервере установлен действительный SSL сертификат. Выполните действия, описанные в этом <a href=\"%s\" target=\"_blank\"> документе </a>, чтобы <b> проверить и зарегистрировать свой домен </b>."
6143
 
6144
  #:
6145
  msgid "Allows to set the start and end times for an appointment for services with the duration of 1 day or longer. This time will be displayed in notifications to customers, backend calendar and codes for booking form."
lib/notifications/Codes.php CHANGED
@@ -371,8 +371,8 @@ class Codes
371
  $this->staff_name = $item->getStaff()->getTranslatedName();
372
 
373
  if ( $order->hasPayment() ) {
374
- $this->payment_type = Entities\Payment::typeToString( $order->getPayment()->getType() );
375
- $this->payment_status = Entities\Payment::statusToString( $order->getPayment()->getStatus() );
376
  }
377
 
378
  Proxy\Shared::prepareNotificationCodesForOrder( $this );
371
  $this->staff_name = $item->getStaff()->getTranslatedName();
372
 
373
  if ( $order->hasPayment() ) {
374
+ $this->payment_type = $order->getPayment()->getType();
375
+ $this->payment_status = $order->getPayment()->getStatus();
376
  }
377
 
378
  Proxy\Shared::prepareNotificationCodesForOrder( $this );
lib/notifications/Sender.php CHANGED
@@ -579,9 +579,8 @@ abstract class Sender
579
  $subject = $codes->replace( $notification->getSubject(), 'text' );
580
 
581
  // Message.
582
- $message = Proxy\Pro::prepareNotificationMessage( $notification->getMessage(), 'admin', $notification->getGateway() );
583
- $send_as_html = Config::sendEmailAsHtml() == 'html';
584
- if ( $send_as_html ) {
585
  $message = wpautop( $codes->replace( $message, 'html' ) );
586
  } else {
587
  $message = $codes->replace( $message, 'text' );
579
  $subject = $codes->replace( $notification->getSubject(), 'text' );
580
 
581
  // Message.
582
+ $message = Proxy\Pro::prepareNotificationMessage( $notification->getMessage(), 'admin', $notification->getGateway() );
583
+ if ( Config::sendEmailAsHtml() ) {
 
584
  $message = wpautop( $codes->replace( $message, 'html' ) );
585
  } else {
586
  $message = $codes->replace( $message, 'text' );
lib/notifications/base/Codes.php ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Lib\Notifications\Base;
3
+
4
+ use Bookly\Lib\Utils;
5
+
6
+ /**
7
+ * Class Codes
8
+ * @package Bookly\Lib\Notifications\Base
9
+ */
10
+ abstract class Codes
11
+ {
12
+ /**
13
+ * Protected constructor.
14
+ */
15
+ protected function __construct()
16
+ {
17
+ // Use static methods for creating new objects.
18
+ }
19
+
20
+ /**
21
+ * Do replacements.
22
+ *
23
+ * @param string $text
24
+ * @param string $format
25
+ * @return string
26
+ */
27
+ public function replace( $text, $format = 'text' )
28
+ {
29
+ $codes = $this->getReplaceCodes( $format );
30
+
31
+ return strtr( $text, $codes );
32
+ }
33
+
34
+ /**
35
+ * Do replacements for SMS.
36
+ * Returns both personal and impersonal text.
37
+ *
38
+ * @param string $text
39
+ * @return array
40
+ */
41
+ public function replaceForSms( $text )
42
+ {
43
+ $codes = $this->getReplaceCodes( 'text' );
44
+
45
+ // Impersonal codes.
46
+ $impersonal_codes = array();
47
+ foreach ( $codes as $name => $code ) {
48
+ $count = Utils\SMSCounter::count( strval( $code ) );
49
+ if ( $count->encoding == Utils\SMSCounter::UTF16 ) {
50
+ $impersonal_symbol = "ϔ";
51
+ } else {
52
+ $impersonal_symbol = "X";
53
+ }
54
+ $impersonal_codes[ $name ] = preg_replace( '/[^\s]/', $impersonal_symbol, $code );
55
+ }
56
+
57
+ return array(
58
+ 'personal' => strtr( $text, $codes ),
59
+ 'impersonal' => strtr( $text, $impersonal_codes ),
60
+ );
61
+ }
62
+
63
+ /**
64
+ * Get replacement codes for given format.
65
+ *
66
+ * @param string $format
67
+ * @return array
68
+ */
69
+ protected function getReplaceCodes( $format )
70
+ {
71
+ $company_logo = '';
72
+
73
+ if ( $format == 'html' ) {
74
+ $img = wp_get_attachment_image_src( get_option( 'bookly_co_logo_attachment_id' ), 'full' );
75
+ // Company logo as <img> tag.
76
+ if ( $img ) {
77
+ $company_logo = sprintf(
78
+ '<img src="%s" alt="%s" />',
79
+ esc_attr( $img[0] ),
80
+ esc_attr( get_option( 'bookly_co_name' ) )
81
+ );
82
+ }
83
+ }
84
+
85
+ return array(
86
+ '{company_address}' => $format == 'html' ? nl2br( get_option( 'bookly_co_address' ) ) : get_option( 'bookly_co_address' ),
87
+ '{company_logo}' => $company_logo,
88
+ '{company_name}' => get_option( 'bookly_co_name' ),
89
+ '{company_phone}' => get_option( 'bookly_co_phone' ),
90
+ '{company_website}' => get_option( 'bookly_co_website' ),
91
+ );
92
+ }
93
+ }
lib/notifications/base/Sender.php ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Lib\Notifications\Base;
3
+
4
+ use Bookly\Lib\Config;
5
+ use Bookly\Lib\Entities\Notification;
6
+ use Bookly\Lib\Proxy;
7
+ use Bookly\Lib\SMS;
8
+ use Bookly\Lib\Utils;
9
+
10
+ /**
11
+ * Class Sender
12
+ * @package Bookly\Lib\Notifications\Base
13
+ */
14
+ abstract class Sender
15
+ {
16
+ /** @var SMS */
17
+ protected static $sms;
18
+
19
+ /**
20
+ * Protected constructor.
21
+ */
22
+ protected function __construct()
23
+ {
24
+ // Use static methods for creating new objects.
25
+ }
26
+
27
+ /**
28
+ * Send email to administrators.
29
+ *
30
+ * @param Notification $notification
31
+ * @param Codes $codes
32
+ * @param array $attachments
33
+ * @param array $extra_headers
34
+ * @return bool
35
+ */
36
+ protected function sendEmailToAdmins( Notification $notification, Codes $codes, $attachments = array(), $extra_headers = array() )
37
+ {
38
+ $admin_emails = Utils\Common::getAdminEmails();
39
+ if ( empty ( $admin_emails ) ) {
40
+ return false;
41
+ }
42
+
43
+ return $this->_sendEmail(
44
+ $admin_emails,
45
+ $notification->getSubject(),
46
+ Proxy\Pro::prepareNotificationMessage( $notification->getMessage(), 'admin', 'email' ),
47
+ $codes,
48
+ $attachments,
49
+ $extra_headers
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Send email to client.
55
+ *
56
+ * @param string $email
57
+ * @param Notification $notification
58
+ * @param Codes $codes
59
+ * @param array $attachments
60
+ * @param array $extra_headers
61
+ * @return bool
62
+ */
63
+ protected function sendEmailToClient( $email, Notification $notification, Codes $codes, $attachments = array(), $extra_headers = array() )
64
+ {
65
+ if ( $email == '' ) {
66
+ return false;
67
+ }
68
+
69
+ return $this->_sendEmail(
70
+ $email,
71
+ $notification->getTranslatedSubject(),
72
+ $notification->getTranslatedMessage(),
73
+ $codes,
74
+ $attachments,
75
+ $extra_headers
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Send email to staff.
81
+ *
82
+ * @param string $email
83
+ * @param Notification $notification
84
+ * @param Codes $codes
85
+ * @param array $attachments
86
+ * @param array $extra_headers
87
+ * @return bool
88
+ */
89
+ protected function sendEmailToStaff( $email, Notification $notification, Codes $codes, $attachments = array(), $extra_headers = array() )
90
+ {
91
+ if ( $email == '' ) {
92
+ return false;
93
+ }
94
+
95
+ return $this->_sendEmail(
96
+ $email,
97
+ $notification->getSubject(),
98
+ Proxy\Pro::prepareNotificationMessage( $notification->getMessage(), 'staff', 'email' ),
99
+ $codes,
100
+ $attachments,
101
+ $extra_headers
102
+ );
103
+ }
104
+
105
+ /**
106
+ * Send SMS to admin.
107
+ *
108
+ * @param Notification $notification
109
+ * @param Codes $codes
110
+ * @return bool
111
+ */
112
+ protected function sendSmsToAdmin( Notification $notification, Codes $codes )
113
+ {
114
+ $phone = get_option( 'bookly_sms_administrator_phone', '' );
115
+ if ( $phone == '' ) {
116
+ return false;
117
+ }
118
+
119
+ return $this->_sendSms(
120
+ $phone,
121
+ Proxy\Pro::prepareNotificationMessage( $notification->getMessage(), 'admin', 'sms' ),
122
+ $codes,
123
+ $notification->getTypeId()
124
+ );
125
+ }
126
+
127
+ /**
128
+ * Send SMS to client.
129
+ *
130
+ * @param string $phone
131
+ * @param Notification $notification
132
+ * @param Codes $codes
133
+ * @return bool
134
+ */
135
+ protected function sendSmsToClient( $phone, Notification $notification, Codes $codes )
136
+ {
137
+ if ( $phone == '' ) {
138
+ return false;
139
+ }
140
+
141
+ return $this->_sendSms(
142
+ $phone,
143
+ $notification->getTranslatedMessage(),
144
+ $codes,
145
+ $notification->getTypeId()
146
+ );
147
+ }
148
+
149
+ /**
150
+ * Send SMS to staff.
151
+ *
152
+ * @param string $phone
153
+ * @param Notification $notification
154
+ * @param Codes $codes
155
+ * @return bool
156
+ */
157
+ protected function sendSmsToStaff( $phone, Notification $notification, Codes $codes )
158
+ {
159
+ if ( $phone == '' ) {
160
+ return false;
161
+ }
162
+
163
+ return $this->_sendSms(
164
+ $phone,
165
+ Proxy\Pro::prepareNotificationMessage( $notification->getMessage(), 'staff', 'sms' ),
166
+ $codes,
167
+ $notification->getTypeId()
168
+ );
169
+ }
170
+
171
+ /**
172
+ * Send email.
173
+ *
174
+ * @param string|array $email
175
+ * @param string $subject
176
+ * @param string $message
177
+ * @param Codes $codes
178
+ * @param array $attachments
179
+ * @param array $extra_headers
180
+ * @return bool
181
+ */
182
+ private function _sendEmail( $email, $subject, $message, Codes $codes, $attachments = array(), $extra_headers = array() )
183
+ {
184
+ // Subject.
185
+ $subject = $codes->replace( $subject, 'text' );
186
+
187
+ // Message.
188
+ if ( Config::sendEmailAsHtml() ) {
189
+ $message = wpautop( $codes->replace( $message, 'html' ) );
190
+ } else {
191
+ $message = $codes->replace( $message, 'text' );
192
+ }
193
+
194
+ // Headers.
195
+ $headers = Utils\Common::getEmailHeaders( $extra_headers );
196
+
197
+ // Do send.
198
+ return wp_mail( $email, $subject, $message, $headers, $attachments );
199
+ }
200
+
201
+ /**
202
+ * Send SMS.
203
+ *
204
+ * @param string $phone
205
+ * @param string $message
206
+ * @param Codes $codes
207
+ * @param int $type_id
208
+ * @return bool
209
+ */
210
+ private function _sendSms( $phone, $message, Codes $codes, $type_id )
211
+ {
212
+ if ( self::$sms === null ) {
213
+ self::$sms = new SMS();
214
+ }
215
+
216
+ // Message.
217
+ $message = $codes->replaceForSms( $message );
218
+
219
+ // Do send.
220
+ return self::$sms->sendSms( $phone, $message['personal'], $message['impersonal'], $type_id );
221
+ }
222
+ }
lib/notifications/booking/Sender.php ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Lib\Notifications\Booking;
3
+
4
+ use Bookly\Lib\DataHolders\Booking as DataHolders;
5
+ use Bookly\Lib\Entities\Notification;
6
+ use Bookly\Lib\Notifications;
7
+ use Bookly\Lib\Notifications\NewBooking\Codes;
8
+ use Bookly\Lib\Notifications\NewBooking\ICS;
9
+ use Bookly\Lib\Proxy;
10
+
11
+ /**
12
+ * Class Sender
13
+ * @package Bookly\Lib\Notifications\Booking
14
+ */
15
+ class Sender extends Notifications\Base\Sender
16
+ {
17
+ /** @var DataHolders\Order */
18
+ protected $order;
19
+ /** @var Codes */
20
+ protected $codes;
21
+
22
+ /**
23
+ * Create new instance.
24
+ *
25
+ * @param DataHolders\Order $order
26
+ * @param Codes $codes
27
+ * @return static
28
+ */
29
+ public static function create( DataHolders\Order $order, Codes $codes )
30
+ {
31
+ $sender = new static();
32
+ $sender->order = $order;
33
+ $sender->codes = $codes;
34
+
35
+ return $sender;
36
+ }
37
+
38
+ /**
39
+ * Send notifications to client.
40
+ *
41
+ * @param DataHolders\Item $item
42
+ * @param string $lang
43
+ */
44
+ public function sendToClient( DataHolders\Item $item, $lang )
45
+ {
46
+ foreach ( $this->getNotificationsForClient() as $notification ) {
47
+ switch ( $notification->getGateway() ) {
48
+ case 'email':
49
+ $this->notifyClientByEmail( $notification, $item, $lang );
50
+ break;
51
+ case 'sms':
52
+ $this->notifyClientBySms( $notification, $item, $lang );
53
+ break;
54
+ }
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Send notifications to staff and/or admins.
60
+ *
61
+ * @param DataHolders\Item $item
62
+ * @param string $lang
63
+ */
64
+ public function sendToStaffAndAdmins( DataHolders\Item $item, $lang )
65
+ {
66
+ // Notify staff and admins.
67
+ foreach ( $this->getNotificationsForStaffAndAdmins() as $notification ) {
68
+ if ( ! $notification->getToAdmin() && $item->getStaff()->isArchived() ) {
69
+ // No recipient.
70
+ continue;
71
+ }
72
+ switch ( $notification->getGateway() ) {
73
+ case 'email':
74
+ $this->notifyStaffAndAdminsByEmail( $notification, $item, $lang );
75
+ break;
76
+ case 'sms':
77
+ $this->notifyStaffAndAdminBySms( $notification, $item, $lang );
78
+ break;
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Notify client by email.
85
+ *
86
+ * @param Notification $notification
87
+ * @param DataHolders\Item $item
88
+ * @param string $lang
89
+ */
90
+ protected function notifyClientByEmail( Notification $notification, DataHolders\Item $item, $lang )
91
+ {
92
+ if ( $this->order->getCustomer()->getEmail() == '' ) {
93
+ // No recipient.
94
+ return;
95
+ }
96
+
97
+ // Prepare codes for this item.
98
+ $this->codes->prepareForItem( $item, $lang, true );
99
+
100
+ // Attachments.
101
+ $attachments = $this->createAttachments( $notification );
102
+
103
+ // Send email to client.
104
+ $this->sendEmailToClient( $this->order->getCustomer()->getEmail(), $notification, $this->codes, $attachments );
105
+
106
+ // Clean up attachments.
107
+ $this->clearAttachments( $attachments );
108
+ }
109
+
110
+ /**
111
+ * Notify client by SMS.
112
+ *
113
+ * @param Notification $notification
114
+ * @param DataHolders\Item $item
115
+ * @param string $lang
116
+ */
117
+ protected function notifyClientBySms( Notification $notification, DataHolders\Item $item, $lang )
118
+ {
119
+ if ( $this->order->getCustomer()->getPhone() == '' ) {
120
+ // No recipient.
121
+ return;
122
+ }
123
+
124
+ // Prepare codes for this item.
125
+ $this->codes->prepareForItem( $item, $lang, true );
126
+
127
+ // Send SMS to client.
128
+ $this->sendSmsToClient( $this->order->getCustomer()->getPhone(), $notification, $this->codes );
129
+ }
130
+
131
+ /**
132
+ * Notify staff and/or administrators by email.
133
+ *
134
+ * @param Notification $notification
135
+ * @param DataHolders\Item $item
136
+ * @param string $lang
137
+ */
138
+ protected function notifyStaffAndAdminsByEmail( Notification $notification, DataHolders\Item $item, $lang )
139
+ {
140
+ if ( ! $notification->getToAdmin() && $item->getStaff()->getEmail() == '' ) {
141
+ // No recipient.
142
+ return;
143
+ }
144
+
145
+ // Prepare codes for this item.
146
+ $this->codes->prepareForItem( $item, $lang, false );
147
+
148
+ // Attachments.
149
+ $attachments = $this->createAttachments( $notification );
150
+
151
+ // Extra headers.
152
+ $extra_headers = array();
153
+ if ( get_option( 'bookly_email_reply_to_customers' ) ) {
154
+ $customer = $this->order->getCustomer();
155
+ $extra_headers = array( 'reply-to' => array( 'email' => $customer->getEmail(), 'name' => $customer->getFullName() ) );
156
+ }
157
+
158
+ // Send email to staff.
159
+ if ( $notification->getToStaff() ) {
160
+ $this->sendEmailToStaff( $item->getStaff()->getEmail(), $notification, $this->codes, $attachments, $extra_headers );
161
+ }
162
+
163
+ // Send email to administrators.
164
+ if ( $notification->getToAdmin() ) {
165
+ $this->sendEmailToAdmins( $notification, $this->codes, $attachments, $extra_headers );
166
+ }
167
+
168
+ // Clean up attachments.
169
+ $this->clearAttachments( $attachments );
170
+ }
171
+
172
+ /**
173
+ * Notify staff and/or administrator by SMS.
174
+ *
175
+ * @param Notification $notification
176
+ * @param DataHolders\Item $item
177
+ * @param string $lang
178
+ */
179
+ protected function notifyStaffAndAdminBySms( Notification $notification, DataHolders\Item $item, $lang )
180
+ {
181
+ if ( ! $notification->getToAdmin() && $item->getStaff()->getPhone() == '' ) {
182
+ // No recipients for this item.
183
+ return;
184
+ }
185
+
186
+ // Prepare codes for this item.
187
+ $this->codes->prepareForItem( $item, $lang, false );
188
+
189
+ // Send SMS to staff.
190
+ if ( $notification->getToStaff() ) {
191
+ $this->sendSmsToStaff( $item->getStaff()->getPhone(), $notification, $this->codes );
192
+ }
193
+
194
+ // Send SMS to administrator.
195
+ if ( $notification->getToAdmin() ) {
196
+ $this->sendSmsToAdmin( $notification, $this->codes );
197
+ }
198
+ }
199
+
200
+ /**
201
+ * @inheritdoc
202
+ */
203
+ protected function createAttachments( Notification $notification )
204
+ {
205
+ $attachments = array();
206
+
207
+ // ICS.
208
+ if ( $notification->getAttachIcs() ) {
209
+ $file = $this->createIcs( $this->codes );
210
+ if ( $file ) {
211
+ $attachments[] = $file;
212
+ }
213
+ }
214
+
215
+ // Invoices.
216
+ if ( $notification->getAttachInvoice() && $this->order->hasPayment() ) {
217
+ $file = Proxy\Invoices::getInvoice( $this->order->getPayment() );
218
+ if ( $file ) {
219
+ $attachments[] = $file;
220
+ }
221
+ }
222
+
223
+ return $attachments;
224
+ }
225
+
226
+ /**
227
+ * Create ICS attachment.
228
+ *
229
+ * @param Codes $codes
230
+ * @return bool|string
231
+ */
232
+ protected function createIcs( Codes $codes )
233
+ {
234
+ $ics = new ICS( $codes );
235
+
236
+ return $ics->create();
237
+ }
238
+
239
+ /**
240
+ * Remove attachment files.
241
+ *
242
+ * @param array $attachments
243
+ */
244
+ protected function clearAttachments( array $attachments )
245
+ {
246
+ foreach ( $attachments as $file ) {
247
+ unlink( $file );
248
+ }
249
+ }
250
+ }
lib/notifications/codes/Combined.php ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Lib\Notifications\Codes;
3
+
4
+ use Bookly\Lib\Config;
5
+ use Bookly\Lib\Proxy;
6
+ use Bookly\Lib\Utils;
7
+
8
+ /**
9
+ * Class Codes
10
+ * @package Bookly\Lib\Notifications\Codes
11
+ */
12
+ class Combined extends Codes
13
+ {
14
+ public $cart_info;
15
+
16
+ /**
17
+ * @inheritdoc
18
+ */
19
+ protected function getCodes( $format )
20
+ {
21
+ $cart_info_c = $cart_info = '';
22
+
23
+ // Cart info.
24
+ $cart_info_data = $this->cart_info;
25
+ if ( ! empty ( $cart_info_data ) ) {
26
+ $cart_columns = get_option( 'bookly_cart_show_columns', array() );
27
+ if ( empty( $cart_columns ) ) {
28
+ $cart_columns = array(
29
+ 'service' => array( 'show' => '1', ),
30
+ 'date' => array( 'show' => '1', ),
31
+ 'time' => array( 'show' => '1', ),
32
+ 'employee' => array( 'show' => '1', ),
33
+ 'price' => array( 'show' => '1', ),
34
+ 'deposit' => array( 'show' => (int) Config::depositPaymentsActive() ),
35
+ 'tax' => array( 'show' => (int) Config::taxesActive(), ),
36
+ );
37
+ }
38
+ if ( ! Proxy\Taxes::showTaxColumn() ) {
39
+ unset( $cart_columns['tax'] );
40
+ }
41
+ if ( ! Config::depositPaymentsActive() ) {
42
+ unset( $cart_columns['deposit'] );
43
+ }
44
+ $ths = array();
45
+ foreach ( $cart_columns as $column => $attr ) {
46
+ if ( $attr['show'] ) {
47
+ switch ( $column ) {
48
+ case 'service':
49
+ $ths[] = Utils\Common::getTranslatedOption( 'bookly_l10n_label_service' );
50
+ break;
51
+ case 'date':
52
+ $ths[] = __( 'Date', 'bookly' );
53
+ break;
54
+ case 'time':
55
+ $ths[] = __( 'Time', 'bookly' );
56
+ break;
57
+ case 'tax':
58
+ $ths[] = __( 'Tax', 'bookly' );
59
+ break;
60
+ case 'employee':
61
+ $ths[] = Utils\Common::getTranslatedOption( 'bookly_l10n_label_employee' );
62
+ break;
63
+ case 'price':
64
+ $ths[] = __( 'Price', 'bookly' );
65
+ break;
66
+ case 'deposit':
67
+ $ths[] = __( 'Deposit', 'bookly' );
68
+ break;
69
+ }
70
+ }
71
+ }
72
+ $trs = array();
73
+ foreach ( $cart_info_data as $codes ) {
74
+ $tds = array();
75
+ foreach ( $cart_columns as $column => $attr ) {
76
+ if ( $attr['show'] ) {
77
+ switch ( $column ) {
78
+ case 'service':
79
+ $service_name = $codes['service_name'];
80
+ if ( ! empty ( $codes['extras'] ) ) {
81
+ $extras = '';
82
+ if ( $format == 'html' ) {
83
+ foreach ( $codes['extras'] as $extra ) {
84
+ $extras .= '<li>' . $extra['title'] . '</li>';
85
+ }
86
+ $extras = '<ul>' . $extras . '</ul>';
87
+ } else {
88
+ foreach ( $codes['extras'] as $extra ) {
89
+ $extras .= ', ' . str_replace( '&nbsp;&times;&nbsp;', ' x ', $extra['title'] );
90
+ }
91
+ }
92
+ $service_name .= $extras;
93
+ }
94
+ $tds[] = $service_name;
95
+ break;
96
+ case 'date':
97
+ $tds[] = $codes['appointment_start'] === null ? __( 'N/A', 'bookly' ) : Utils\DateTime::formatDate( $codes['appointment_start'] );
98
+ break;
99
+ case 'time':
100
+ if ( $codes['appointment_start_info'] !== null ) {
101
+ $tds[] = $codes['appointment_start_info'];
102
+ } else {
103
+ $tds[] = $codes['appointment_start'] === null ? __( 'N/A', 'bookly' ) : Utils\DateTime::formatTime( $codes['appointment_start'] );
104
+ }
105
+ break;
106
+ case 'tax':
107
+ $tds[] = Utils\Price::format( $codes['tax'] );
108
+ break;
109
+ case 'employee':
110
+ $tds[] = $codes['staff_name'];
111
+ break;
112
+ case 'price':
113
+ $tds[] = Utils\Price::format( $codes['appointment_price'] );
114
+ break;
115
+ case 'deposit':
116
+ $tds[] = $codes['deposit'];
117
+ break;
118
+ }
119
+ }
120
+ }
121
+ $tds[] = $codes['cancel_url'];
122
+ $trs[] = $tds;
123
+ }
124
+ if ( $format == 'html' ) {
125
+ $cart_info = '<table cellspacing="1" border="1" cellpadding="5"><thead><tr><th>' . implode( '</th><th>', $ths ) . '</th></tr></thead><tbody>';
126
+ $cart_info_c = '<table cellspacing="1" border="1" cellpadding="5"><thead><tr><th>' . implode( '</th><th>', $ths ) . '</th><th>' . __( 'Cancel', 'bookly' ) . '</th></tr></thead><tbody>';
127
+ foreach ( $trs as $tr ) {
128
+ $cancel_url = array_pop( $tr );
129
+ $cart_info .= '<tr><td>' . implode( '</td><td>', $tr ) . '</td></tr>';
130
+ $cart_info_c .= '<tr><td>' . implode( '</td><td>', $tr ) . '</td><td><a href="' . $cancel_url . '">' . __( 'Cancel', 'bookly' ) . '</a></td></tr>';
131
+ }
132
+ $cart_info .= '</tbody></table>';
133
+ $cart_info_c .= '</tbody></table>';
134
+ } else {
135
+ foreach ( $trs as $tr ) {
136
+ $cancel_url = array_pop( $tr );
137
+ foreach ( $ths as $position => $column ) {
138
+ $cart_info .= $column . ' ' . $tr[ $position ] . "\r\n";
139
+ $cart_info_c .= $column . ' ' . $tr[ $position ] . "\r\n";
140
+ }
141
+ $cart_info .= "\r\n";
142
+ $cart_info_c .= __( 'Cancel', 'bookly' ) . ' ' . $cancel_url . "\r\n\r\n";
143
+ }
144
+ }
145
+ }
146
+ // Codes.
147
+ $codes = array_merge( parent::getCodes( $format ), array(
148
+ '{cart_info}' => $cart_info,
149
+ '{cart_info_c}' => $cart_info_c,
150
+ ) );
151
+
152
+ return $codes;
153
+ }
154
+ }
lib/notifications/new_booking/Codes.php ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Lib\Notifications\NewBooking;
3
+
4
+ use Bookly\Lib\Config;
5
+ use Bookly\Lib\DataHolders\Booking as DataHolders;
6
+ use Bookly\Lib\Entities;
7
+ use Bookly\Lib\Notifications\Base;
8
+ use Bookly\Lib\Utils;
9
+
10
+ /**
11
+ * Class Codes
12
+ * @package Bookly\Lib\Notifications\NewBooking
13
+ */
14
+ class Codes extends Base\Codes
15
+ {
16
+ public $agenda_date;
17
+ public $amount_due;
18
+ public $amount_paid;
19
+ public $appointment_end;
20
+ public $appointment_end_info;
21
+ public $appointment_notes;
22
+ public $appointment_schedule;
23
+ public $appointment_schedule_c;
24
+ public $appointment_start;
25
+ public $appointment_start_info;
26
+ public $appointment_token;
27
+ public $appointment_waiting_list;
28
+ public $booking_number;
29
+ public $cancellation_reason;
30
+ public $cart_info;
31
+ public $category_name;
32
+ public $client_email;
33
+ public $client_address;
34
+ public $client_first_name;
35
+ public $client_last_name;
36
+ public $client_name;
37
+ public $client_phone;
38
+ public $client_timezone;
39
+ public $custom_fields;
40
+ public $custom_fields_2c;
41
+ public $deposit_value;
42
+ public $extras;
43
+ public $extras_total_price;
44
+ public $files_count;
45
+ public $google_calendar_url;
46
+ public $invoice_date;
47
+ public $invoice_due_date;
48
+ public $invoice_due_days;
49
+ public $invoice_link;
50
+ public $invoice_number; // payment_id
51
+ public $location_info;
52
+ public $location_name;
53
+ public $new_password;
54
+ public $new_username;
55
+ public $next_day_agenda;
56
+ public $next_day_agenda_extended;
57
+ public $number_of_persons;
58
+ public $package_life_time;
59
+ public $package_name;
60
+ public $package_price;
61
+ public $package_size;
62
+ public $payment_type;
63
+ public $payment_status;
64
+ public $schedule;
65
+ public $series_token;
66
+ public $service_duration;
67
+ public $service_info;
68
+ public $service_name;
69
+ public $service_price;
70
+ public $service_tax;
71
+ public $service_tax_rate;
72
+ public $site_address;
73
+ public $staff_email;
74
+ public $staff_info;
75
+ public $staff_name;
76
+ public $staff_phone;
77
+ public $staff_photo;
78
+ public $staff_rating_url;
79
+ public $total_price;
80
+ public $total_tax;
81
+
82
+ /** @var DataHolders\Order */
83
+ protected $order;
84
+ /** @var DataHolders\Item */
85
+ protected $item;
86
+ /** @var string */
87
+ protected $lang;
88
+ /** @var bool */
89
+ protected $use_client_tz;
90
+
91
+ /**
92
+ * Create new instance.
93
+ *
94
+ * @param DataHolders\Order $order
95
+ * @return static
96
+ */
97
+ public static function create( DataHolders\Order $order )
98
+ {
99
+ $codes = new static();
100
+
101
+ $codes->order = $order;
102
+
103
+ $codes->client_address = $order->getCustomer()->getAddress();
104
+ $codes->client_email = $order->getCustomer()->getEmail();
105
+ $codes->client_first_name = $order->getCustomer()->getFirstName();
106
+ $codes->client_last_name = $order->getCustomer()->getLastName();
107
+ $codes->client_name = $order->getCustomer()->getFullName();
108
+ $codes->client_phone = $order->getCustomer()->getPhone();
109
+ if ( $order->hasPayment() ) {
110
+ $codes->amount_paid = $order->getPayment()->getPaid();
111
+ $codes->amount_due = $order->getPayment()->getTotal() - $order->getPayment()->getPaid();
112
+ $codes->total_price = $order->getPayment()->getTotal();
113
+ $codes->total_tax = $order->getPayment()->getTax();
114
+ $codes->invoice_number = $order->getPayment()->getId();
115
+ $codes->payment_status = $order->getPayment()->getStatus();
116
+ $codes->payment_type = $order->getPayment()->getType();
117
+ }
118
+
119
+ Proxy\Shared::prepareCodesForOrder( $codes );
120
+
121
+ return $codes;
122
+ }
123
+
124
+ /**
125
+ * Prepare codes for given order item.
126
+ *
127
+ * @param DataHolders\Item $item
128
+ * @param string $lang
129
+ * @param bool $use_client_tz
130
+ */
131
+ public function prepareForItem( DataHolders\Item $item, $lang, $use_client_tz )
132
+ {
133
+ if (
134
+ $this->item === $item &&
135
+ $this->lang == $lang &&
136
+ ( $this->use_client_tz == $use_client_tz || $item->getCA()->getTimeZoneOffset() === null )
137
+ ) {
138
+ return;
139
+ }
140
+
141
+ $this->item = $item;
142
+ $this->lang = $lang;
143
+ $this->use_client_tz = $use_client_tz;
144
+
145
+ $staff_photo = wp_get_attachment_image_src( $item->getStaff()->getAttachmentId(), 'full' );
146
+
147
+ $this->appointment_end = $this->tz( $item->getTotalEnd()->format( 'Y-m-d H:i:s' ) );
148
+ $this->appointment_end_info = $item->getService()->getEndTimeInfo();
149
+ $this->appointment_notes = $item->getCA()->getNotes();
150
+ $this->appointment_start = $this->tz( $item->getAppointment()->getStartDate() );
151
+ $this->appointment_start_info = $item->getService()->getStartTimeInfo();
152
+ $this->appointment_token = $item->getCA()->getToken();
153
+ $this->booking_number = $item->getAppointment()->getId();
154
+ $this->category_name = $item->getService()->getTranslatedCategoryName();
155
+ $this->client_timezone = $item->getCA()->getTimeZone() ?: (
156
+ $item->getCA()->getTimeZoneOffset() !== null
157
+ ? 'UTC' . Utils\DateTime::guessTimeZone( - $item->getCA()->getTimeZoneOffset() * 60 )
158
+ : ''
159
+ );
160
+ $this->number_of_persons = $item->getCA()->getNumberOfPersons();
161
+ $this->service_duration = $item->getServiceDuration();
162
+ $this->service_info = $item->getService()->getTranslatedInfo();
163
+ $this->service_name = $item->getService()->getTranslatedTitle();
164
+ $this->service_price = $item->getServicePrice();
165
+ $this->staff_email = $item->getStaff()->getEmail();
166
+ $this->staff_info = $item->getStaff()->getTranslatedInfo();
167
+ $this->staff_name = $item->getStaff()->getTranslatedName();
168
+ $this->staff_phone = $item->getStaff()->getPhone();
169
+ $this->staff_photo = $staff_photo ? $staff_photo[0] : '';
170
+ if ( ! $this->order->hasPayment() ) {
171
+ $this->total_price = $item->getTotalPrice();
172
+ $this->total_tax = $item->getTax();
173
+ if ( Config::taxesActive() && get_option( 'bookly_taxes_in_price' ) == 'excluded' ) {
174
+ $this->total_price += $this->total_tax;
175
+ }
176
+ }
177
+
178
+ Proxy\Shared::prepareCodesForItem( $this );
179
+ }
180
+
181
+ /**
182
+ * @inheritdoc
183
+ */
184
+ protected function getReplaceCodes( $format )
185
+ {
186
+ $replace_codes = parent::getReplaceCodes( $format );
187
+
188
+ // Prepare data.
189
+ $staff_photo = '';
190
+ if ( $format == 'html' ) {
191
+ if ( $this->staff_photo != '' ) {
192
+ // Staff photo as <img> tag.
193
+ $staff_photo = sprintf(
194
+ '<img src="%s" alt="%s" />',
195
+ esc_attr( $this->staff_photo ),
196
+ esc_attr( $this->staff_name )
197
+ );
198
+ }
199
+ }
200
+ $cancel_appointment_confirm_url = get_option( 'bookly_url_cancel_confirm_page_url' );
201
+ $cancel_appointment_confirm_url = $this->appointment_token
202
+ ? add_query_arg( 'bookly-appointment-token', $this->appointment_token, $cancel_appointment_confirm_url )
203
+ : '';
204
+
205
+ // Add replace codes.
206
+ $replace_codes += array(
207
+ '{agenda_date}' => $this->agenda_date ? Utils\DateTime::formatDate( $this->agenda_date ) : '',
208
+ '{amount_due}' => Utils\Price::format( $this->amount_due ),
209
+ '{amount_paid}' => Utils\Price::format( $this->amount_paid ),
210
+ '{appointment_date}' => $this->appointment_start === null ? __( 'N/A', 'bookly' ) : Utils\DateTime::formatDate( $this->appointment_start ),
211
+ '{appointment_time}' => $this->appointment_start === null ? __( 'N/A', 'bookly' ) : ( $this->service_duration < DAY_IN_SECONDS ? Utils\DateTime::formatTime( $this->appointment_start ) : $this->appointment_start_info ),
212
+ '{appointment_end_date}' => $this->appointment_start === null ? __( 'N/A', 'bookly' ) : Utils\DateTime::formatDate( $this->appointment_end ),
213
+ '{appointment_end_time}' => $this->appointment_start === null ? __( 'N/A', 'bookly' ) : ( $this->service_duration < DAY_IN_SECONDS ? Utils\DateTime::formatTime( $this->appointment_end ) : $this->appointment_end_info ),
214
+ '{appointment_notes}' => $format == 'html' ? nl2br( $this->appointment_notes ) : $this->appointment_notes,
215
+ '{approve_appointment_url}' => $this->appointment_token ? admin_url( 'admin-ajax.php?action=bookly_approve_appointment&token=' . urlencode( Utils\Common::xorEncrypt( $this->appointment_token, 'approve' ) ) ) : '',
216
+ '{booking_number}' => $this->booking_number,
217
+ '{cancel_appointment_url}' => $this->appointment_token ? admin_url( 'admin-ajax.php?action=bookly_cancel_appointment&token=' . $this->appointment_token ) : '',
218
+ '{cancel_appointment_confirm_url}' => $cancel_appointment_confirm_url,
219
+ '{category_name}' => $this->category_name,
220
+ '{client_email}' => $this->client_email,
221
+ '{client_address}' => $this->client_address,
222
+ '{client_name}' => $this->client_name,
223
+ '{client_first_name}' => $this->client_first_name,
224
+ '{client_last_name}' => $this->client_last_name,
225
+ '{client_phone}' => $this->client_phone,
226
+ '{client_timezone}' => $this->client_timezone,
227
+ '{google_calendar_url}' => sprintf( 'https://calendar.google.com/calendar/render?action=TEMPLATE&text=%s&dates=%s/%s&details=%s',
228
+ urlencode( $this->service_name ),
229
+ date( 'Ymd\THis', strtotime( $this->appointment_start ) ),
230
+ date( 'Ymd\THis', strtotime( $this->appointment_end ) ),
231
+ urlencode( sprintf( "%s\n%s", $this->service_name, $this->staff_name ) )
232
+ ),
233
+ '{new_password}' => $this->new_password,
234
+ '{new_username}' => $this->new_username,
235
+ '{next_day_agenda}' => $this->next_day_agenda,
236
+ '{next_day_agenda_extended}' => $this->next_day_agenda_extended,
237
+ '{number_of_persons}' => $this->number_of_persons,
238
+ '{payment_type}' => Entities\Payment::typeToString( $this->payment_type ),
239
+ '{payment_status}' => Entities\Payment::statusToString( $this->payment_status ),
240
+ '{reject_appointment_url}' => $this->appointment_token ? admin_url( 'admin-ajax.php?action=bookly_reject_appointment&token=' . urlencode( Utils\Common::xorEncrypt( $this->appointment_token, 'reject' ) ) ) : '',
241
+ '{service_info}' => $format == 'html' ? nl2br( $this->service_info ) : $this->service_info,
242
+ '{service_name}' => $this->service_name,
243
+ '{service_price}' => Utils\Price::format( $this->service_price ),
244
+ '{service_duration}' => $this->appointment_start === null ? __( 'N/A', 'bookly' ) : Utils\DateTime::secondsToInterval( $this->service_duration ),
245
+ '{site_address}' => $this->site_address,
246
+ '{staff_email}' => $this->staff_email,
247
+ '{staff_info}' => $format == 'html' ? nl2br( $this->staff_info ) : $this->staff_info,
248
+ '{staff_name}' => $this->staff_name,
249
+ '{staff_phone}' => $this->staff_phone,
250
+ '{staff_photo}' => $staff_photo,
251
+ '{tomorrow_date}' => Utils\DateTime::formatDate( date_create( current_time( 'mysql' ) )->modify( '+1 day' )->format( 'Y-m-d' ) ),
252
+ '{total_price}' => Utils\Price::format( $this->total_price ),
253
+ '{total_tax}' => Utils\Price::format( $this->total_tax ),
254
+ '{total_price_no_tax}' => Utils\Price::format( $this->total_price - $this->total_tax ),
255
+ '{cancellation_reason}' => $this->cancellation_reason,
256
+ );
257
+ $replace_codes['{cancel_appointment}'] = $format == 'html'
258
+ ? sprintf( '<a href="%1$s">%1$s</a>', $replace_codes['{cancel_appointment_url}'] )
259
+ : $replace_codes['{cancel_appointment_url}'];
260
+
261
+ return Proxy\Shared::prepareReplaceCodes( $replace_codes, $this, $format );
262
+ }
263
+
264
+ /**
265
+ * Get order.
266
+ *
267
+ * @return DataHolders\Order
268
+ */
269
+ public function getOrder()
270
+ {
271
+ return $this->order;
272
+ }
273
+
274
+ /**
275
+ * Get item.
276
+ *
277
+ * @return DataHolders\Item
278
+ */
279
+ public function getItem()
280
+ {
281
+ return $this->item;
282
+ }
283
+
284
+ /**
285
+ * Apply client time zone to given datetime string in WP time zone if use_client_tz is true.
286
+ *
287
+ * @param string $datetime
288
+ * @return mixed
289
+ */
290
+ public function tz( $datetime )
291
+ {
292
+ if ( $this->use_client_tz && $datetime != '' ) {
293
+ $time_zone = $this->item->getCA()->getTimeZone();
294
+ $time_zone_offset = $this->item->getCA()->getTimeZoneOffset();
295
+
296
+ if ( $time_zone !== null ) {
297
+ $datetime = date_create( $datetime . ' ' . Config::getWPTimeZone() );
298
+ return date_format( date_timestamp_set( date_create( $time_zone ), $datetime->getTimestamp() ), 'Y-m-d H:i:s' );
299
+ } else if ( $time_zone_offset !== null ) {
300
+ return Utils\DateTime::applyTimeZoneOffset( $datetime, $time_zone_offset );
301
+ }
302
+ }
303
+
304
+ return $datetime;
305
+ }
306
+ }
lib/notifications/new_booking/Sender.php ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Lib\Notifications\NewBooking;
3
+
4
+ use Bookly\Lib\DataHolders\Booking as DataHolders;
5
+
6
+ /**
7
+ * Class Sender
8
+ * @package Bookly\Lib\Notifications\NewBooking
9
+ */
10
+ class Sender
11
+ {
12
+ /** @var DataHolders\Order */
13
+ protected $order;
14
+ /** @var ItemSenders\Base[] */
15
+ protected $item_senders;
16
+ /** @var Codes */
17
+ protected $codes;
18
+ /** @var string */
19
+ protected $initial_lang;
20
+
21
+ /**
22
+ * Create new sender for order.
23
+ *
24
+ * @param DataHolders\Order $order
25
+ * @return static
26
+ */
27
+ public static function createForOrder( DataHolders\Order $order )
28
+ {
29
+ $sender = new static();
30
+ $sender->order = $order;
31
+ $sender->codes = Codes::create( $order );
32
+
33
+ return $sender;
34
+ }
35
+
36
+ /**
37
+ * Send notifications.
38
+ */
39
+ public function send()
40
+ {
41
+ foreach ( $this->order->getItems() as $item ) {
42
+ $sender = $this->getSenderForItem( $item );
43
+ if ( $sender ) {
44
+ // Notify client.
45
+ $lang = $this->wpmlSwitchToItemLang( $item );
46
+ $sender->sendToClient( $item, $lang );
47
+ // Notify staff and admins.
48
+ $lang = $this->wpmlSwitchToDefaultLang();
49
+ $sender->sendToStaffAndAdmins( $item, $lang );
50
+ }
51
+ }
52
+ $this->wpmlRestoreLang();
53
+ }
54
+
55
+ /**
56
+ * Get sender for given order item.
57
+ *
58
+ * @param DataHolders\Item $item
59
+ * @return ItemSenders\Base|null
60
+ */
61
+ protected function getSenderForItem( DataHolders\Item $item )
62
+ {
63
+ if ( ! isset ( $this->item_senders[ $item->getType() ] ) ) {
64
+ $sender = $item->isSimple()
65
+ ? ItemSenders\Simple::create( $this->order, $this->codes )
66
+ : Proxy\Shared::getSenderForItem( null, $item, $this->order, $this->codes );
67
+ $this->item_senders[ $item->getType() ] = $sender;
68
+ }
69
+
70
+ return $this->item_senders[ $item->getType() ];
71
+ }
72
+
73
+ /**
74
+ * Switch WPML lang.
75
+ *
76
+ * @param string $lang
77
+ * @return string|null
78
+ */
79
+ protected function wpmlSwitchLang( $lang )
80
+ {
81
+ global $sitepress;
82
+
83
+ if ( $sitepress instanceof \SitePress ) {
84
+ if ( $lang != $sitepress->get_current_language() ) {
85
+ if ( $this->initial_lang === null ) {
86
+ $this->initial_lang = $sitepress->get_current_language();
87
+ }
88
+ $sitepress->switch_lang( $lang );
89
+ // WPML Multilingual CMS 3.9.2 // 2018-02
90
+ // Does not overload the date translation
91
+ $GLOBALS['wp_locale'] = new \WP_Locale();
92
+ }
93
+
94
+ return $lang;
95
+ }
96
+
97
+ return null;
98
+ }
99
+
100
+ /**
101
+ * Switch WPML to default lang.
102
+ *
103
+ * @return string|null
104
+ */
105
+ protected function wpmlSwitchToDefaultLang()
106
+ {
107
+ global $sitepress;
108
+
109
+ if ( $sitepress instanceof \SitePress ) {
110
+ return $this->wpmlSwitchLang( $sitepress->get_default_language() );
111
+ }
112
+
113
+ return null;
114
+ }
115
+
116
+ /**
117
+ * Switch WPML to client lang of given order item.
118
+ *
119
+ * @param DataHolders\Item $item
120
+ * @return string|null
121
+ */
122
+ protected function wpmlSwitchToItemLang( DataHolders\Item $item )
123
+ {
124
+ $lang = $item->getCA()->getLocale();
125
+ if ( $lang ) {
126
+ return $this->wpmlSwitchLang( $lang );
127
+ } else {
128
+ return $this->wpmlSwitchToDefaultLang();
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Restore WPML lang.
134
+ */
135
+ protected function wpmlRestoreLang()
136
+ {
137
+ if ( $this->initial_lang !== null ) {
138
+ $this->wpmlSwitchLang( $this->initial_lang );
139
+ $this->initial_lang = null;
140
+ }
141
+ }
142
+ }
lib/notifications/new_booking/proxy/Shared.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Lib\Notifications\NewBooking\Proxy;
3
+
4
+ use Bookly\Lib;
5
+ use Bookly\Lib\DataHolders\Booking\Item;
6
+ use Bookly\Lib\DataHolders\Booking\Order;
7
+ use Bookly\Lib\Notifications\NewBooking\Codes;
8
+ use Bookly\Lib\Notifications\NewBooking\ItemSenders;
9
+
10
+ /**
11
+ * Class Shared
12
+ * @package Bookly\Lib\Notifications\NewBooking\Proxy
13
+ *
14
+ * @method static ItemSenders\Base getSenderForItem( $default, Item $item, Order $order, Codes $codes ) Get sender for given order item.
15
+ * @method static void prepareCodesForItem( Codes $codes ) Prepare codes data for new order item (translatable data should be set here).
16
+ * @method static void prepareCodesForOrder( Codes $codes ) Prepare codes data for order.
17
+ * @method static array prepareNotificationTitles( array $titles ) Prepare notification titles.
18
+ * @method static array prepareNotificationTypeIds( array $type_ids ) Prepare notification type IDs.
19
+ * @method static array prepareReplaceCodes( array $replace_codes, Codes $codes, $format ) Prepare codes for replacements.
20
+ * @method static Lib\Notifications\Codes prepareTestNotificationCodes( Lib\Notifications\Codes $codes ) Prepare codes for testing email templates
21
+ */
22
+ abstract class Shared extends Lib\Base\Proxy
23
+ {
24
+
25
+ }
lib/notifications/status_changed/StatusChanged.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Bookly\Lib\Notifications\Senders;
3
+
4
+ use Bookly\Lib\Config;
5
+ use Bookly\Lib\DataHolders\Booking as DataHolders;
6
+ use Bookly\Lib\DataHolders\Notification\Settings;
7
+ use Bookly\Lib\Entities\Notification;
8
+ use Bookly\Lib\Notifications\Codes;
9
+
10
+ /**
11
+ * Class StatusChanged
12
+ * @package Bookly\Lib\Notifications\Senders
13
+ */
14
+ class StatusChanged extends BaseSender
15
+ {
16
+ /**
17
+ * Create new instance.
18
+ *
19
+ * @param DataHolders\Item $item
20
+ * @return static
21
+ */
22
+ public static function create( DataHolders\Item $item )
23
+ {
24
+ $sender = new static();
25
+ $sender->order = DataHolders\Order::createFromItem( $item );
26
+ $sender->codes = Codes\Codes::createForOrder( $sender->order );
27
+
28
+ return $sender;
29
+ }
30
+
31
+ /**
32
+ * @inheritdoc
33
+ */
34
+ protected function fetchNotifications()
35
+ {
36
+ $this->client_notifications = array();
37
+ $this->staff_notifications = array();
38
+ /** @var Notification[] $notifications */
39
+ $notifications = Notification::query()
40
+ ->where( '`type`', Notification::TYPE_CUSTOMER_APPOINTMENT_STATUS_CHANGED )
41
+ ->where( 'active', '1' )
42
+ ->find()
43
+ ;
44
+ foreach ( $notifications as $notification ) {
45
+ if ( Config::proActive() || $notification->getGateway() == 'sms' ) {
46
+ $settings = new Settings( $notification );
47
+ if ( $settings->getInstant() && ! $settings->getRepeated() ) {
48
+ if ( $notification->getToCustomer() ) {
49
+ $this->client_notifications[] = $notification;
50
+ }
51
+ if ( $notification->getToStaff() || $notification->getToAdmin() ) {
52
+ $this->staff_notifications[] = $notification;
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
lib/slots/Staff.php CHANGED
@@ -27,7 +27,7 @@ class Staff
27
  */
28
  public function __construct()
29
  {
30
- $this->schedule = array();
31
  $this->bookings = array();
32
  $this->services = array();
33
  }
27
  */
28
  public function __construct()
29
  {
30
+ $this->schedule = array( new Schedule() );
31
  $this->bookings = array();
32
  $this->services = array();
33
  }
main.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Bookly
4
  Plugin URI: https://www.booking-wp-plugin.com/?utm_source=bookly_admin&utm_medium=plugins_page&utm_campaign=plugins_page
5
  Description: Bookly Plugin – is a great easy-to-use and easy-to-manage booking tool for service providers who think about their customers. The plugin supports a wide range of services provided by business and individuals who offer reservations through websites. Set up any reservation quickly, pleasantly and easily with Bookly!
6
- Version: 16.3
7
  Author: Bookly
8
  Author URI: https://www.booking-wp-plugin.com/?utm_source=bookly_admin&utm_medium=plugins_page&utm_campaign=plugins_page
9
  Text Domain: bookly
3
  Plugin Name: Bookly
4
  Plugin URI: https://www.booking-wp-plugin.com/?utm_source=bookly_admin&utm_medium=plugins_page&utm_campaign=plugins_page
5
  Description: Bookly Plugin – is a great easy-to-use and easy-to-manage booking tool for service providers who think about their customers. The plugin supports a wide range of services provided by business and individuals who offer reservations through websites. Set up any reservation quickly, pleasantly and easily with Bookly!
6
+ Version: 16.4
7
  Author: Bookly
8
  Author URI: https://www.booking-wp-plugin.com/?utm_source=bookly_admin&utm_medium=plugins_page&utm_campaign=plugins_page
9
  Text Domain: bookly
readme.txt CHANGED
@@ -5,7 +5,7 @@ Donate link: https://www.booking-wp-plugin.com/
5
  Requires at least: 3.7
6
  Tested up to: 4.9.8
7
  Requires PHP: 5.3.7
8
- Stable tag: 16.3
9
  License: GPLv3
10
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
11
 
@@ -106,6 +106,7 @@ Also, Bookly is an excellent solution for **web studios** and **developers**, wh
106
  ### 4. Bookly Pro add-on features:
107
 
108
  **Bookly Pro** add-on allows you to use more features and settings, install other add-ons for Bookly, includes six months of customer support, and provides you with advanced capabilities for automating your **online scheduling system**. Some of them include:
 
109
  * **Unlimited** number of **staff members** with an individual working schedule and ability to manage their profiles and online booking calendar
110
  * **Unlimited** number of **services** with additional settings (padding time, visibility, limitations, etc.)
111
  * Ability to receive secure and flexible **online payments** on your website
@@ -149,7 +150,7 @@ Bookly Pro is a paid version which requires the Bookly Pro add-on. Purchase and
149
 
150
  = What’s the price of the Bookly Pro add-on? =
151
 
152
- Bookly has a free version which can be used for the unlimited period and contains most of the basic features. Bookly Pro add-on costs $89 and includes six months of customer support and lifetime free updates of the plugin. For Pro version you can also install add-ons. You can check the add-ons prices [here](https://codecanyon.net/user/ladela/portfolio?utm_source=WP_ORG&utm_medium=cpc&utm_campaign=wp_FAQ&ref=ladela).
153
 
154
  = Can I install add-ons with the free version of Bookly? =
155
 
@@ -196,7 +197,7 @@ You can access all Bookly capabilities from the admin area, which will be an int
196
 
197
  1. In *Bookly menu –> SMS Notifications*, click on "Register" in the login form on the left side of the page;
198
  2. Top up your balance by one of the standard $10, $25, $50, or $100 amounts (transaction processed by PayPal);
199
- 3. If you use cron for scheduled notifications, make sure to update your cron settings by replacing the current command with the following:
200
  wget -q -O - http://[your-domain]/wp-cron.php
201
  4. Choose notification types and build your messages using shortcodes: tick a checkbox, and an editor field will open. Write the copy, format it as you like, and add personalized data using shortcodes that you’ll see just below the text edit window.
202
 
5
  Requires at least: 3.7
6
  Tested up to: 4.9.8
7
  Requires PHP: 5.3.7
8
+ Stable tag: 16.4
9
  License: GPLv3
10
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
11
 
106
  ### 4. Bookly Pro add-on features:
107
 
108
  **Bookly Pro** add-on allows you to use more features and settings, install other add-ons for Bookly, includes six months of customer support, and provides you with advanced capabilities for automating your **online scheduling system**. Some of them include:
109
+
110
  * **Unlimited** number of **staff members** with an individual working schedule and ability to manage their profiles and online booking calendar
111
  * **Unlimited** number of **services** with additional settings (padding time, visibility, limitations, etc.)
112
  * Ability to receive secure and flexible **online payments** on your website
150
 
151
  = What’s the price of the Bookly Pro add-on? =
152
 
153
+ Bookly has a free version which can be used for the unlimited period and contains most of the basic features. You can check the current price and purchase the Bookly Pro add-on [here](https://codecanyon.net/item/bookly/7226091?utm_campaign=wp_FAQ&utm_medium=cpc&utm_source=WP_ORG&ref=ladela). It includes six months of customer support and lifetime free updates of the plugin. For Pro version you can also install add-ons. You can check the add-ons prices [here](https://codecanyon.net/user/ladela/portfolio?utm_source=WP_ORG&utm_medium=cpc&utm_campaign=wp_FAQ&ref=ladela).
154
 
155
  = Can I install add-ons with the free version of Bookly? =
156
 
197
 
198
  1. In *Bookly menu –> SMS Notifications*, click on "Register" in the login form on the left side of the page;
199
  2. Top up your balance by one of the standard $10, $25, $50, or $100 amounts (transaction processed by PayPal);
200
+ 3. To send scheduled notifications please execute the following command hourly with your cron:
201
  wget -q -O - http://[your-domain]/wp-cron.php
202
  4. Choose notification types and build your messages using shortcodes: tick a checkbox, and an editor field will open. Write the copy, format it as you like, and add personalized data using shortcodes that you’ll see just below the text edit window.
203